mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
Merge branch 'master' of github.com:darkrenaissance/darkfi into dao_demo
This commit is contained in:
4
Makefile
4
Makefile
@@ -54,14 +54,14 @@ fix: token_lists zkas $(PROOFS_BIN)
|
|||||||
clippy: token_lists zkas $(PROOFS_BIN)
|
clippy: token_lists zkas $(PROOFS_BIN)
|
||||||
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clippy --release --all-features --all
|
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clippy --release --all-features --all
|
||||||
|
|
||||||
rustdoc: token_lists
|
rustdoc: token_lists zkas
|
||||||
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) doc --release --workspace --all-features \
|
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) doc --release --workspace --all-features \
|
||||||
--no-deps --document-private-items
|
--no-deps --document-private-items
|
||||||
|
|
||||||
test: token_lists zkas $(PROOFS_BIN) test-tx
|
test: token_lists zkas $(PROOFS_BIN) test-tx
|
||||||
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) test --release --all-features --all
|
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) test --release --all-features --all
|
||||||
|
|
||||||
test-tx:
|
test-tx: zkas
|
||||||
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) run --release --features=node,zkas --example tx
|
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) run --release --features=node,zkas --example tx
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -5,9 +5,10 @@
|
|||||||
[](https://dark.fi/manifesto.html)
|
[](https://dark.fi/manifesto.html)
|
||||||
[](https://darkrenaissance.github.io/darkfi)
|
[](https://darkrenaissance.github.io/darkfi)
|
||||||
|
|
||||||
## Connect to darkfi IRC
|
## Connect to DarkFi IRC
|
||||||
|
|
||||||
Follow [installation instructions](https://darkrenaissance.github.io/darkfi/misc/ircd.html#installation) for the p2p IRC daemon.
|
Follow the [installation instructions](https://darkrenaissance.github.io/darkfi/misc/ircd.html#installation)
|
||||||
|
for the P2P IRC daemon.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
@@ -30,8 +31,8 @@ The following dependencies are also required:
|
|||||||
| freetype2 libs | libfreetype6-dev |
|
| freetype2 libs | libfreetype6-dev |
|
||||||
| expat xml lib | libexpat1-dev |
|
| expat xml lib | libexpat1-dev |
|
||||||
|
|
||||||
Users of Debian-based systems (e.g. Ubuntu) can simply run the following
|
Users of Debian-based systems (e.g. Ubuntu) can simply run the
|
||||||
to install the required dependencies:
|
following to install the required dependencies:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# apt-get update
|
# apt-get update
|
||||||
@@ -40,20 +41,21 @@ to install the required dependencies:
|
|||||||
libexpat1-dev
|
libexpat1-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, users can chose one of the automated scripts
|
Alternatively, users can try using the automated script under `contrib`
|
||||||
under `contrib` folder by executing:
|
folder by executing:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
% bash contrib/*_setup.sh
|
% sh contrib/dependency_setup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
The following setup script are provided:
|
The script will try to recognize which system you are running,
|
||||||
* **mac_setup.sh**: installation using brew (brew will be installed if not present).
|
and install dependencies accordingly. In case it does not find your
|
||||||
* **void_setup.sh**: Xbps dependencies for Void Linux.
|
package manager, please consider adding support for it into the script
|
||||||
|
and sending a patch.
|
||||||
|
|
||||||
To build the necessary binaries, we can just clone the repo, and use the
|
To build the necessary binaries, we can just clone the repo, and use
|
||||||
provided Makefile to build the project. This will download the trusted
|
the provided Makefile to build the project. This will download the
|
||||||
setup params, and compile the source code.
|
trusted setup params, and compile the source code.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
% git clone https://github.com/darkrenaissance/darkfi
|
% git clone https://github.com/darkrenaissance/darkfi
|
||||||
|
|||||||
251
bin/dao/daod/demo-spec.md
Normal file
251
bin/dao/daod/demo-spec.md
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
---
|
||||||
|
title: DAO demo architecture
|
||||||
|
author: jstark
|
||||||
|
---
|
||||||
|
|
||||||
|
This document outlines a simple demo to showcase the smart contract
|
||||||
|
schema underlying the initial DAO MVP. We have tried to mimic the
|
||||||
|
basic DarkFi architecture while remaining as simple as possible.
|
||||||
|
|
||||||
|
We do not have a blockchain, p2p network, or encrypted wallet database
|
||||||
|
in this highly simplified demo. It is just a local network of 4 nodes
|
||||||
|
and a relayer. The values are all stored in memory.
|
||||||
|
|
||||||
|
# Layers
|
||||||
|
|
||||||
|
**bin**
|
||||||
|
Located in darkfi/bin/dao.
|
||||||
|
|
||||||
|
* **daod/**: receives rpc requests and operates a `client`.
|
||||||
|
* **dao-cli/**: command-line interface that receives input and sends rpc requests.
|
||||||
|
* **relayerd/**: receives transactions on TCP and relays them to all nodes.
|
||||||
|
|
||||||
|
**src**
|
||||||
|
Located in darkfi/bin/dao/daod.
|
||||||
|
|
||||||
|
* **contract/**: source code for dao and money contracts.
|
||||||
|
* **util/**: demo-wide utilities.
|
||||||
|
* **state/**: stores dao and money states.
|
||||||
|
* **tx**: underlying types required by transactions, function calls and call data.
|
||||||
|
* **node/**: a dao full node.
|
||||||
|
|
||||||
|
**node**
|
||||||
|
A dao node containing `client` and `wallet` submodules.
|
||||||
|
|
||||||
|
* **client/**: operates a wallet and performs state transition and validate methods.
|
||||||
|
* **wallet/**: owns and operates secret values.
|
||||||
|
|
||||||
|
**proof**
|
||||||
|
Located in darkfi/bin/dao/proof. Contains the zk proofs.
|
||||||
|
|
||||||
|
# Command Flow
|
||||||
|
|
||||||
|
The following assumes that a user has already compiled the zk contracts
|
||||||
|
by running `make`.
|
||||||
|
|
||||||
|
This requires a `Makescript` as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
ZK_SRC_FILES := $(wildcard proof/*.zk)
|
||||||
|
ZK_BIN_FILES := $(patsubst proof/%.zk, proof/%.zk.bin, $(ZK_SRC_FILES))
|
||||||
|
|
||||||
|
daod: $(ZK_BIN_FILES)
|
||||||
|
cargo run --release
|
||||||
|
|
||||||
|
proof/%.zk.bin: proof/%.zk
|
||||||
|
zkas $<
|
||||||
|
```
|
||||||
|
|
||||||
|
We will also need to write a shell script that opens 9 terminals and
|
||||||
|
runs the following:
|
||||||
|
|
||||||
|
*Terminal 1:* relayerd.
|
||||||
|
*Terminal 2-5:* 4 instances of daod.
|
||||||
|
*Terminal 6-9:* 4 instances of dao-cli.
|
||||||
|
|
||||||
|
Relayerd and daod should be sent to the background, so the demo will
|
||||||
|
consist visually of 4 terminals running dao-cli.
|
||||||
|
|
||||||
|
## Start relayer
|
||||||
|
|
||||||
|
1. `relayerd` starts a listener for all TCP ports specified in config.
|
||||||
|
|
||||||
|
## Initialize DAO
|
||||||
|
|
||||||
|
Note: this happens automatically on daod first run.
|
||||||
|
|
||||||
|
1. `daod`: starts a listener on the relayer TCP port.
|
||||||
|
2. `daod:` creates a client and calls `client.init()`.
|
||||||
|
3. `client`: creates a money wallet.
|
||||||
|
4. `money-wallet`: generates cashier, faucet keys.
|
||||||
|
5. `client`: gets public keys from wallet and calls `state.new()`.
|
||||||
|
6. `state`: creates ZkContractTable, StateRegistry.
|
||||||
|
7. `state`: loads all zk binaries and saves them in ZkContractTable.
|
||||||
|
8. `state`: creates a new money/dao state and registers them in StateRegistry.
|
||||||
|
|
||||||
|
## Stage 1: create DAO
|
||||||
|
|
||||||
|
1. `dao-cli:` sends `create()` rpc request to daod.
|
||||||
|
2. `daod`: receives rpc request and calls `client.create()`.
|
||||||
|
3. `client`: creates a dao wallet.
|
||||||
|
4. `dao-wallet`: specifies the dao params.
|
||||||
|
5. `dao-wallet`: creates a dao keypair, bulla blind, and signature secret.
|
||||||
|
|
||||||
|
**build sequence.**
|
||||||
|
|
||||||
|
Note: Builders differ according to the FuncCall, but the basic sequence
|
||||||
|
is the same.
|
||||||
|
|
||||||
|
6. `dao-wallet`: build: creates dao_contract::mint::wallet::Builder.
|
||||||
|
7. `dao-wallet`: generates a FuncCall from builder.build().
|
||||||
|
8. `dao-wallet`: adds FuncCall to a vector.
|
||||||
|
9. `dao-wallet`: sign the vector of FuncCalls.
|
||||||
|
|
||||||
|
**send sequence.**
|
||||||
|
|
||||||
|
10. `dao-wallet`: create a Transaction.
|
||||||
|
11. `dao-wallet`: send the Transaction to the relayer.
|
||||||
|
12. `relayer`: receives a Transaction on one of its connections.
|
||||||
|
13. `relayer`: relays the Transaction to all connected nodes.
|
||||||
|
|
||||||
|
**recv sequence.**
|
||||||
|
|
||||||
|
14. `daod`: receives a Transaction on its relayerd listener.
|
||||||
|
15. 'daod`: sends the Transaction to Client.
|
||||||
|
|
||||||
|
**validate sequence.**
|
||||||
|
|
||||||
|
16. `client`: validate: creates an empty vector of updates.
|
||||||
|
16. `client`: loops through all FuncCalls in the Transaction.
|
||||||
|
17. `client`: runs a match statement on the FUNC_ID.
|
||||||
|
18. `client`: finds mint FUNC_ID and runs a state transition function.
|
||||||
|
20. `client`: pushes the result to Vec<Update>
|
||||||
|
21. `client`: outside the loop, atomically applies all updates.
|
||||||
|
22. `client`: calls zk_verify() on the Transaction.
|
||||||
|
23. `client`: verifies signatures.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
24. `client`: sends Transaction to the relayer.
|
||||||
|
25. `relayer`: receives Transaction and relays.
|
||||||
|
* TODO: `dao-wallet`: waits until Transction is confirmed. (how?)
|
||||||
|
27. `dao-wallet`: look up the dao state and call witness().
|
||||||
|
28. `dao-wallet`: get the dao bulla from the Transaction.
|
||||||
|
29. `dao-cli`: print "Created DAO {}".
|
||||||
|
|
||||||
|
## Stage 2: fund DAO
|
||||||
|
|
||||||
|
* TODO: for the demo it might be better to call mint() first and then
|
||||||
|
fund(), passing the values into fund()
|
||||||
|
|
||||||
|
Here we are creating a treasury token and sending it to the DAO.
|
||||||
|
|
||||||
|
1. `dao-cli:` `fund()` rpc request to daod
|
||||||
|
2. `daod`: receives rpc request and calls `client.fund()`.
|
||||||
|
3. `client`: creates treasury token, random token ID and supply
|
||||||
|
|
||||||
|
Note: dao-wallet must manually track coins to retrieve coins belonging
|
||||||
|
to its private key.
|
||||||
|
|
||||||
|
4. `dao-wallet`: looks up the money state, and calls state.wallet_cache.track()
|
||||||
|
|
||||||
|
5. `money-wallet`: sets spend hook to dao_contract::exec::FUNC_ID
|
||||||
|
5. `money-wallet`: sets user_data to dao_bulla
|
||||||
|
|
||||||
|
* TODO: how does it get the dao_bulla? Must be stored somewhere.
|
||||||
|
|
||||||
|
6. `money-wallet`: specifies dao public key and treasury token BuilderOutputInfo.
|
||||||
|
5. `money-wallet`: runs the build sequence for money::transfer.
|
||||||
|
9. `money-wallet`: create Transaction and send.
|
||||||
|
10. `relayer`: receives Transaction and relays.
|
||||||
|
11. `daod`: receives a Transaction and sends to client.
|
||||||
|
12. `client`: runs the validate sequence.
|
||||||
|
|
||||||
|
Note: here we get all coins associated with the private key.
|
||||||
|
13. `dao-wallet`: looks up the state and calls WalletCache.get_received()
|
||||||
|
14. `dao-wallet`: check the coin is valid by recreating Coin
|
||||||
|
15. `daod`: sendswith token ID and balance to dao-cli.
|
||||||
|
16. `dao-cli`: displays data using pretty table.
|
||||||
|
|
||||||
|
## Stage 3: airdrop
|
||||||
|
|
||||||
|
1. `dao-cli`: calls keygen()
|
||||||
|
2. `daod`: client.keygen()
|
||||||
|
3. `daod`: money-wallet.keygen()
|
||||||
|
4. `money-wallet`: creates new keypair
|
||||||
|
5. `money-wallet`: looks up the money_contract State and calls WalletCache.track()
|
||||||
|
6. `money-wallet`: return the public key
|
||||||
|
7. `dao-cli`: prints the public key
|
||||||
|
|
||||||
|
Note: do this 3 times to generate 3 pubkey keys for different daod instances.
|
||||||
|
|
||||||
|
8. `dao-cli`: calls mint()
|
||||||
|
9. `daod`: call client.mint()
|
||||||
|
10. `client:` creates governance token with random token ID and supply
|
||||||
|
11. `dao-cli`: prints "created token {} with supply {}"
|
||||||
|
12. `dao-cli`: calls airdrop() and passes a value and a pubkey.
|
||||||
|
13. `dao-wallet:` runs the build sequence for money::transfer.
|
||||||
|
14. `dao-wallet`: create Transaction and send.
|
||||||
|
15. `relayer`: receives Transaction and relays.
|
||||||
|
16. `daod`: receives a Transaction and sends to client.
|
||||||
|
17. `client`: runs the validate sequence.
|
||||||
|
18. `money-wallet`: state.wallet_cache.get_received()
|
||||||
|
19. `money-wallet`: check the coin is valid by recreating Coin
|
||||||
|
20. `daod`: sends token ID and balance to cli
|
||||||
|
21. `dao-cli`: prints "received coin {} with value {}".
|
||||||
|
|
||||||
|
* TODO: money-wallet must keep track of Coins and have a flag for whether or not they are spent.
|
||||||
|
* Hashmap of <Coin, bool> ?
|
||||||
|
|
||||||
|
## Stage 4: create proposal
|
||||||
|
|
||||||
|
* TODO: maybe for the demo we should just hardcode a user/ proposal recipient.
|
||||||
|
|
||||||
|
1. `dao-cli`: calls propose() and enter a user pubkey and an amount
|
||||||
|
3. `dao-wallet`: runs the build sequence for dao_contract::propose
|
||||||
|
4. `dao-wallet`: specifies user pubkey, amount and token ID in Proposal
|
||||||
|
5. `dao-cli`: prints "Created proposal to send {} xDRK to {}"
|
||||||
|
6. `dao-wallet`: create Transaction and send.
|
||||||
|
7. `relayer`: receives Transaction and relays.
|
||||||
|
8. `daod`: receives a Transaction and sends to client.
|
||||||
|
9. `client`: runs the validate sequence.
|
||||||
|
* TODO: how does everyone have access to DAO private key?
|
||||||
|
10. `dao-wallet`: reads received proposal and tries to decrypt Note
|
||||||
|
11. `dao-wallet`: sends decrypted values to daod
|
||||||
|
12. `dao-cli`: prints "Proposal is now active"
|
||||||
|
|
||||||
|
## Stage 5 vote
|
||||||
|
|
||||||
|
1. `dao-cli`: calls vote() and enters a vote option (yes or no) and an amount
|
||||||
|
2. `daod`: calls client.vote()
|
||||||
|
3. `money-wallet`: get money_leaf_position and money_merkle_path
|
||||||
|
4. `money-wallet`: create builder sequence for dao_contract::vote
|
||||||
|
5. `money-wallet`: specify dao_keypair in vote_keypair field
|
||||||
|
* TODO: this implies that money-wallet is able to access private values in dao-wallet
|
||||||
|
6. `money-wallet`: signs and sends
|
||||||
|
7. `relayer`: receives Transaction and relays.
|
||||||
|
8. `daod`: receives a Transaction and sends to client.
|
||||||
|
9. `client`: runs the validate sequence.
|
||||||
|
10. `dao-wallet`: tries to decrypt the Vote.
|
||||||
|
11. `dao-cli`: prints "Received vote {} value {}"
|
||||||
|
|
||||||
|
Note: repeat 3 times with different values and vote options.
|
||||||
|
|
||||||
|
* TODO: ignore section re: vote commitments?
|
||||||
|
* TODO: determine outcome: yes_votes_value/ all_votes_value
|
||||||
|
e.g. when the quorum is reached, print "Quorum reached! Outcome {}"
|
||||||
|
or just hardcode it for X n. of voters
|
||||||
|
|
||||||
|
## Stage 6: Executing the proposal
|
||||||
|
|
||||||
|
1. `dao-cli`: calls exec()
|
||||||
|
* TODO: how does dao have access to user data?
|
||||||
|
2. `dao-wallet`: get money_leaf_position and money_merkle_path
|
||||||
|
3. `dao-wallet`: specifies user_kaypair and proposal amount in 1st output
|
||||||
|
4. `dao-wallet`: specifies change in 2nd output
|
||||||
|
5. `dao-wallet`: run build sequence for money_contract::transfer
|
||||||
|
6. `dao-wallet`: run build sequence for dao_contract::exec
|
||||||
|
7. `dao-wallet`: signs transaction and sends
|
||||||
|
8. `relayer`: receives Transaction and relays.
|
||||||
|
9. `daod`: receives a Transaction and sends to client.
|
||||||
|
10. `client`: runs the validate sequence.
|
||||||
@@ -21,6 +21,7 @@ pub struct OwnCoin {
|
|||||||
|
|
||||||
pub struct WalletCache {
|
pub struct WalletCache {
|
||||||
// Normally this would be a HashMap, but SecretKey is not Hash-able
|
// Normally this would be a HashMap, but SecretKey is not Hash-able
|
||||||
|
// TODO: This can be HashableBase
|
||||||
cache: Vec<(SecretKey, Vec<OwnCoin>)>,
|
cache: Vec<(SecretKey, Vec<OwnCoin>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -433,6 +433,7 @@ pub async fn demo() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tx.zk_verify(&zk_bins);
|
tx.zk_verify(&zk_bins);
|
||||||
|
tx.verify_sigs();
|
||||||
|
|
||||||
// Wallet stuff
|
// Wallet stuff
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ pub enum RpcError {
|
|||||||
InvalidKeypair = -32106,
|
InvalidKeypair = -32106,
|
||||||
UnknownSlot = -32107,
|
UnknownSlot = -32107,
|
||||||
TxBuildFail = -32108,
|
TxBuildFail = -32108,
|
||||||
NetworkNameError = -32109,
|
// NetworkNameError = -32109,
|
||||||
ParseError = -32110,
|
ParseError = -32110,
|
||||||
TxBroadcastFail = -32111,
|
TxBroadcastFail = -32111,
|
||||||
NotYetSynced = -32112,
|
NotYetSynced = -32112,
|
||||||
InvalidAddressParam = -32113,
|
InvalidAddressParam = -32113,
|
||||||
InvalidAmountParam = -32114,
|
// InvalidAmountParam = -32114,
|
||||||
DecryptionFailed = -32115,
|
DecryptionFailed = -32115,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,12 +30,12 @@ fn to_tuple(e: RpcError) -> (i64, String) {
|
|||||||
RpcError::InvalidKeypair => "Invalid keypair",
|
RpcError::InvalidKeypair => "Invalid keypair",
|
||||||
RpcError::UnknownSlot => "Did not find slot",
|
RpcError::UnknownSlot => "Did not find slot",
|
||||||
RpcError::TxBuildFail => "Failed building transaction",
|
RpcError::TxBuildFail => "Failed building transaction",
|
||||||
RpcError::NetworkNameError => "Unknown network name",
|
// RpcError::NetworkNameError => "Unknown network name",
|
||||||
RpcError::ParseError => "Parse error",
|
RpcError::ParseError => "Parse error",
|
||||||
RpcError::TxBroadcastFail => "Failed broadcasting transaction",
|
RpcError::TxBroadcastFail => "Failed broadcasting transaction",
|
||||||
RpcError::NotYetSynced => "Blockchain not yet synced",
|
RpcError::NotYetSynced => "Blockchain not yet synced",
|
||||||
RpcError::InvalidAddressParam => "Invalid address parameter",
|
RpcError::InvalidAddressParam => "Invalid address parameter",
|
||||||
RpcError::InvalidAmountParam => "invalid amount parameter",
|
// RpcError::InvalidAmountParam => "invalid amount parameter",
|
||||||
RpcError::DecryptionFailed => "Decryption failed",
|
RpcError::DecryptionFailed => "Decryption failed",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -50,8 +50,9 @@ enum ArgsSubCommand {
|
|||||||
fn print_patches(value: &Vec<serde_json::Value>) {
|
fn print_patches(value: &Vec<serde_json::Value>) {
|
||||||
for res in value {
|
for res in value {
|
||||||
let res = res.as_array().unwrap();
|
let res = res.as_array().unwrap();
|
||||||
let (title, changes) = (res[0].as_str().unwrap(), res[1].as_str().unwrap());
|
let res: Vec<&str> = res.iter().map(|r| r.as_str().unwrap()).collect();
|
||||||
println!("FILE: {}", title);
|
let (title, workspace, changes) = (res[0], res[1], res[2]);
|
||||||
|
println!("WORKSPACE: {} FILE: {}", workspace, title);
|
||||||
println!("{}", changes);
|
println!("{}", changes);
|
||||||
println!("----------------------------------");
|
println!("----------------------------------");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
## Sets Author name
|
## Sets Author name
|
||||||
# author="name"
|
# author="name"
|
||||||
|
|
||||||
## Secret Key To Encrypt/Decrypt Patches
|
## Workspaces
|
||||||
#secret = "86MGNN31r3VxT4ULMmhQnMtV8pDnod339KwHwHCfabG2"
|
# workspaces = ["darkfi:86MGNN31r3VxT4ULMmhQnMtV8pDnod339KwHwHCfabG2"]
|
||||||
|
|
||||||
## Raft net settings
|
## Raft net settings
|
||||||
[net]
|
[net]
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ use darkfi::{
|
|||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::Patch;
|
||||||
|
|
||||||
pub struct JsonRpcInterface {
|
pub struct JsonRpcInterface {
|
||||||
sender: async_channel::Sender<(String, bool, Vec<String>)>,
|
sender: async_channel::Sender<(String, bool, Vec<String>)>,
|
||||||
receiver: async_channel::Receiver<Vec<Vec<(String, String)>>>,
|
receiver: async_channel::Receiver<Vec<Vec<Patch>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -36,10 +38,25 @@ impl RequestHandler for JsonRpcInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn patch_to_tuple(p: &Patch, colorize: bool) -> (String, String, String) {
|
||||||
|
(p.path.to_owned(), p.workspace.to_owned(), if colorize { p.colorize() } else { p.to_string() })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printable_patches(
|
||||||
|
patches: Vec<Vec<Patch>>,
|
||||||
|
colorize: bool,
|
||||||
|
) -> Vec<Vec<(String, String, String)>> {
|
||||||
|
let mut response = vec![];
|
||||||
|
for ps in patches {
|
||||||
|
response.push(ps.iter().map(|p| patch_to_tuple(p, colorize)).collect())
|
||||||
|
}
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
impl JsonRpcInterface {
|
impl JsonRpcInterface {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sender: async_channel::Sender<(String, bool, Vec<String>)>,
|
sender: async_channel::Sender<(String, bool, Vec<String>)>,
|
||||||
receiver: async_channel::Receiver<Vec<Vec<(String, String)>>>,
|
receiver: async_channel::Receiver<Vec<Vec<Patch>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { sender, receiver }
|
Self { sender, receiver }
|
||||||
}
|
}
|
||||||
@@ -60,6 +77,7 @@ impl JsonRpcInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let response = self.receiver.recv().await.unwrap();
|
let response = self.receiver.recv().await.unwrap();
|
||||||
|
let response = printable_patches(response, true);
|
||||||
JsonResponse::new(json!(response), id).into()
|
JsonResponse::new(json!(response), id).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +98,7 @@ impl JsonRpcInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let response = self.receiver.recv().await.unwrap();
|
let response = self.receiver.recv().await.unwrap();
|
||||||
|
let response = printable_patches(response, false);
|
||||||
JsonResponse::new(json!(response), id).into()
|
JsonResponse::new(json!(response), id).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,11 +65,11 @@ pub struct Args {
|
|||||||
#[structopt(long, default_value = "NONE")]
|
#[structopt(long, default_value = "NONE")]
|
||||||
pub author: String,
|
pub author: String,
|
||||||
/// Secret Key To Encrypt/Decrypt Patches
|
/// Secret Key To Encrypt/Decrypt Patches
|
||||||
#[structopt(long, default_value = "")]
|
#[structopt(long)]
|
||||||
pub secret: String,
|
pub workspaces: Vec<String>,
|
||||||
/// Generate A New Secret Key
|
/// Generate A New Secret Key
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
pub keygen: bool,
|
pub generate: bool,
|
||||||
/// Clean all the local data in docs path
|
/// Clean all the local data in docs path
|
||||||
/// (BE CAREFULL) Check the docs path in the config file before running this
|
/// (BE CAREFULL) Check the docs path in the config file before running this
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
@@ -90,6 +90,28 @@ pub struct EncryptedPatch {
|
|||||||
payload: Vec<u8>,
|
payload: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_workspaces(settings: &Args, docs_path: &Path) -> Result<FxHashMap<String, SalsaBox>> {
|
||||||
|
let mut workspaces = FxHashMap::default();
|
||||||
|
|
||||||
|
for workspace in settings.workspaces.iter() {
|
||||||
|
let workspace: Vec<&str> = workspace.split(':').collect();
|
||||||
|
let (workspace, secret) = (workspace[0], workspace[1]);
|
||||||
|
|
||||||
|
let bytes: [u8; 32] = bs58::decode(secret)
|
||||||
|
.into_vec()?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::ParseFailed("Parse secret key failed"))?;
|
||||||
|
|
||||||
|
let secret = crypto_box::SecretKey::from(bytes);
|
||||||
|
let public = secret.public_key();
|
||||||
|
let salsa_box = crypto_box::SalsaBox::new(&public, &secret);
|
||||||
|
workspaces.insert(workspace.to_string(), salsa_box);
|
||||||
|
create_dir_all(docs_path.join(workspace))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(workspaces)
|
||||||
|
}
|
||||||
|
|
||||||
fn encrypt_patch(
|
fn encrypt_patch(
|
||||||
patch: &Patch,
|
patch: &Patch,
|
||||||
salsa_box: &SalsaBox,
|
salsa_box: &SalsaBox,
|
||||||
@@ -126,9 +148,9 @@ fn str_to_chars(s: &str) -> Vec<&str> {
|
|||||||
s.graphemes(true).collect::<Vec<&str>>()
|
s.graphemes(true).collect::<Vec<&str>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_to_id(path: &str) -> String {
|
fn path_to_id(path: &str, workspace: &str) -> String {
|
||||||
let mut hasher = sha2::Sha256::new();
|
let mut hasher = sha2::Sha256::new();
|
||||||
hasher.update(path);
|
hasher.update(&format!("{}{}", path, workspace));
|
||||||
bs58::encode(hex::encode(hasher.finalize())).into_string()
|
bs58::encode(hex::encode(hasher.finalize())).into_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,11 +199,11 @@ struct Darkwiki {
|
|||||||
settings: DarkWikiSettings,
|
settings: DarkWikiSettings,
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
rpc: (
|
rpc: (
|
||||||
async_channel::Sender<Vec<Vec<(String, String)>>>,
|
async_channel::Sender<Vec<Vec<Patch>>>,
|
||||||
async_channel::Receiver<(String, bool, Vec<String>)>,
|
async_channel::Receiver<(String, bool, Vec<String>)>,
|
||||||
),
|
),
|
||||||
raft: (async_channel::Sender<EncryptedPatch>, async_channel::Receiver<EncryptedPatch>),
|
raft: (async_channel::Sender<EncryptedPatch>, async_channel::Receiver<EncryptedPatch>),
|
||||||
salsa_box: SalsaBox,
|
workspaces: FxHashMap<String, SalsaBox>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Darkwiki {
|
impl Darkwiki {
|
||||||
@@ -202,17 +224,19 @@ impl Darkwiki {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
patch = self.raft.1.recv().fuse() => {
|
patch = self.raft.1.recv().fuse() => {
|
||||||
self.on_receive_patch(&patch?)?;
|
for (workspace, salsa_box) in self.workspaces.iter() {
|
||||||
|
if let Ok(patch) = decrypt_patch(&patch.clone()?, &salsa_box) {
|
||||||
|
info!("[{}] Receive a {:?}", workspace, patch);
|
||||||
|
self.on_receive_patch(&patch)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_receive_patch(&self, received_patch: &EncryptedPatch) -> Result<()> {
|
fn on_receive_patch(&self, received_patch: &Patch) -> Result<()> {
|
||||||
let received_patch = decrypt_patch(received_patch, &self.salsa_box)?;
|
|
||||||
|
|
||||||
info!("Receive a {:?}", received_patch);
|
|
||||||
let sync_id_path = self.settings.datastore_path.join("sync").join(&received_patch.id);
|
let sync_id_path = self.settings.datastore_path.join("sync").join(&received_patch.id);
|
||||||
let local_id_path = self.settings.datastore_path.join("local").join(&received_patch.id);
|
let local_id_path = self.settings.datastore_path.join("local").join(&received_patch.id);
|
||||||
|
|
||||||
@@ -233,7 +257,7 @@ impl Darkwiki {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sync_patch.timestamp = received_patch.timestamp;
|
sync_patch.timestamp = received_patch.timestamp;
|
||||||
sync_patch.author = received_patch.author;
|
sync_patch.author = received_patch.author.clone();
|
||||||
save_json_file::<Patch>(&sync_id_path, &sync_patch)?;
|
save_json_file::<Patch>(&sync_id_path, &sync_patch)?;
|
||||||
} else if !received_patch.base.is_empty() {
|
} else if !received_patch.base.is_empty() {
|
||||||
save_json_file::<Patch>(&sync_id_path, &received_patch)?;
|
save_json_file::<Patch>(&sync_id_path, &received_patch)?;
|
||||||
@@ -248,43 +272,62 @@ impl Darkwiki {
|
|||||||
files: Vec<String>,
|
files: Vec<String>,
|
||||||
rng: &mut OsRng,
|
rng: &mut OsRng,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (patches, local, sync, merge) = self.update(dry, files)?;
|
let mut local: Vec<Patch> = vec![];
|
||||||
|
let mut sync: Vec<Patch> = vec![];
|
||||||
|
let mut merge: Vec<Patch> = vec![];
|
||||||
|
|
||||||
if !dry {
|
for (workspace, salsa_box) in self.workspaces.iter() {
|
||||||
for patch in patches {
|
let (patches, l, s, m) = self.update(
|
||||||
info!("Send a {:?}", patch);
|
dry,
|
||||||
let encrypt_patch = encrypt_patch(&patch, &self.salsa_box, rng)?;
|
&self.settings.docs_path.join(workspace),
|
||||||
self.raft.0.send(encrypt_patch).await?;
|
files.clone(),
|
||||||
|
workspace,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
local.extend(l);
|
||||||
|
sync.extend(s);
|
||||||
|
merge.extend(m);
|
||||||
|
|
||||||
|
if !dry {
|
||||||
|
for patch in patches {
|
||||||
|
info!("Send a {:?}", patch);
|
||||||
|
let encrypt_patch = encrypt_patch(&patch, &salsa_box, rng)?;
|
||||||
|
self.raft.0.send(encrypt_patch).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let local: Vec<(String, String)> =
|
|
||||||
local.iter().map(|p| (p.path.to_owned(), p.colorize())).collect();
|
|
||||||
|
|
||||||
let sync: Vec<(String, String)> =
|
|
||||||
sync.iter().map(|p| (p.path.to_owned(), p.colorize())).collect();
|
|
||||||
|
|
||||||
let merge: Vec<(String, String)> =
|
|
||||||
merge.iter().map(|p| (p.path.to_owned(), p.colorize())).collect();
|
|
||||||
|
|
||||||
self.rpc.0.send(vec![local, sync, merge]).await?;
|
self.rpc.0.send(vec![local, sync, merge]).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_receive_restore(&self, dry: bool, files_name: Vec<String>) -> Result<()> {
|
async fn on_receive_restore(&self, dry: bool, files_name: Vec<String>) -> Result<()> {
|
||||||
let patches = self.restore(dry, files_name)?;
|
let mut patches: Vec<Patch> = vec![];
|
||||||
let patches: Vec<(String, String)> =
|
|
||||||
patches.iter().map(|p| (p.path.to_owned(), p.to_string())).collect();
|
for (workspace, _) in self.workspaces.iter() {
|
||||||
|
let ps = self.restore(
|
||||||
|
dry,
|
||||||
|
&self.settings.docs_path.join(workspace),
|
||||||
|
&files_name,
|
||||||
|
workspace,
|
||||||
|
)?;
|
||||||
|
patches.extend(ps);
|
||||||
|
}
|
||||||
|
|
||||||
self.rpc.0.send(vec![patches]).await?;
|
self.rpc.0.send(vec![patches]).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore(&self, dry: bool, files_name: Vec<String>) -> Result<Vec<Patch>> {
|
fn restore(
|
||||||
|
&self,
|
||||||
|
dry: bool,
|
||||||
|
docs_path: &Path,
|
||||||
|
files_name: &[String],
|
||||||
|
workspace: &str,
|
||||||
|
) -> Result<Vec<Patch>> {
|
||||||
let local_path = self.settings.datastore_path.join("local");
|
let local_path = self.settings.datastore_path.join("local");
|
||||||
let docs_path = self.settings.docs_path.clone();
|
|
||||||
|
|
||||||
let mut patches = vec![];
|
let mut patches = vec![];
|
||||||
|
|
||||||
@@ -294,6 +337,10 @@ impl Darkwiki {
|
|||||||
let file_path = local_path.join(&file_id);
|
let file_path = local_path.join(&file_id);
|
||||||
let local_patch: Patch = load_json_file(&file_path)?;
|
let local_patch: Patch = load_json_file(&file_path)?;
|
||||||
|
|
||||||
|
if local_patch.workspace != workspace {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if !files_name.is_empty() && !files_name.contains(&local_patch.path.to_string()) {
|
if !files_name.is_empty() && !files_name.contains(&local_patch.path.to_string()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -305,7 +352,7 @@ impl Darkwiki {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !dry {
|
if !dry {
|
||||||
self.save_doc(&local_patch.path, &local_patch.to_string())?;
|
self.save_doc(&local_patch.path, &local_patch.to_string(), workspace)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
patches.push(local_patch);
|
patches.push(local_patch);
|
||||||
@@ -314,7 +361,13 @@ impl Darkwiki {
|
|||||||
Ok(patches)
|
Ok(patches)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self, dry: bool, files_name: Vec<String>) -> Result<Patches> {
|
fn update(
|
||||||
|
&self,
|
||||||
|
dry: bool,
|
||||||
|
docs_path: &Path,
|
||||||
|
files_name: Vec<String>,
|
||||||
|
workspace: &str,
|
||||||
|
) -> Result<Patches> {
|
||||||
let mut patches: Vec<Patch> = vec![];
|
let mut patches: Vec<Patch> = vec![];
|
||||||
let mut local_patches: Vec<Patch> = vec![];
|
let mut local_patches: Vec<Patch> = vec![];
|
||||||
let mut sync_patches: Vec<Patch> = vec![];
|
let mut sync_patches: Vec<Patch> = vec![];
|
||||||
@@ -322,7 +375,6 @@ impl Darkwiki {
|
|||||||
|
|
||||||
let local_path = self.settings.datastore_path.join("local");
|
let local_path = self.settings.datastore_path.join("local");
|
||||||
let sync_path = self.settings.datastore_path.join("sync");
|
let sync_path = self.settings.datastore_path.join("sync");
|
||||||
let docs_path = self.settings.docs_path.clone();
|
|
||||||
|
|
||||||
// save and compare docs in darkwiki and local dirs
|
// save and compare docs in darkwiki and local dirs
|
||||||
// then merged with sync patches if any received
|
// then merged with sync patches if any received
|
||||||
@@ -342,10 +394,10 @@ impl Darkwiki {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let doc_id = path_to_id(doc_path);
|
let doc_id = path_to_id(doc_path, workspace);
|
||||||
|
|
||||||
// create new patch
|
// create new patch
|
||||||
let mut new_patch = Patch::new(doc_path, &doc_id, &self.settings.author);
|
let mut new_patch = Patch::new(doc_path, &doc_id, &self.settings.author, workspace);
|
||||||
|
|
||||||
// check for any changes found with local doc and darkwiki doc
|
// check for any changes found with local doc and darkwiki doc
|
||||||
if let Ok(local_patch) = load_json_file::<Patch>(&local_path.join(&doc_id)) {
|
if let Ok(local_patch) = load_json_file::<Patch>(&local_path.join(&doc_id)) {
|
||||||
@@ -381,7 +433,7 @@ impl Darkwiki {
|
|||||||
let sync_patch_t = new_patch.transform(&sync_patch);
|
let sync_patch_t = new_patch.transform(&sync_patch);
|
||||||
new_patch = new_patch.merge(&sync_patch_t);
|
new_patch = new_patch.merge(&sync_patch_t);
|
||||||
if !dry {
|
if !dry {
|
||||||
self.save_doc(doc_path, &new_patch.to_string())?;
|
self.save_doc(doc_path, &new_patch.to_string(), workspace)?;
|
||||||
}
|
}
|
||||||
merge_patches.push(new_patch.clone());
|
merge_patches.push(new_patch.clone());
|
||||||
}
|
}
|
||||||
@@ -410,6 +462,10 @@ impl Darkwiki {
|
|||||||
let file_path = sync_path.join(&file_id);
|
let file_path = sync_path.join(&file_id);
|
||||||
let sync_patch: Patch = load_json_file(&file_path)?;
|
let sync_patch: Patch = load_json_file(&file_path)?;
|
||||||
|
|
||||||
|
if sync_patch.workspace != workspace {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if is_delete_patch(&sync_patch) {
|
if is_delete_patch(&sync_patch) {
|
||||||
if local_path.join(&sync_patch.id).exists() {
|
if local_path.join(&sync_patch.id).exists() {
|
||||||
sync_patches.push(sync_patch.clone());
|
sync_patches.push(sync_patch.clone());
|
||||||
@@ -435,7 +491,7 @@ impl Darkwiki {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !dry {
|
if !dry {
|
||||||
self.save_doc(&sync_patch.path, &sync_patch.to_string())?;
|
self.save_doc(&sync_patch.path, &sync_patch.to_string(), workspace)?;
|
||||||
save_json_file(&local_path.join(file_id), &sync_patch)?;
|
save_json_file(&local_path.join(file_id), &sync_patch)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,13 +507,21 @@ impl Darkwiki {
|
|||||||
let file_path = local_path.join(&file_id);
|
let file_path = local_path.join(&file_id);
|
||||||
let local_patch: Patch = load_json_file(&file_path)?;
|
let local_patch: Patch = load_json_file(&file_path)?;
|
||||||
|
|
||||||
|
if local_patch.workspace != workspace {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if !files_name.is_empty() && !files_name.contains(&local_patch.path.to_string()) {
|
if !files_name.is_empty() && !files_name.contains(&local_patch.path.to_string()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !docs_path.join(&local_patch.path).exists() {
|
if !docs_path.join(&local_patch.path).exists() {
|
||||||
let mut new_patch =
|
let mut new_patch = Patch::new(
|
||||||
Patch::new(&local_patch.path, &local_patch.id, &self.settings.author);
|
&local_patch.path,
|
||||||
|
&local_patch.id,
|
||||||
|
&self.settings.author,
|
||||||
|
&local_patch.workspace,
|
||||||
|
);
|
||||||
new_patch.add_op(&OpMethod::Delete(local_patch.to_string().len() as u64));
|
new_patch.add_op(&OpMethod::Delete(local_patch.to_string().len() as u64));
|
||||||
patches.push(new_patch.clone());
|
patches.push(new_patch.clone());
|
||||||
|
|
||||||
@@ -473,8 +537,8 @@ impl Darkwiki {
|
|||||||
Ok((patches, local_patches, sync_patches, merge_patches))
|
Ok((patches, local_patches, sync_patches, merge_patches))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_doc(&self, path: &str, edit: &str) -> Result<()> {
|
fn save_doc(&self, path: &str, edit: &str, workspace: &str) -> Result<()> {
|
||||||
let path = self.settings.docs_path.join(path);
|
let path = self.settings.docs_path.join(workspace).join(path);
|
||||||
if let Some(p) = path.parent() {
|
if let Some(p) = path.parent() {
|
||||||
if !p.exists() && !p.to_str().unwrap().is_empty() {
|
if !p.exists() && !p.to_str().unwrap().is_empty() {
|
||||||
create_dir_all(p)?;
|
create_dir_all(p)?;
|
||||||
@@ -492,7 +556,7 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
|
|||||||
if settings.refresh {
|
if settings.refresh {
|
||||||
println!("Removing local docs in: {:?} (yes/no)? ", docs_path);
|
println!("Removing local docs in: {:?} (yes/no)? ", docs_path);
|
||||||
let mut confirm = String::new();
|
let mut confirm = String::new();
|
||||||
stdin().read_line(&mut confirm).ok().expect("Failed to read line");
|
stdin().read_line(&mut confirm).expect("Failed to read line");
|
||||||
|
|
||||||
let confirm = confirm.to_lowercase();
|
let confirm = confirm.to_lowercase();
|
||||||
let confirm = confirm.trim();
|
let confirm = confirm.trim();
|
||||||
@@ -512,25 +576,43 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
|
|||||||
create_dir_all(datastore_path.join("local"))?;
|
create_dir_all(datastore_path.join("local"))?;
|
||||||
create_dir_all(datastore_path.join("sync"))?;
|
create_dir_all(datastore_path.join("sync"))?;
|
||||||
|
|
||||||
if settings.keygen {
|
if settings.generate {
|
||||||
info!("Generating a new secret key");
|
println!("Generating a new workspace");
|
||||||
let mut rng = crypto_box::rand_core::OsRng;
|
|
||||||
let secret_key = SecretKey::generate(&mut rng);
|
loop {
|
||||||
let encoded = bs58::encode(secret_key.as_bytes());
|
println!("Name for the new workspace: ");
|
||||||
println!("Secret key: {}", encoded.into_string());
|
let mut workspace = String::new();
|
||||||
|
stdin().read_line(&mut workspace).ok().expect("Failed to read line");
|
||||||
|
let workspace = workspace.to_lowercase();
|
||||||
|
let workspace = workspace.trim();
|
||||||
|
if workspace.is_empty() && workspace.len() < 3 {
|
||||||
|
error!("Wrong workspace try again");
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let mut rng = crypto_box::rand_core::OsRng;
|
||||||
|
let secret_key = SecretKey::generate(&mut rng);
|
||||||
|
let encoded = bs58::encode(secret_key.as_bytes());
|
||||||
|
|
||||||
|
create_dir_all(docs_path.join(workspace))?;
|
||||||
|
|
||||||
|
println!("workspace: {}:{}", workspace, encoded.into_string());
|
||||||
|
println!("Please add it to the config file.");
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes: [u8; 32] = bs58::decode(settings.secret)
|
let workspaces = get_workspaces(&settings, &docs_path)?;
|
||||||
.into_vec()?
|
|
||||||
.try_into()
|
if workspaces.is_empty() {
|
||||||
.map_err(|_| Error::ParseFailed("Parse secret key failed"))?;
|
error!("Please add at least on workspace to the config file.");
|
||||||
let secret = crypto_box::SecretKey::from(bytes);
|
println!("Run `$ darkwikid --generate` to generate new workspace.");
|
||||||
let public = secret.public_key();
|
return Ok(())
|
||||||
let salsa_box = crypto_box::SalsaBox::new(&public, &secret);
|
}
|
||||||
|
|
||||||
let (rpc_sx, rpc_rv) = async_channel::unbounded::<(String, bool, Vec<String>)>();
|
let (rpc_sx, rpc_rv) = async_channel::unbounded::<(String, bool, Vec<String>)>();
|
||||||
let (notify_sx, notify_rv) = async_channel::unbounded::<Vec<Vec<(String, String)>>>();
|
let (notify_sx, notify_rv) = async_channel::unbounded::<Vec<Vec<Patch>>>();
|
||||||
|
|
||||||
//
|
//
|
||||||
// RPC
|
// RPC
|
||||||
@@ -575,8 +657,6 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
|
|||||||
|
|
||||||
executor.spawn(p2p.clone().run(executor.clone())).detach();
|
executor.spawn(p2p.clone().run(executor.clone())).detach();
|
||||||
|
|
||||||
p2p.clone().wait_for_outbound(executor.clone()).await?;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Darkwiki start
|
// Darkwiki start
|
||||||
//
|
//
|
||||||
@@ -590,7 +670,7 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
|
|||||||
settings: darkwiki_settings,
|
settings: darkwiki_settings,
|
||||||
raft: (raft_sx, raft_rv),
|
raft: (raft_sx, raft_rv),
|
||||||
rpc: (notify_sx, rpc_rv),
|
rpc: (notify_sx, rpc_rv),
|
||||||
salsa_box,
|
workspaces,
|
||||||
};
|
};
|
||||||
darkwiki.start().await.unwrap_or(());
|
darkwiki.start().await.unwrap_or(());
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ pub struct Patch {
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
pub base: String,
|
pub base: String,
|
||||||
pub timestamp: Timestamp,
|
pub timestamp: Timestamp,
|
||||||
|
pub workspace: String,
|
||||||
ops: OpMethods,
|
ops: OpMethods,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,12 +67,13 @@ impl std::string::ToString for Patch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Patch {
|
impl Patch {
|
||||||
pub fn new(path: &str, id: &str, author: &str) -> Self {
|
pub fn new(path: &str, id: &str, author: &str, workspace: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
ops: OpMethods(vec![]),
|
ops: OpMethods(vec![]),
|
||||||
base: String::new(),
|
base: String::new(),
|
||||||
|
workspace: workspace.to_string(),
|
||||||
author: author.to_string(),
|
author: author.to_string(),
|
||||||
timestamp: Timestamp::current_time(),
|
timestamp: Timestamp::current_time(),
|
||||||
}
|
}
|
||||||
@@ -146,7 +148,7 @@ impl Patch {
|
|||||||
//
|
//
|
||||||
// TODO need more work to get better performance with iterators
|
// TODO need more work to get better performance with iterators
|
||||||
pub fn transform(&self, other: &Self) -> Self {
|
pub fn transform(&self, other: &Self) -> Self {
|
||||||
let mut new_patch = Self::new(&self.path, &self.id, &self.author);
|
let mut new_patch = Self::new(&self.path, &self.id, &self.author, "");
|
||||||
new_patch.base = self.base.clone();
|
new_patch.base = self.base.clone();
|
||||||
|
|
||||||
let mut ops1 = self.ops.0.iter().cloned();
|
let mut ops1 = self.ops.0.iter().cloned();
|
||||||
@@ -253,7 +255,7 @@ impl Patch {
|
|||||||
let mut ops1 = ops1.iter().cloned();
|
let mut ops1 = ops1.iter().cloned();
|
||||||
let mut ops2 = other.ops.0.iter().cloned();
|
let mut ops2 = other.ops.0.iter().cloned();
|
||||||
|
|
||||||
let mut new_patch = Self::new(&self.path, &self.id, &self.author);
|
let mut new_patch = Self::new(&self.path, &self.id, &self.author, "");
|
||||||
new_patch.base = self.base.clone();
|
new_patch.base = self.base.clone();
|
||||||
|
|
||||||
let mut op1 = ops1.next();
|
let mut op1 = ops1.next();
|
||||||
@@ -468,7 +470,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_to_string() {
|
fn test_to_string() {
|
||||||
let mut patch = Patch::new("", &gen_id(30), "");
|
let mut patch = Patch::new("", &gen_id(30), "", "");
|
||||||
patch.base = "text example\n hello".to_string();
|
patch.base = "text example\n hello".to_string();
|
||||||
patch.retain(14);
|
patch.retain(14);
|
||||||
patch.delete(5);
|
patch.delete(5);
|
||||||
@@ -479,7 +481,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge() {
|
fn test_merge() {
|
||||||
let mut patch_init = Patch::new("", &gen_id(30), "");
|
let mut patch_init = Patch::new("", &gen_id(30), "", "");
|
||||||
let base = "text example\n hello";
|
let base = "text example\n hello";
|
||||||
patch_init.base = base.to_string();
|
patch_init.base = base.to_string();
|
||||||
|
|
||||||
@@ -517,7 +519,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transform() {
|
fn test_transform() {
|
||||||
let mut patch_init = Patch::new("", &gen_id(30), "");
|
let mut patch_init = Patch::new("", &gen_id(30), "", "");
|
||||||
let base = "text example\n hello";
|
let base = "text example\n hello";
|
||||||
patch_init.base = base.to_string();
|
patch_init.base = base.to_string();
|
||||||
|
|
||||||
@@ -555,7 +557,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transform2() {
|
fn test_transform2() {
|
||||||
let mut patch_init = Patch::new("", &gen_id(30), "");
|
let mut patch_init = Patch::new("", &gen_id(30), "", "");
|
||||||
let base = "#hello\n hello";
|
let base = "#hello\n hello";
|
||||||
patch_init.base = base.to_string();
|
patch_init.base = base.to_string();
|
||||||
|
|
||||||
@@ -587,7 +589,7 @@ mod tests {
|
|||||||
assert_eq!(op_method, op_method_deser);
|
assert_eq!(op_method, op_method_deser);
|
||||||
|
|
||||||
// serialize & deserialize Patch
|
// serialize & deserialize Patch
|
||||||
let mut patch = Patch::new("", &gen_id(30), "");
|
let mut patch = Patch::new("", &gen_id(30), "", "");
|
||||||
patch.insert("hello");
|
patch.insert("hello");
|
||||||
patch.delete(2);
|
patch.delete(2);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use async_std::sync::Arc;
|
use async_std::sync::Arc;
|
||||||
use std::{fs::File, io, io::Read, path::PathBuf};
|
use std::{fs::File, io, io::Read};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use darkfi::util::{
|
use darkfi::util::{
|
||||||
@@ -114,7 +114,7 @@ async fn main() -> DnetViewResult<()> {
|
|||||||
let log_level = get_log_level(args.verbose.into());
|
let log_level = get_log_level(args.verbose.into());
|
||||||
let log_config = get_log_config();
|
let log_config = get_log_config();
|
||||||
|
|
||||||
let log_file_path = PathBuf::from(expand_path(&args.log_path)?);
|
let log_file_path = expand_path(&args.log_path)?;
|
||||||
if let Some(parent) = log_file_path.parent() {
|
if let Some(parent) = log_file_path.parent() {
|
||||||
std::fs::create_dir_all(parent)?;
|
std::fs::create_dir_all(parent)?;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ impl DataParser {
|
|||||||
// Parse response
|
// Parse response
|
||||||
match response {
|
match response {
|
||||||
Ok(reply) => {
|
Ok(reply) => {
|
||||||
if !reply.as_object().is_some() || reply.as_object().unwrap().is_empty() {
|
if reply.as_object().is_none() || reply.as_object().unwrap().is_empty() {
|
||||||
return Err(DnetViewError::EmptyRpcReply)
|
return Err(DnetViewError::EmptyRpcReply)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{process::exit, str::FromStr, time::Instant};
|
use std::{process::exit, str::FromStr, time::Instant};
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use prettytable::{cell, format, row, Table};
|
use prettytable::{format, row, Table};
|
||||||
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use simplelog::{ColorChoice, TermLogger, TerminalMode};
|
use simplelog::{ColorChoice, TermLogger, TerminalMode};
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ outbound_connections=5
|
|||||||
seeds = ["tls://lilith0.dark.fi:25551", "tls://lilith1.dark.fi:25551"]
|
seeds = ["tls://lilith0.dark.fi:25551", "tls://lilith1.dark.fi:25551"]
|
||||||
|
|
||||||
# Prefered transports for outbound connections
|
# Prefered transports for outbound connections
|
||||||
#transports = ["tls", "tcp"]
|
#outbound_transports = ["tls", "tcp"]
|
||||||
|
|
||||||
## Only used for debugging. Compromises privacy when set.
|
## Only used for debugging. Compromises privacy when set.
|
||||||
#node_id = "foo"
|
#node_id = "foo"
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use async_std::sync::{Arc, Mutex};
|
use async_std::sync::{Arc, Mutex};
|
||||||
use std::{cmp::Ordering, collections::VecDeque};
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
collections::{BTreeMap, VecDeque},
|
||||||
|
};
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use fxhash::FxHashMap;
|
|
||||||
use ripemd::{Digest, Ripemd160};
|
use ripemd::{Digest, Ripemd160};
|
||||||
|
|
||||||
use crate::Privmsg;
|
use crate::Privmsg;
|
||||||
@@ -31,20 +33,33 @@ pub fn create_buffers() -> Buffers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct UMsgs(pub FxHashMap<String, Privmsg>);
|
pub struct UMsgs {
|
||||||
|
pub msgs: BTreeMap<String, Privmsg>,
|
||||||
|
capacity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl UMsgs {
|
impl UMsgs {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(FxHashMap::default())
|
Self { msgs: BTreeMap::new(), capacity: SIZE_OF_MSGS_BUFFER }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, msg: &Privmsg) -> String {
|
pub fn insert(&mut self, msg: &Privmsg) -> String {
|
||||||
let mut hasher = Ripemd160::new();
|
let mut hasher = Ripemd160::new();
|
||||||
hasher.update(msg.to_string());
|
hasher.update(msg.to_string());
|
||||||
let key = hex::encode(hasher.finalize());
|
let key = hex::encode(hasher.finalize());
|
||||||
self.0.insert(key.clone(), msg.clone());
|
|
||||||
|
if self.msgs.len() == self.capacity {
|
||||||
|
self.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.msgs.insert(key.clone(), msg.clone());
|
||||||
key
|
key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pop_front(&mut self) {
|
||||||
|
let first_key = self.msgs.iter().next_back().unwrap().0.clone();
|
||||||
|
self.msgs.remove(&first_key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UMsgs {
|
impl Default for UMsgs {
|
||||||
@@ -126,6 +141,14 @@ impl PrivmsgsBuffer {
|
|||||||
self.buffer.iter()
|
self.buffer.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.buffer.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn last_term(&self) -> u64 {
|
pub fn last_term(&self) -> u64 {
|
||||||
match self.buffer.len() {
|
match self.buffer.len() {
|
||||||
0 => 0,
|
0 => 0,
|
||||||
|
|||||||
@@ -157,8 +157,6 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
|
|||||||
let executor_cloned = executor.clone();
|
let executor_cloned = executor.clone();
|
||||||
executor_cloned.spawn(p2p.clone().run(executor.clone())).detach();
|
executor_cloned.spawn(p2p.clone().run(executor.clone())).detach();
|
||||||
|
|
||||||
p2p.clone().wait_for_outbound(executor.clone()).await?;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// RPC interface
|
// RPC interface
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ impl ProtocolPrivmsg {
|
|||||||
|
|
||||||
let mut inv_requested = vec![];
|
let mut inv_requested = vec![];
|
||||||
for inv_object in inv.invs.iter() {
|
for inv_object in inv.invs.iter() {
|
||||||
let msgs = &mut self.buffers.unread_msgs.lock().await.0;
|
let msgs = &mut self.buffers.unread_msgs.lock().await.msgs;
|
||||||
if let Some(msg) = msgs.get_mut(&inv_object.0) {
|
if let Some(msg) = msgs.get_mut(&inv_object.0) {
|
||||||
msg.read_confirms += 1;
|
msg.read_confirms += 1;
|
||||||
} else {
|
} else {
|
||||||
@@ -164,7 +164,7 @@ impl ProtocolPrivmsg {
|
|||||||
let getdata = self.getdata_sub.receive().await?;
|
let getdata = self.getdata_sub.receive().await?;
|
||||||
let getdata = (*getdata).to_owned();
|
let getdata = (*getdata).to_owned();
|
||||||
|
|
||||||
let msgs = &self.buffers.unread_msgs.lock().await.0;
|
let msgs = &self.buffers.unread_msgs.lock().await.msgs;
|
||||||
for inv in getdata.invs {
|
for inv in getdata.invs {
|
||||||
if let Some(msg) = msgs.get(&inv.0) {
|
if let Some(msg) = msgs.get(&inv.0) {
|
||||||
self.channel.send(msg.clone()).await?;
|
self.channel.send(msg.clone()).await?;
|
||||||
@@ -178,7 +178,7 @@ impl ProtocolPrivmsg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn update_unread_msgs(&self) -> Result<()> {
|
async fn update_unread_msgs(&self) -> Result<()> {
|
||||||
let msgs = &mut self.buffers.unread_msgs.lock().await.0;
|
let msgs = &mut self.buffers.unread_msgs.lock().await.msgs;
|
||||||
for (hash, msg) in msgs.clone() {
|
for (hash, msg) in msgs.clone() {
|
||||||
if msg.timestamp + UNREAD_MSG_EXPIRE_TIME < Utc::now().timestamp() {
|
if msg.timestamp + UNREAD_MSG_EXPIRE_TIME < Utc::now().timestamp() {
|
||||||
msgs.remove(&hash);
|
msgs.remove(&hash);
|
||||||
@@ -203,7 +203,7 @@ impl ProtocolPrivmsg {
|
|||||||
|
|
||||||
self.update_unread_msgs().await?;
|
self.update_unread_msgs().await?;
|
||||||
|
|
||||||
for msg in self.buffers.unread_msgs.lock().await.0.values() {
|
for msg in self.buffers.unread_msgs.lock().await.msgs.values() {
|
||||||
self.channel.send(msg.clone()).await?;
|
self.channel.send(msg.clone()).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crypto_box::SalsaBox;
|
use crypto_box::SalsaBox;
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use log::info;
|
use log::{info, warn};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use structopt_toml::StructOptToml;
|
use structopt_toml::StructOptToml;
|
||||||
@@ -135,6 +135,7 @@ fn parse_priv_key(data: &str) -> Result<String> {
|
|||||||
pk = prv_key.0.into();
|
pk = prv_key.0.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("Found secret key in config, noted it down.");
|
||||||
Ok(pk)
|
Ok(pk)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,9 +149,12 @@ fn parse_priv_key(data: &str) -> Result<String> {
|
|||||||
pub fn parse_configured_contacts(data: &str) -> Result<FxHashMap<String, ContactInfo>> {
|
pub fn parse_configured_contacts(data: &str) -> Result<FxHashMap<String, ContactInfo>> {
|
||||||
let mut ret = FxHashMap::default();
|
let mut ret = FxHashMap::default();
|
||||||
|
|
||||||
let map = match toml::from_str(data)? {
|
let map = match toml::from_str(data) {
|
||||||
Value::Table(m) => m,
|
Ok(Value::Table(m)) => m,
|
||||||
_ => return Ok(ret),
|
_ => {
|
||||||
|
warn!("Invalid TOML string passed as argument to parse_configured_contacts()");
|
||||||
|
return Ok(ret)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !map.contains_key("contact") {
|
if !map.contains_key("contact") {
|
||||||
@@ -158,34 +162,82 @@ pub fn parse_configured_contacts(data: &str) -> Result<FxHashMap<String, Contact
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !map["contact"].is_table() {
|
if !map["contact"].is_table() {
|
||||||
|
warn!("TOML configuration contains a \"contact\" field, but it is not a table.");
|
||||||
return Ok(ret)
|
return Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
let contacts = map["contact"].as_table().unwrap();
|
let contacts = map["contact"].as_table().unwrap();
|
||||||
|
|
||||||
|
// Our secret key for NaCl boxes.
|
||||||
|
let found_priv = match parse_priv_key(data) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
info!("Did not found private key in config, skipping contact configuration.");
|
||||||
|
return Ok(ret)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes: [u8; 32] = match bs58::decode(found_priv).into_vec() {
|
||||||
|
Ok(v) => {
|
||||||
|
if v.len() != 32 {
|
||||||
|
warn!("Decoded base58 secret key string is not 32 bytes");
|
||||||
|
warn!("Skipping private contact configuration");
|
||||||
|
return Ok(ret)
|
||||||
|
}
|
||||||
|
v.try_into().unwrap()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to decode base58 secret key from string: {}", e);
|
||||||
|
warn!("Skipping private contact configuration");
|
||||||
|
return Ok(ret)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let secret = crypto_box::SecretKey::from(bytes);
|
||||||
|
|
||||||
for cnt in contacts {
|
for cnt in contacts {
|
||||||
info!("Found configuration for contact {}", cnt.0);
|
info!("Found configuration for contact {}", cnt.0);
|
||||||
|
|
||||||
let mut contact_info = ContactInfo::new()?;
|
let mut contact_info = ContactInfo::new()?;
|
||||||
|
|
||||||
if !cnt.1.is_table() && !cnt.1.as_table().unwrap().contains_key("contact_pubkey") {
|
if !cnt.1.is_table() {
|
||||||
|
warn!("Config for contact {} isn't a TOML table", cnt.0);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let table = cnt.1.as_table().unwrap();
|
||||||
|
if table.is_empty() {
|
||||||
|
warn!("Configuration for contact {} is empty.", cnt.0);
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the NaCl box
|
// Build the NaCl box
|
||||||
//// public_key
|
if !table.contains_key("contact_pubkey") || !table["contact_pubkey"].is_str() {
|
||||||
let pubkey = cnt.1["contact_pubkey"].as_str().unwrap();
|
warn!("Contact {} doesn't have `contact_pubkey` set or is not a string.", cnt.0);
|
||||||
let bytes: [u8; 32] = bs58::decode(pubkey).into_vec()?.try_into().unwrap();
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let pub_str = table["contact_pubkey"].as_str().unwrap();
|
||||||
|
let bytes: [u8; 32] = match bs58::decode(pub_str).into_vec() {
|
||||||
|
Ok(v) => {
|
||||||
|
if v.len() != 32 {
|
||||||
|
warn!("Decoded base58 string is not 32 bytes");
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v.try_into().unwrap()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to decode base58 pubkey from string: {}", e);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let public = crypto_box::PublicKey::from(bytes);
|
let public = crypto_box::PublicKey::from(bytes);
|
||||||
|
|
||||||
//// private_key
|
|
||||||
let bytes: [u8; 32] = bs58::decode(parse_priv_key(data)?).into_vec()?.try_into().unwrap();
|
|
||||||
let secret = crypto_box::SecretKey::from(bytes);
|
|
||||||
|
|
||||||
contact_info.salt_box = Some(SalsaBox::new(&public, &secret));
|
contact_info.salt_box = Some(SalsaBox::new(&public, &secret));
|
||||||
ret.insert(cnt.0.to_string(), contact_info);
|
ret.insert(cnt.0.to_string(), contact_info);
|
||||||
info!("Instantiated NaCl box for contact {}", cnt.0);
|
info!("Instantiated NaCl box for contact {}", cnt.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
|
|||||||
if settings.refresh {
|
if settings.refresh {
|
||||||
println!("Removing local data in: {:?} (yes/no)? ", datastore_path);
|
println!("Removing local data in: {:?} (yes/no)? ", datastore_path);
|
||||||
let mut confirm = String::new();
|
let mut confirm = String::new();
|
||||||
stdin().read_line(&mut confirm).ok().expect("Failed to read line");
|
stdin().read_line(&mut confirm).expect("Failed to read line");
|
||||||
|
|
||||||
let confirm = confirm.to_lowercase();
|
let confirm = confirm.to_lowercase();
|
||||||
let confirm = confirm.trim();
|
let confirm = confirm.trim();
|
||||||
@@ -221,8 +221,6 @@ async fn realmain(settings: Args, executor: Arc<Executor<'_>>) -> Result<()> {
|
|||||||
|
|
||||||
executor.spawn(p2p.clone().run(executor.clone())).detach();
|
executor.spawn(p2p.clone().run(executor.clone())).detach();
|
||||||
|
|
||||||
p2p.clone().wait_for_outbound(executor.clone()).await?;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// RPC interface
|
// RPC interface
|
||||||
//
|
//
|
||||||
|
|||||||
79
contrib/dependency_setup.sh
Normal file
79
contrib/dependency_setup.sh
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$UID" != 0 ]; then
|
||||||
|
SUDO="$(command -v sudo)"
|
||||||
|
else
|
||||||
|
SUDO=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
brew_sh="https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh"
|
||||||
|
|
||||||
|
setup_mac() {
|
||||||
|
if ! command -v brew >/dev/null; then
|
||||||
|
echo "brew not found, installing..." >&2
|
||||||
|
bash -c "$(curl -fL "${brew_sh}")" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for i in cmake gcc jq pkgconf llvm@13 freetype expat; do
|
||||||
|
echo "Installing $i with brew..." >&2
|
||||||
|
brew install "$i" || return 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_apt() {
|
||||||
|
APTGET="$SUDO $1"
|
||||||
|
|
||||||
|
$APTGET update || return 1
|
||||||
|
$APTGET install -y build-essential cmake jq wget pkg-config \
|
||||||
|
clang libclang-dev llvm-dev libudev-dev libfreetype6-dev \
|
||||||
|
libexpat1-dev || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_xbps() {
|
||||||
|
XBPS="$SUDO $1"
|
||||||
|
|
||||||
|
$XBPS -S base-devel cmake wget expat-devel freetype-devel \
|
||||||
|
fontconfig-devel jq openssl-devel clang libclang llvm \
|
||||||
|
libllvm12 libgudev-devel
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Linux)
|
||||||
|
if command -v apt >/dev/null; then
|
||||||
|
echo "Setting up for apt" >&2
|
||||||
|
setup_apt "$(command -v apt)" || exit 1
|
||||||
|
echo "Dependencies installed!" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v apt-get >/dev/null; then
|
||||||
|
echo "Setting up for apt-get" >&2
|
||||||
|
setup_apt "$(command -v apt-get)" || exit 1
|
||||||
|
echo "Dependencies installed!" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v xbps-install; then
|
||||||
|
echo "Setting up for xbps" >&2
|
||||||
|
setup_xbps "$(command -v xbps-install)" || exit 1
|
||||||
|
echo "Dependencies installed!" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Error: Could not recognize your package manager." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
Darwin)
|
||||||
|
echo "Setting up for OSX" >&2
|
||||||
|
setup_mac || exit 1
|
||||||
|
echo "Dependencies installed!" >&2
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
*|"")
|
||||||
|
echo "Unsupported OS, sorry." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if ! command -v brew &> /dev/null ; then
|
|
||||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
brew install cmake
|
|
||||||
brew install gcc
|
|
||||||
brew install jq
|
|
||||||
brew install pkgconf
|
|
||||||
brew install llvm@13
|
|
||||||
brew install freetype
|
|
||||||
brew install expat
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
# Update the version in the toplevel Cargo.toml for DarkFi, and then run this
|
||||||
|
# script to update all the other Cargo.toml files.
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from os import chdir
|
from os import chdir
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
sudo xbps-install -S \
|
|
||||||
base-devel \
|
|
||||||
cmake \
|
|
||||||
wget \
|
|
||||||
expat-devel \
|
|
||||||
freetype-devel \
|
|
||||||
fontconfig-devel \
|
|
||||||
jq \
|
|
||||||
openssl-devel \
|
|
||||||
clang \
|
|
||||||
libclang \
|
|
||||||
llvm \
|
|
||||||
libllvm12 \
|
|
||||||
libgudev-devel \
|
|
||||||
@@ -65,7 +65,7 @@ on how to install it on your computer.
|
|||||||
Once installed, we can configure a new server which will represent our
|
Once installed, we can configure a new server which will represent our
|
||||||
`ircd` instance. First, start weechat, and in its window - run the
|
`ircd` instance. First, start weechat, and in its window - run the
|
||||||
following commands (there is an assumption that `irc_listen` in the
|
following commands (there is an assumption that `irc_listen` in the
|
||||||
`ircd` config file is set to `127.0.0.1:11066`):
|
`ircd` config file is set to `127.0.0.1:6667`):
|
||||||
|
|
||||||
```
|
```
|
||||||
/server add darkfi localhost/6667 -autoconnect
|
/server add darkfi localhost/6667 -autoconnect
|
||||||
|
|||||||
@@ -286,10 +286,10 @@ impl Channel {
|
|||||||
if Self::is_eof_error(err.clone()) {
|
if Self::is_eof_error(err.clone()) {
|
||||||
info!("Inbound connection {} disconnected", self.address());
|
info!("Inbound connection {} disconnected", self.address());
|
||||||
} else {
|
} else {
|
||||||
error!("Read error on channel: {}", err);
|
error!("Read error on channel {}: {}", self.address(), err);
|
||||||
}
|
}
|
||||||
debug!(target: "net",
|
debug!(target: "net",
|
||||||
"Channel::receive_loop() stopping channel {:?}",
|
"Channel::receive_loop() stopping channel {}",
|
||||||
self.address()
|
self.address()
|
||||||
);
|
);
|
||||||
self.stop().await;
|
self.stop().await;
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ impl P2p {
|
|||||||
|
|
||||||
// Wait for the process for each of the provided addresses, excluding our own inbound addresses
|
// Wait for the process for each of the provided addresses, excluding our own inbound addresses
|
||||||
async fn outbound_addr_loop(
|
async fn outbound_addr_loop(
|
||||||
self_inbound_addr: &Vec<Url>,
|
self_inbound_addr: &[Url],
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
stop_sub: Subscription<()>,
|
stop_sub: Subscription<()>,
|
||||||
addrs: &Vec<Url>,
|
addrs: &Vec<Url>,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SEND_ADDR_SLEEP_SECONDS: u64 = 900;
|
const SEND_ADDR_SLEEP_SECONDS: u64 = 900;
|
||||||
const LOCALNET: [&'static str; 5] = ["localhost", "0.0.0.0", "[::]", "127.0.0.1", "[::1]"];
|
const LOCALNET: [&str; 5] = ["localhost", "0.0.0.0", "[::]", "127.0.0.1", "[::1]"];
|
||||||
|
|
||||||
/// Defines address and get-address messages.
|
/// Defines address and get-address messages.
|
||||||
pub struct ProtocolAddress {
|
pub struct ProtocolAddress {
|
||||||
@@ -67,10 +67,9 @@ impl ProtocolAddress {
|
|||||||
loop {
|
loop {
|
||||||
let addrs_msg = self.addrs_sub.receive().await?;
|
let addrs_msg = self.addrs_sub.receive().await?;
|
||||||
debug!(target: "net", "ProtocolAddress::handle_receive_addrs() received {} addrs", addrs_msg.addrs.len());
|
debug!(target: "net", "ProtocolAddress::handle_receive_addrs() received {} addrs", addrs_msg.addrs.len());
|
||||||
let mut filtered = vec![];
|
let addrs = if !self.settings.localnet {
|
||||||
for (i, addr) in addrs_msg.addrs.iter().enumerate() {
|
let mut filtered = vec![];
|
||||||
debug!(target: "net", " addr[{}]: {}", i, addr);
|
for addr in &addrs_msg.addrs {
|
||||||
if !self.settings.localnet {
|
|
||||||
match addr.host_str() {
|
match addr.host_str() {
|
||||||
Some(host_str) => {
|
Some(host_str) => {
|
||||||
if LOCALNET.contains(&host_str) {
|
if LOCALNET.contains(&host_str) {
|
||||||
@@ -83,10 +82,13 @@ impl ProtocolAddress {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
filtered.push(addr.clone());
|
||||||
}
|
}
|
||||||
filtered.push(addr.clone());
|
filtered
|
||||||
}
|
} else {
|
||||||
self.hosts.store(filtered).await;
|
addrs_msg.addrs.clone()
|
||||||
|
};
|
||||||
|
self.hosts.store(addrs).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -219,8 +219,8 @@ impl ProgramState for State {
|
|||||||
if let Ok(mr) = self.merkle_roots.contains(merkle_root) {
|
if let Ok(mr) = self.merkle_roots.contains(merkle_root) {
|
||||||
return mr
|
return mr
|
||||||
}
|
}
|
||||||
// FIXME: An error here means a db issue
|
|
||||||
false
|
panic!("RootStore db corruption, could not check merkle_roots.contains()");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
||||||
@@ -228,8 +228,8 @@ impl ProgramState for State {
|
|||||||
if let Ok(nf) = self.nullifiers.contains(nullifier) {
|
if let Ok(nf) = self.nullifiers.contains(nullifier) {
|
||||||
return nf
|
return nf
|
||||||
}
|
}
|
||||||
// FIXME: An error here means a db issue
|
|
||||||
false
|
panic!("NullifierStore db corruption, could not check nullifiers.contains()");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mint_vk(&self) -> &VerifyingKey {
|
fn mint_vk(&self) -> &VerifyingKey {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use async_std::{
|
use async_std::{
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
task,
|
task::sleep,
|
||||||
};
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ use rand::{rngs::OsRng, Rng, RngCore};
|
|||||||
use crate::{
|
use crate::{
|
||||||
net,
|
net,
|
||||||
util::{
|
util::{
|
||||||
self, gen_id,
|
gen_id,
|
||||||
serial::{deserialize, serialize, Decodable, Encodable},
|
serial::{deserialize, serialize, Decodable, Encodable},
|
||||||
},
|
},
|
||||||
Error, Result,
|
Error, Result,
|
||||||
@@ -29,9 +29,9 @@ use super::{
|
|||||||
prune_map, DataStore, RaftSettings,
|
prune_map, DataStore, RaftSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn send_node_id_loop(sender: async_channel::Sender<()>, timeout: i64) -> Result<()> {
|
async fn send_loop(sender: async_channel::Sender<()>, timeout: Duration) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
util::sleep(timeout as u64).await;
|
sleep(timeout).await;
|
||||||
sender.send(()).await?;
|
sender.send(()).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,6 +52,8 @@ pub struct Raft<T> {
|
|||||||
|
|
||||||
pub(super) last_term: u64,
|
pub(super) last_term: u64,
|
||||||
|
|
||||||
|
pub(super) last_heartbeat: i64,
|
||||||
|
|
||||||
p2p_sender: Sender,
|
p2p_sender: Sender,
|
||||||
|
|
||||||
msgs_channel: Channel<T>,
|
msgs_channel: Channel<T>,
|
||||||
@@ -61,7 +63,7 @@ pub struct Raft<T> {
|
|||||||
|
|
||||||
seen_msgs: Arc<Mutex<FxHashMap<String, i64>>>,
|
seen_msgs: Arc<Mutex<FxHashMap<String, i64>>>,
|
||||||
|
|
||||||
settings: RaftSettings,
|
pub(super) settings: RaftSettings,
|
||||||
|
|
||||||
pending_msgs: Vec<T>,
|
pending_msgs: Vec<T>,
|
||||||
}
|
}
|
||||||
@@ -104,6 +106,7 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
|
|||||||
acked_length: MapLength(FxHashMap::default()),
|
acked_length: MapLength(FxHashMap::default()),
|
||||||
nodes: Arc::new(Mutex::new(FxHashMap::default())),
|
nodes: Arc::new(Mutex::new(FxHashMap::default())),
|
||||||
last_term: 0,
|
last_term: 0,
|
||||||
|
last_heartbeat: Utc::now().timestamp(),
|
||||||
p2p_sender,
|
p2p_sender,
|
||||||
msgs_channel,
|
msgs_channel,
|
||||||
commits_channel,
|
commits_channel,
|
||||||
@@ -126,45 +129,39 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let p2p_send_task = executor.spawn(p2p_send_loop(self.p2p_sender.1.clone(), p2p.clone()));
|
let p2p_send_task = executor.spawn(p2p_send_loop(self.p2p_sender.1.clone(), p2p.clone()));
|
||||||
|
|
||||||
let prune_seen_messages_task = executor.spawn(prune_map::<String>(
|
let prune_seen_messages_task = executor
|
||||||
self.seen_msgs.clone(),
|
.spawn(prune_map::<String>(self.seen_msgs.clone(), self.settings.prun_duration));
|
||||||
self.settings.prun_messages_duration,
|
|
||||||
));
|
|
||||||
|
|
||||||
let prune_nodes_id_task = executor
|
let prune_nodes_id_task =
|
||||||
.spawn(prune_map::<NodeId>(self.nodes.clone(), self.settings.prun_nodes_ids_duration));
|
executor.spawn(prune_map::<NodeId>(self.nodes.clone(), self.settings.prun_duration));
|
||||||
|
|
||||||
let (node_id_sx, node_id_rv) = async_channel::unbounded::<()>();
|
|
||||||
let send_node_id_loop_task =
|
|
||||||
executor.spawn(send_node_id_loop(node_id_sx, self.settings.node_id_timeout));
|
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let (id_sx, id_rv) = async_channel::unbounded::<()>();
|
||||||
|
let (heartbeat_sx, heartbeat_rv) = async_channel::unbounded::<()>();
|
||||||
|
let (timeout_sx, timeout_rv) = async_channel::unbounded::<()>();
|
||||||
|
|
||||||
|
let id_timeout = Duration::from_secs(self.settings.id_timeout);
|
||||||
|
let send_id_task = executor.spawn(send_loop(id_sx, id_timeout));
|
||||||
|
|
||||||
|
let heartbeat_timeout = Duration::from_millis(self.settings.heartbeat_timeout);
|
||||||
|
let send_heartbeat_task = executor.spawn(send_loop(heartbeat_sx, heartbeat_timeout));
|
||||||
|
|
||||||
|
let timeout =
|
||||||
|
Duration::from_secs(rng.gen_range(0..self.settings.timeout) + self.settings.timeout);
|
||||||
|
let send_timeout_task = executor.spawn(send_loop(timeout_sx, timeout));
|
||||||
|
|
||||||
let broadcast_msg_rv = self.msgs_channel.1.clone();
|
let broadcast_msg_rv = self.msgs_channel.1.clone();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let timeout = if self.role == Role::Leader {
|
let mut result = select! {
|
||||||
self.settings.heartbeat_timeout
|
m = p2p_recv_channel.recv().fuse() => self.handle_method(m?).await,
|
||||||
} else {
|
m = broadcast_msg_rv.recv().fuse() => self.broadcast_msg(&m?,None).await,
|
||||||
rng.gen_range(0..self.settings.timeout) + self.settings.timeout
|
_ = id_rv.recv().fuse() => self.send_id_msg().await,
|
||||||
};
|
_ = heartbeat_rv.recv().fuse() => self.send_heartbeat().await,
|
||||||
let timeout = Duration::from_millis(timeout);
|
_ = timeout_rv.recv().fuse() => self.send_vote_request().await,
|
||||||
|
|
||||||
let mut result: Result<()>;
|
|
||||||
|
|
||||||
select! {
|
|
||||||
m = p2p_recv_channel.recv().fuse() => result = self.handle_method(m?).await,
|
|
||||||
m = broadcast_msg_rv.recv().fuse() => result = self.broadcast_msg(&m?,None).await,
|
|
||||||
_ = node_id_rv.recv().fuse() => result = self.send_node_id_msg().await,
|
|
||||||
_ = task::sleep(timeout).fuse() => {
|
|
||||||
result = if self.role == Role::Leader {
|
|
||||||
self.send_heartbeat().await
|
|
||||||
}else {
|
|
||||||
self.send_vote_request().await
|
|
||||||
};
|
|
||||||
},
|
|
||||||
_ = stop_signal.recv().fuse() => break,
|
_ = stop_signal.recv().fuse() => break,
|
||||||
}
|
};
|
||||||
|
|
||||||
// send pending messages
|
// send pending messages
|
||||||
if !self.pending_msgs.is_empty() && self.role != Role::Candidate {
|
if !self.pending_msgs.is_empty() && self.role != Role::Candidate {
|
||||||
@@ -175,9 +172,8 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
|
|||||||
self.pending_msgs = vec![];
|
self.pending_msgs = vec![];
|
||||||
}
|
}
|
||||||
|
|
||||||
match result {
|
if let Err(e) = result {
|
||||||
Ok(_) => {}
|
warn!(target: "raft", "warn: {}", e);
|
||||||
Err(e) => warn!(target: "raft", "warn: {}", e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +181,9 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
|
|||||||
p2p_send_task.cancel().await;
|
p2p_send_task.cancel().await;
|
||||||
prune_seen_messages_task.cancel().await;
|
prune_seen_messages_task.cancel().await;
|
||||||
prune_nodes_id_task.cancel().await;
|
prune_nodes_id_task.cancel().await;
|
||||||
send_node_id_loop_task.cancel().await;
|
send_id_task.cancel().await;
|
||||||
|
send_heartbeat_task.cancel().await;
|
||||||
|
send_timeout_task.cancel().await;
|
||||||
self.datastore.flush().await?;
|
self.datastore.flush().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -213,9 +211,9 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
|
|||||||
self.id.clone()
|
self.id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_node_id_msg(&self) -> Result<()> {
|
async fn send_id_msg(&self) -> Result<()> {
|
||||||
let node_id_msg = serialize(&NodeIdMsg { id: self.id.clone() });
|
let id_msg = serialize(&NodeIdMsg { id: self.id.clone() });
|
||||||
self.send(None, &node_id_msg, NetMsgMethod::NodeIdMsg, None).await?;
|
self.send(None, &id_msg, NetMsgMethod::NodeIdMsg, None).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +236,6 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
Role::Candidate => {
|
Role::Candidate => {
|
||||||
warn!("The role is Candidate, add the msg to pending_msgs");
|
|
||||||
self.pending_msgs.push(msg.clone());
|
self.pending_msgs.push(msg.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,6 +252,7 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
|
|||||||
self.receive_log_response(lr).await?;
|
self.receive_log_response(lr).await?;
|
||||||
}
|
}
|
||||||
NetMsgMethod::LogRequest => {
|
NetMsgMethod::LogRequest => {
|
||||||
|
self.last_heartbeat = Utc::now().timestamp();
|
||||||
let lr: LogRequest = deserialize(&msg.payload)?;
|
let lr: LogRequest = deserialize(&msg.payload)?;
|
||||||
self.receive_log_request(lr).await?;
|
self.receive_log_request(lr).await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use chrono::Utc;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -12,7 +13,15 @@ use super::{
|
|||||||
|
|
||||||
impl<T: Decodable + Encodable + Clone> Raft<T> {
|
impl<T: Decodable + Encodable + Clone> Raft<T> {
|
||||||
pub(super) async fn send_vote_request(&mut self) -> Result<()> {
|
pub(super) async fn send_vote_request(&mut self) -> Result<()> {
|
||||||
let self_id = self.id();
|
if self.role == Role::Leader {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_heartbeat_duration = Utc::now().timestamp() - self.last_heartbeat;
|
||||||
|
|
||||||
|
if last_heartbeat_duration < self.settings.timeout as i64 {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
self.set_current_term(&(self.current_term()? + 1))?;
|
self.set_current_term(&(self.current_term()? + 1))?;
|
||||||
|
|
||||||
@@ -21,14 +30,13 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
|
|||||||
self.role = Role::Candidate;
|
self.role = Role::Candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_voted_for(&Some(self_id.clone()))?;
|
self.set_voted_for(&Some(self.id()))?;
|
||||||
self.votes_received = vec![];
|
self.votes_received = vec![self.id()];
|
||||||
self.votes_received.push(self_id.clone());
|
|
||||||
|
|
||||||
self.reset_last_term()?;
|
self.reset_last_term()?;
|
||||||
|
|
||||||
let request = VoteRequest {
|
let request = VoteRequest {
|
||||||
node_id: self_id,
|
node_id: self.id(),
|
||||||
current_term: self.current_term()?,
|
current_term: self.current_term()?,
|
||||||
log_length: self.logs_len(),
|
log_length: self.logs_len(),
|
||||||
last_term: self.last_term,
|
last_term: self.last_term,
|
||||||
@@ -40,6 +48,10 @@ impl<T: Decodable + Encodable + Clone> Raft<T> {
|
|||||||
|
|
||||||
pub(super) async fn receive_vote_response(&mut self, vr: VoteResponse) -> Result<()> {
|
pub(super) async fn receive_vote_response(&mut self, vr: VoteResponse) -> Result<()> {
|
||||||
if self.role == Role::Candidate && vr.current_term == self.current_term()? && vr.ok {
|
if self.role == Role::Candidate && vr.current_term == self.current_term()? && vr.ok {
|
||||||
|
if self.votes_received.contains(&vr.node_id) {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
self.votes_received.push(vr.node_id);
|
self.votes_received.push(vr.node_id);
|
||||||
|
|
||||||
let nodes = self.nodes.lock().await;
|
let nodes = self.nodes.lock().await;
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ use super::{
|
|||||||
|
|
||||||
impl<T: Decodable + Encodable + Clone> Raft<T> {
|
impl<T: Decodable + Encodable + Clone> Raft<T> {
|
||||||
pub(super) async fn send_heartbeat(&mut self) -> Result<()> {
|
pub(super) async fn send_heartbeat(&mut self) -> Result<()> {
|
||||||
|
if self.role != Role::Leader {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
let nodes = self.nodes.lock().await;
|
let nodes = self.nodes.lock().await;
|
||||||
let nodes_cloned = nodes.clone();
|
let nodes_cloned = nodes.clone();
|
||||||
drop(nodes);
|
drop(nodes);
|
||||||
|
|||||||
@@ -2,23 +2,19 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RaftSettings {
|
pub struct RaftSettings {
|
||||||
//
|
// the leader duration for sending heartbeat; in milliseconds
|
||||||
// Milliseconds
|
|
||||||
//
|
|
||||||
pub heartbeat_timeout: u64,
|
pub heartbeat_timeout: u64,
|
||||||
|
|
||||||
|
// the duration for electing new leader; in seconds
|
||||||
pub timeout: u64,
|
pub timeout: u64,
|
||||||
|
|
||||||
//
|
// the duration for sending id to other nodes; in seconds
|
||||||
// Seconds
|
pub id_timeout: u64,
|
||||||
//
|
|
||||||
pub prun_messages_duration: i64,
|
// this duration used to clean up hashmaps; in seconds
|
||||||
pub prun_nodes_ids_duration: i64,
|
pub prun_duration: i64,
|
||||||
// must be greater than (timeout * 2)
|
|
||||||
pub node_id_timeout: i64,
|
|
||||||
|
|
||||||
//
|
|
||||||
// Datastore path
|
// Datastore path
|
||||||
//
|
|
||||||
pub datastore_path: PathBuf,
|
pub datastore_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,10 +22,9 @@ impl Default for RaftSettings {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
heartbeat_timeout: 500,
|
heartbeat_timeout: 500,
|
||||||
timeout: 7000,
|
timeout: 6,
|
||||||
prun_messages_duration: 120,
|
id_timeout: 12,
|
||||||
prun_nodes_ids_duration: 120,
|
prun_duration: 240,
|
||||||
node_id_timeout: 16,
|
|
||||||
datastore_path: PathBuf::from(""),
|
datastore_path: PathBuf::from(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user