...
 
Commits (11)
genesis_data
priv/current_data.bin
.eunit
deps
......
image: erlang:20
test:
before_script:
# Workaround for https://github.com/ninenines/erlang.mk/issues/501
- make plt || test $? -eq 2
script:
- make check
- make rel
......@@ -14,18 +14,19 @@ PROJECT = ercoin
PROJECT_DESCRIPTION = A simple cryptocurrency using Tendermint
PROJECT_VERSION = 0.1.0
DEPS = erlsha2 libsodium abci_server gb_merkle_trees dynarec jiffy triq
dep_abci_server = git https://github.com/KrzysiekJ/abci_server.git v0.3.0
DEPS = abci_server dynarec erlsha2 gb_merkle_trees jiffy libsodium nist_beacon triq
dep_abci_server = git https://github.com/KrzysiekJ/abci_server.git v0.4.0
dep_dynarec = git https://github.com/dieswaytoofast/dynarec.git 1f477
dep_erlsha2 = git https://github.com/vinoski/erlsha2 2.2.1
dep_gb_merkle_trees = git https://github.com/KrzysiekJ/gb_merkle_trees.git v0.2.0
dep_jiffy = git https://github.com/davisp/jiffy.git 0.14.11
dep_libsodium = git https://github.com/potatosalad/erlang-libsodium.git 0.0.10
dep_triq = git https://github.com/triqng/triq.git 9d4951a
dep_nist_beacon = git https://gitlab.com/KrzysiekJ/nist_beacon v0.1.1
dep_triq = git https://github.com/triqng/triq.git babad
BUILD_DEPS = lfe lfe.mk
dep_lfe = git https://github.com/rvirding/lfe 2880c8a2
dep_lfe.mk = git https://github.com/ninenines/lfe.mk master
dep_lfe.mk = git https://github.com/KrzysiekJ/lfe.mk be7a892
DEP_PLUGINS = lfe.mk
# Whitespace to be used when creating files from templates.
......
......@@ -18,15 +18,14 @@ For support and other ephemeral discussions, see [the #ercoin IRC channel on irc
## Development installation
1. Install [Tendermint](https://tendermint.com) (version 0.10.x).
1. Install [Tendermint](https://tendermint.com) (version 0.15.x).
2. Install [Erlang](https://www.erlang.org) (19 is the minimum version).
3. Install [IPFS](https://ipfs.io/) and run `ipfs init`.
4. Install [Sodium](https://download.libsodium.org/doc/).
5. Clone the Ercoin’s repository and enter the created directory.
6. `./bootstrap.sh`.
7. `make app shell`.
8. In the opened Erlang shell, execute `application:ensure_all_started(ercoin).`.
9. In another window, run `tendermint node --home ~/.ercoin/`.
7. `make run`.
8. In another window, run `tendermint node --home ~/.ercoin/`.
You can play with the node using [`ercoin_wallet`](https://gitlab.com/Ercoin/ercoin_wallet).
......@@ -128,9 +127,12 @@ The following Merkle tree is formed when calculating an application hash:
* Other data hash:
* Protocol number.
* Block height.
* POSIX timestamp of last block in miliseconds.
* Short fee deposit.
* Long fee deposit.
* Validators’ hash.
* Transfer transactions’ hash.
* Vote transactions’ hash.
* Future validators’ hash.
* Fresh transactions’ hash.
### State machine mechanism
......@@ -150,7 +152,12 @@ Every transaction except a vote transaction deducts a fee from the account from
At the end of every epoch, short fee deposit and part of long fee deposit are divided between those validators that have less than 2/3 of absencies in block signatures, proportionally to their voting power.
At the end of every epoch, a new validator set is drawn from the locked accounts. Voting power is statistically proportional to account balance and to lock period.
When new epoch is reached, validator set is replaced by a new one which has been drawn previously. When quarter of epoch (rounded down) is reached, data is locked for the purpose of drawing new validators. When three quarters of epoch (rounded down) are reached, result of drawing is yielded into application state. From that point future validators are able to send fee votes.
Drawing of validators is performed randomly among locked accounts, with handicap proportional to:
* account balance;
* time for which an account will be locked, counting from the start of the next epoch.
If a validator proposes an invalid tx in a block, it pays a fee like it was a transaction tx.
......
......@@ -12,7 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
IPFS_BOOTSTRAP_HASH=QmXo1TymYh7NSGMZLxV1LUqSx2PjC7m7pi3FwE1oxfbQFt
IPFS_BOOTSTRAP_HASH=QmSykgTV3Pdg9EsrdQDFkJpKMemaB1xQCbXN4uAqV5xmLS
ipfs get /ipfs/$IPFS_BOOTSTRAP_HASH/genesis_data
mkdir -p priv
ipfs get -o ~/.ercoin /ipfs/$IPFS_BOOTSTRAP_HASH/tendermint
ipfs get -o ~/.ercoin/current_data.bin /ipfs/$IPFS_BOOTSTRAP_HASH/genesis_data
......@@ -10,7 +10,6 @@
%% See the License for the specific language governing permissions and
%% limitations under the License.
-define(EPOCH_LENGTH, 7200). %% In blocks.
-define(FEE_DEPOSIT_LONG_RATIO, 13140). %% Inversed ratio of long fee deposit which is dispatched.
-define(MAX_BLOCK_HEIGHT, 4294967295). %% 2^32 - 1
-define(HASH(X), crypto:hash(sha256, X)).
......@@ -32,6 +31,8 @@
-type balance() :: 0..18446744073709551615.
-type fee() :: 0..18446744073709551615.
-type tx_value() :: 0..18446744073709551615.
%% @type timestamp() = 0..18446744073709551615. POSIX timestamp in miliseconds.
-type timestamp() :: 0..18446744073709551615.
-type voting_power() :: non_neg_integer().
-type message() :: binary().
-record(
......@@ -81,12 +82,14 @@
{protocol=1 :: pos_integer(),
epoch_length :: 2..31536000,
height=0 :: block_height() | 0,
timestamp :: timestamp(),
%% Short deposit collects fees for current processing, long deposit collects fees for long term storage.
fee_deposit_short=0 :: fee(),
fee_deposit_long=0 :: fee(),
fresh_txs=?SETS:new() :: ?SETS:set(binary()),
fresh_txs_hash= <<0:256>> :: hash(),
accounts=gb_merkle_trees:empty() :: gb_merkle_trees:tree(),
last_block_hash= <<0:256>> :: <<_:256>>,
validators=gb_merkle_trees:empty() :: gb_merkle_trees:tree()}).
entropy_fun :: {Module :: atom(), Function :: atom()},
validators=gb_merkle_trees:empty() :: gb_merkle_trees:tree(),
future_validators :: undefined | {promise, docile_rpc:key(), hash()} | gb_merkle_trees:tree()}).
-type data() :: #data{}.
%% @doc An incomplete version of RPC module, which doesn’t send replies when not asked to do so.
-module(docile_rpc).
-export_type([key/0]).
-export(
[async_call/4,
yield/1,
nb_yield/1,
nb_yield/2]).
-opaque key() :: pid().
-spec async_call(Node, Module, Function, Args) -> Key when
Node :: node(),
Module :: module(),
Function :: atom(),
Args :: [term()],
Key :: key().
async_call(Node, Mod, Fun, Args) ->
ReplyTo = self(),
spawn(
fun() ->
R = rpc:call(Node, Mod, Fun, Args), %% proper rpc
ok =
receive
deliver_reply ->
ok
end,
ReplyTo ! {self(), {promise_reply, R}} %% self() is key
end).
-spec yield(Key) -> Res | {badrpc, Reason} when
Key :: key(),
Res :: term(),
Reason :: term().
yield(Key) when is_pid(Key) ->
{value,R} = do_yield(Key, infinity),
R.
-spec nb_yield(Key, Timeout) -> {value, Val} | timeout when
Key :: key(),
Timeout :: timeout(),
Val :: (Res :: term()) | {badrpc, Reason :: term()}.
nb_yield(Key, infinity=Inf) when is_pid(Key) ->
do_yield(Key, Inf);
nb_yield(Key, Timeout) when is_pid(Key), is_integer(Timeout), Timeout >= 0 ->
do_yield(Key, Timeout).
-spec nb_yield(Key) -> {value, Val} | timeout when
Key :: key(),
Val :: (Res :: term()) | {badrpc, Reason :: term()}.
nb_yield(Key) when is_pid(Key) ->
do_yield(Key, 0).
-spec do_yield(pid(), timeout()) -> {'value', _} | 'timeout'.
do_yield(Key, Timeout) ->
Key ! deliver_reply,
receive
{Key,{promise_reply,R}} ->
{value,R}
after Timeout ->
timeout
end.
-module(ercoin_entropy).
-export([reliable_entropy/1]).
-export([simple_entropy/1]).
-include_lib("include/ercoin.hrl").
-spec reliable_entropy(data()) -> binary().
%% @doc Obtain entropy for data in such way that should not be subject to manipulation.
reliable_entropy(#data{timestamp=Timestamp, epoch_length=EpochLength}) ->
%% Drawing of validators waits half of the epoch before yielding result, so waiting for 1/100th of the epoch
%% before obtaining entropy (to further ensure that it will not be manipulated) should leave us with plenty of time
%% to wait in case of eventual beacon downtime.
nist_beacon:pulse(Timestamp + EpochLength div 100).
-spec simple_entropy(data()) -> binary().
%% @doc Obtain pseudoentropy for data, possibly for development purposes.
%% Will be cheap and quick to generate but don’t expect it to be secure.
simple_entropy(#data{height=Height, timestamp=Timestamp}) ->
?HASH(<<Height, Timestamp:8/unit:8>>).
......@@ -13,8 +13,7 @@
-module(ercoin_genesis).
-export(
[genesis/0,
data_to_genesis_json/1,
[data_to_genesis_json/1,
sk_to_priv_validator_json/1]).
-include_lib("include/ercoin.hrl").
......@@ -37,11 +36,6 @@ binary_to_iolist(<<Head/integer, Rest/binary>>) ->
binary_to_hex(Bin) ->
iolist_to_binary(binary_to_iolist(Bin)).
-spec genesis() -> data().
genesis() ->
{ok, GenesisBin} = file:read_file("genesis_data"),
binary_to_term(GenesisBin).
%% @doc Convert data to a genesis.json file for Tendermint.
-spec data_to_genesis_json(data()) -> binary().
data_to_genesis_json(Genesis) ->
......
-module(ercoin_persistence).
-behaviour(gen_server).
%% API.
-export([start_link/0]).
-export([current_data/0]).
-export([dump_data_async/1]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
data_dir() ->
case os:getenv("ERCOIN_HOME") of
false ->
Home = os:getenv("HOME"),
filename:absname_join(Home, ".ercoin");
ErcoinHome ->
ErcoinHome
end.
-spec current_data() -> term().
current_data() ->
DataPath = filename:absname_join(data_dir(), "current_data.bin"),
{ok, DataBin} = file:read_file(DataPath),
binary_to_term(DataBin).
%% @doc Dump data asynchronously.
%% This function converts term to binary and schedules writing it to disc.
-spec dump_data_async(term()) -> ok.
dump_data_async(Data) ->
%% We create a binary here to not copy large term between processes.
DataBin = term_to_binary(Data),
gen_server:cast(?MODULE, {store_data_bin, DataBin}).
%% gen_server.
init([]) ->
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast({store_data_bin, DataBin}, State) ->
DataPath = filename:absname_join(data_dir(), "current_data.bin"),
ok = write_atomically(DataPath, DataBin),
{noreply, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
-spec write_atomically(file:name_all(), binary()) -> ok | {error, term()}.
%% @doc Write file atomically, with obtaining lock.
write_atomically(Filename, Bytes) ->
TmpFilename = [Filename, ".tmp"],
case file:open(TmpFilename, [exclusive]) of
{ok, F} ->
ok = file:write(F, Bytes),
file:rename(TmpFilename, Filename);
{error, eexist} ->
{error, temporary_file_already_exists}
end.
......@@ -23,6 +23,8 @@ init([]) ->
Procs =
[#{id => ercoin,
start => {ercoin_abci, start_link, []}},
#{id => ercoin_persistence,
start => {ercoin_persistence, start_link, []}},
#{id => abci_server,
start => {abci_server_sup, start_link, [{ercoin_abci, 46658}]},
type => supervisor}],
......