├── docs
├── hard_fork.md
├── notes.md
├── warning.md
├── oracle_lifecycle.md
├── from_source.md
├── governance.md
├── compile_mac.md
├── scalability.md
├── accounts_channels.md
├── off_chain_censored_orders.md
├── contributions.md
├── dapp_developer_introduction.md
├── channels_untrusted_slasher.md
├── compile_ubuntu.md
├── dominant_assurance_contract.md
├── channels.md
├── town_crier.md
├── community_roadmap.md
├── using_channels.md
├── new_account.md
├── voluntaryism.md
├── keys.md
├── shares.md
├── api_examples.md
├── oracle.md
├── anarchy.md
├── go_game_predictions.md
├── oracle_simple.md
├── trees.md
├── ethereum_comparison.md
├── insured_crowdfund.md
├── oracle_off_chain.md
├── questions.md
├── threshold_signature_anonimity.md
├── oracle_manipulation.md
├── oracle_design.md
├── oracle_motivations.md
├── oracle_order_book.md
└── todo.md
├── .gitattributes
├── header_test.sh
├── blocks
└── save
│ └── ignore
├── kill_all_erlang.sh
├── data
└── backup_data
│ └── ignore
├── src
├── consensus
│ ├── trees
│ │ ├── README.md
│ │ ├── tree_test.erl
│ │ ├── trees.erl
│ │ ├── burn.erl
│ │ ├── existence.erl
│ │ ├── oracle_bets.erl
│ │ └── oracles.erl
│ ├── testnet_app.erl
│ ├── testnet_hasher.erl
│ ├── txs
│ │ ├── existence_tx.erl
│ │ ├── delete_account_tx.erl
│ │ ├── spend_tx.erl
│ │ ├── repo_tx.erl
│ │ ├── channel_repo_tx.erl
│ │ ├── oracle_unmatched_tx.erl
│ │ ├── create_account_tx.erl
│ │ ├── oracle_shares_tx.erl
│ │ ├── txs.erl
│ │ ├── grow_channel_tx.erl
│ │ ├── oracle_close_tx.erl
│ │ ├── channel_team_close_tx.erl
│ │ ├── channel_slash_tx.erl
│ │ ├── new_channel_tx.erl
│ │ ├── spk.erl
│ │ ├── channel_timeout_tx.erl
│ │ └── oracle_new_tx.erl
│ ├── chain
│ │ ├── backup.erl
│ │ ├── block_absorber.erl
│ │ ├── top.erl
│ │ └── block_hashes.erl
│ ├── mine.erl
│ ├── tx_pool_feeder.erl
│ ├── tester.erl
│ ├── packer.erl
│ ├── tx_pool.erl
│ ├── testnet_sup.erl
│ └── fractions.erl
├── secrets.erl
├── web
│ ├── message_reader.js
│ ├── height.js
│ ├── login.html
│ ├── total_coins.js
│ ├── sync.js
│ ├── id.js
│ ├── pubkey.js
│ ├── halt.js
│ ├── address.js
│ ├── channel_spend.js
│ ├── register.js
│ ├── main.html
│ ├── lightning_spend.js
│ ├── talk.js
│ ├── hash_javascript.html
│ ├── spend.js
│ ├── to_channel.js
│ ├── balance.js
│ ├── create_account.js
│ ├── rpc.js
│ ├── chat.js
│ ├── tester2.js
│ ├── login.js
│ └── tester.js
├── calculate_lines.sh
├── test_key.erl
├── external_web
│ ├── height.js
│ ├── total_coins.js
│ ├── explorer.html
│ ├── signing.js
│ ├── rpc.js
│ └── hexbase64.js
├── compile.erl
├── testnet.app.src
├── empty_gen_server.save
├── networking
│ ├── port.erl
│ ├── main_handler.erl
│ ├── external_handler.erl
│ ├── serve.erl
│ ├── talker.erl
│ └── peers.erl
├── bets
│ └── dice.fs
├── oracle_questions.erl
├── dice.erl
├── timing_experiment.erl
├── channels
│ ├── channel_manager.erl
│ └── channel_notes.md
├── free_constants.erl
├── inbox.erl
└── gas_price.erl
├── oracle_questions
└── ignore
│ └── ignore
├── .gitignore
├── update.sh
├── lightning_test_setup.sh
├── merging_experimental.md
├── clean.sh
├── rebar.config
├── signing_example.txt
├── start.sh
├── share_blocks_test.sh
├── test.sh
├── install.sh
├── spend_test.sh
├── api_examples.md
├── README.md
├── channel_test.sh
└── lightning_test.sh
/docs/hard_fork.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | erlang.mk -diff
--------------------------------------------------------------------------------
/header_test.sh:
--------------------------------------------------------------------------------
1 | curl -i -d '["header", 0]' http://localhost:8040
2 |
--------------------------------------------------------------------------------
/blocks/save/ignore:
--------------------------------------------------------------------------------
1 |
2 | do not delete. otherwise, github deletes this folder.
--------------------------------------------------------------------------------
/docs/notes.md:
--------------------------------------------------------------------------------
1 | key selling points.
2 |
3 | channels + smart contracts
4 | oracles
--------------------------------------------------------------------------------
/kill_all_erlang.sh:
--------------------------------------------------------------------------------
1 | for i in `ps -ef | grep erl | awk '{print $2}'`; do echo $i; kill -9 $i; done
2 |
--------------------------------------------------------------------------------
/data/backup_data/ignore:
--------------------------------------------------------------------------------
1 | the empty /data directory needs to exist.
2 | This file is to make sure that /data is added to the git repository.
--------------------------------------------------------------------------------
/src/consensus/trees/README.md:
--------------------------------------------------------------------------------
1 | The blockchain has several types of merkel trees. [you can get an overview of them all here](docs/trees.md)
--------------------------------------------------------------------------------
/oracle_questions/ignore/ignore:
--------------------------------------------------------------------------------
1 | the empty /data directory needs to exist.
2 | This file is to make sure that /data is added to the git repository.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /deps/
2 | /ebin/
3 | /rel/
4 | *.db
5 | *~
6 | .#*
7 | \#*
8 | rebar
9 | rebar_source
10 | .rebar
11 | erl_crash.dump
12 | keys_backup
--------------------------------------------------------------------------------
/docs/warning.md:
--------------------------------------------------------------------------------
1 |
2 | I think that crowdsales are bad.
3 | I disagree with how Aeternity spends money.
4 | I think the token sale money will be wasted.
5 |
6 | From Zack
--------------------------------------------------------------------------------
/src/secrets.erl:
--------------------------------------------------------------------------------
1 | -module(secrets).
2 | -export([new/0]).
3 | new() ->
4 | Secret = crypto:strong_rand_bytes(12),
5 | Commit = testnet_hasher:doit(Secret),
6 | {Commit, Secret}.
7 |
--------------------------------------------------------------------------------
/src/web/message_reader.js:
--------------------------------------------------------------------------------
1 | setTimeout(get_message, 5000);
2 | function get_message() {
3 | console.log("get message");
4 | local_get(["get_msg", IP, Port]);
5 | setTimeout(get_message, 4000);
6 | }
7 |
--------------------------------------------------------------------------------
/update.sh:
--------------------------------------------------------------------------------
1 | git pull origin master
2 | if [ `uname -s`==Linux ]
3 | then
4 | ./rebar get-deps
5 | ./rebar compile
6 | elif [ `uname -s`==Darwin ]
7 | then
8 | rebar get-deps
9 | rebar compile
10 | else
11 | echo "your computer cannot update this"
12 | fi
13 |
--------------------------------------------------------------------------------
/lightning_test_setup.sh:
--------------------------------------------------------------------------------
1 | #this sets up the other 2 copies of the software so we can run the lightning test.
2 |
3 | rm -r ../FlyingFox2
4 | rm -r ../FlyingFox3
5 | cp -r . ../FlyingFox2
6 | cp -r . ../FlyingFox3
7 | chmod -R 777 ../FlyingFox2
8 | chmod -R 777 ../FlyingFox3
9 |
--------------------------------------------------------------------------------
/docs/oracle_lifecycle.md:
--------------------------------------------------------------------------------
1 | What data does an oracle keep track of?
2 |
3 | The oracle needs to remember the orders in the order book. It needs to remember which one of the three types is currently being stored in the order book. It needs to remember the volume of trades that happen.
--------------------------------------------------------------------------------
/src/calculate_lines.sh:
--------------------------------------------------------------------------------
1 | echo "" > all_txt
2 | cat *.erl >> all_txt
3 | cat */*.erl >> all_txt
4 | cat */*/*.erl >> all_txt
5 | cat */*/*/*.erl >> all_txt
6 | cat */*/*/*/*.erl >> all_txt
7 | cat */*/*/*/*/*.erl >> all_txt
8 | cat */*/*/*/*/*/*.erl >> all_txt
9 | cat */*/*/*/*/*/*/*.erl >> all_txt
10 |
--------------------------------------------------------------------------------
/src/test_key.erl:
--------------------------------------------------------------------------------
1 | -module(test_key).
2 | -export([test/0]).
3 |
4 | test() ->
5 | {NewAddr,NewPub,NewPriv} = testnet_sign:hard_new_key(),
6 | Pub = keys:pubkey(),
7 | Priv = keys:shared_secret(Pub),
8 | {Pub, Priv} = crypto:generate_key(ecdh, crypto:ec_curve(secp256k1)),
9 | success.
10 |
11 |
--------------------------------------------------------------------------------
/merging_experimental.md:
--------------------------------------------------------------------------------
1 | 1) comment out `sh clean.sh` from start.sh
2 | 2) don't assume the password is "abc" in start.sh
3 | 3) in start.sh, automatically add some peers. {46,101,103,165}, 8080
4 | 3) increase difficulty
5 | 4) don't automatically git pull in start.sh
6 | 5) channel_solo_close check slash timer needs to sleep long enough.
--------------------------------------------------------------------------------
/src/web/height.js:
--------------------------------------------------------------------------------
1 | var height = document.createElement("div");
2 | height.id = "height";
3 | document.body.appendChild(height);
4 | function height_f(x) {
5 | var h = document.getElementById("height");
6 | b = (x).toString();
7 | h.innerHTML = "current height: ".concat(b);
8 | }
9 | variable_public_get(["height"], function(x) {height_f(x)});
10 |
--------------------------------------------------------------------------------
/clean.sh:
--------------------------------------------------------------------------------
1 | if [ -e "data/keys.db" ]
2 | then
3 | cp data/keys.db data/keys_backup
4 | fi
5 |
6 | rm data/*.db
7 | rm blocks/*.db
8 |
9 | if [ -d "backup" ]
10 | then
11 | touch backup/temp.db
12 | rm backup/*.db
13 | else
14 | mkdir backup
15 | fi
16 |
17 | #if [ -e "data/keys_backup" ]
18 | #then
19 | cp data/keys_backup data/keys.db
20 | #fi
21 |
--------------------------------------------------------------------------------
/src/external_web/height.js:
--------------------------------------------------------------------------------
1 | var height = document.createElement("div");
2 | height.id = "height";
3 | document.body.appendChild(height);
4 | function height_f(x) {
5 | var h = document.getElementById("height");
6 | b = (x).toString();
7 | h.innerHTML = "current height: ".concat(b);
8 | }
9 | variable_public_get(["height"], function(x) {height_f(x)});
10 |
--------------------------------------------------------------------------------
/src/web/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
--------------------------------------------------------------------------------
/docs/from_source.md:
--------------------------------------------------------------------------------
1 | First, make sure you have erlang installed. Version 18 is prefered, but older versions will probably work. Ubuntu makes erlang available in the package manager
2 | ```
3 | sudo apt-get install erlang
4 | ```
5 |
6 | Here is how to install erlang from source:
7 | download: http://www.erlang.org/download.html
8 | install instructions: http://www.erlang.org/doc/installation_guide/INSTALL.html
--------------------------------------------------------------------------------
/src/compile.erl:
--------------------------------------------------------------------------------
1 | -module(compile).
2 |
3 | -export([doit/2]).
4 | doit(X, Y) when is_list(Y) ->
5 | doit(X, list_to_binary(Y));
6 | doit(F, Front) ->
7 | %for satoshi dice for a pair of users each betting 1000, Front is <<"Amount 1000">>
8 | %and F is "src/bets/dice.fs"
9 | {ok, Text} = file:read_file(F),
10 | compiler_chalang:doit(<>).
11 |
12 |
--------------------------------------------------------------------------------
/src/consensus/trees/tree_test.erl:
--------------------------------------------------------------------------------
1 | -module(tree_test).
2 | -export([test/0]).
3 |
4 | test() ->
5 | S = success,
6 | S = account:test(),
7 | S = channel:test(),
8 | S = existence:test(),
9 | S = oracles:test(),
10 | S = burn:test(),
11 | S = oracle_bets:test(),
12 | S = shares:test(),
13 | S = orders:test(),
14 | S = governance:test(),
15 | S.
16 |
17 |
--------------------------------------------------------------------------------
/docs/governance.md:
--------------------------------------------------------------------------------
1 | the governance system sets variables to maximize for the future difficulty per token created to be as high as possible.
2 |
3 | We have an oracle that asks "should this variable be pushed up or down?". it pays out shares normally, like oracles do.
4 |
5 | How is the block reward set?
6 |
7 | We have a lower limit growth rate of 1% annual. The normal governance mechanism can set it to any higher number.
--------------------------------------------------------------------------------
/docs/compile_mac.md:
--------------------------------------------------------------------------------
1 | First install brew, a package manager for OSX.
2 |
3 | https://brew.sh/
4 |
5 |
6 | Next install erlang and rebar, the package manager for erlang
7 | ```
8 | brew install erlang
9 | brew install rebar
10 | ```
11 |
12 | now use rebar to get and compile Aeternity:
13 |
14 | ```
15 | rebar get
16 | rebar get-deps
17 | rebar compile
18 | ```
19 |
20 | Now you can run your node with ```sh start.sh```
21 |
--------------------------------------------------------------------------------
/src/testnet.app.src:
--------------------------------------------------------------------------------
1 | {application, testnet,
2 | [
3 | {description, "blockchain"},
4 | {vsn, "1"},
5 | {registered, []},
6 | {applications, [
7 | kernel,
8 | stdlib,
9 | ssl,
10 | cowboy,
11 | jiffy
12 | ]},
13 | {modules, []},
14 | {mod, { testnet_app, []}},
15 | {env, [
16 | {error_logger, {file, "log.db"}}
17 | ]}
18 | ]}.
19 |
--------------------------------------------------------------------------------
/src/web/total_coins.js:
--------------------------------------------------------------------------------
1 | var total_coins = document.createElement("div");
2 | total_coins.id = "total_coins";
3 | document.body.appendChild(total_coins);
4 | function total_coins_f(x) {
5 | var h = document.getElementById("total_coins");
6 | console.log(x);
7 | b = (x).toString();
8 | h.innerHTML = "current total coins: ".concat(b);
9 | }
10 | variable_public_get(["total_coins"], function(x) {total_coins_f(x)});
11 |
--------------------------------------------------------------------------------
/src/external_web/total_coins.js:
--------------------------------------------------------------------------------
1 | var total_coins = document.createElement("div");
2 | total_coins.id = "total_coins";
3 | document.body.appendChild(total_coins);
4 | function total_coins_f(x) {
5 | var h = document.getElementById("total_coins");
6 | console.log(x);
7 | b = (x).toString();
8 | h.innerHTML = "current total coins: ".concat(b);
9 | }
10 | variable_public_get(["total_coins"], function(x) {total_coins_f(x)});
11 |
--------------------------------------------------------------------------------
/src/web/sync.js:
--------------------------------------------------------------------------------
1 | sync1();
2 | function sync1() {
3 | document.body.appendChild(document.createElement("br"));
4 | var button = document.createElement("BUTTON");
5 | button.id = "sync_button";
6 | var button_text = document.createTextNode("sync");
7 | button.appendChild(button_text);
8 | button.onclick = function() {
9 | local_get(["sync", IP, Port]);
10 | };
11 | document.body.appendChild(button);
12 | }
13 |
--------------------------------------------------------------------------------
/src/web/id.js:
--------------------------------------------------------------------------------
1 | var id_number = document.createElement("div");
2 | id_number.id = "id_number";
3 | document.body.appendChild(id_number);
4 | function id_number_func() {
5 | variable_get(["id"], id_number1);
6 | }
7 | setTimeout(id_number_func, 2000);
8 |
9 | function id_number1(id) {
10 | var balance = document.getElementById("id_number");
11 | //balance.innerHTML = "id_number ".concat(atob(id));
12 | balance.innerHTML = "id ".concat(id);
13 | }
14 |
--------------------------------------------------------------------------------
/src/web/pubkey.js:
--------------------------------------------------------------------------------
1 | var pubkey = document.createElement("div");
2 | pubkey.id = "pubkey";
3 | document.body.appendChild(pubkey);
4 | function pubkey_func() {
5 | variable_get(["pubkey"], pubkey1);
6 | }
7 | setTimeout(pubkey_func, 2000);
8 |
9 | function pubkey1(id) {
10 | console.log("pubkey 1");
11 | var balance = document.getElementById("pubkey");
12 | balance.innerHTML = "pubkey ".concat(atob(id));
13 | console.log("pubkey 3");
14 | }
15 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | %{erl_opts, [native, {hipe, [verbose]}, warnings_as_errors, debug_info]}.
2 | {deps, [
3 | {trie, "1", {git, "https://github.com/BumblebeeBat/MerkleTrie", {tag, "master"}}},
4 | {chalang, "1", {git, "https://github.com/BumblebeeBat/chalang", {tag, "master"}}},
5 | {jiffy, "0.14.8", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
6 | {cowboy, "1.0.4", {git, "https://github.com/ninenines/cowboy.git", {tag, "1.0.4"}}}
7 | ]}.
8 |
--------------------------------------------------------------------------------
/src/web/halt.js:
--------------------------------------------------------------------------------
1 | halt1();
2 | function halt1() {
3 | document.body.appendChild(document.createElement("br"));
4 | var halt_button = document.createElement("BUTTON");
5 | halt_button.id = "halt_button";
6 | var halt_button_text = document.createTextNode("halt");
7 | halt_button.appendChild(halt_button_text);
8 | halt_button.onclick = function() {
9 | local_get(["halt"]);
10 | };
11 | document.body.appendChild(halt_button);
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/web/address.js:
--------------------------------------------------------------------------------
1 | var addressA = document.createElement("div");
2 | addressA.id = "addressA";
3 | document.body.appendChild(addressA);
4 |
5 | function address_func() {
6 | variable_get(["address"], address1);
7 | }
8 | setTimeout(address_func, 2000);
9 |
10 | function address1(id) {
11 | console.log("address 1");
12 | var balance = document.getElementById("addressA");
13 | balance.innerHTML = "address ".concat(atob(id));
14 | console.log("address 3");
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/src/empty_gen_server.save:
--------------------------------------------------------------------------------
1 | -module().
2 | -behaviour(gen_server).
3 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2]).
4 | init(ok) -> {ok, []}.
5 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
6 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
7 | terminate(_, _) -> io:format("died!"), ok.
8 | handle_info(_, X) -> {noreply, X}.
9 | handle_cast(_, X) -> {noreply, X}.
10 | handle_call(_, _From, X) -> {reply, X, X}.
--------------------------------------------------------------------------------
/docs/scalability.md:
--------------------------------------------------------------------------------
1 | The channels in Aeternity are more scalable for 2 reasons.
2 | 1) Channels move computation off-chain, so we can parallelize the smart contracts. So there is no limit to the amount of smart contract computation that can happen per second. Ethereum channels also have this capability.
3 | 2) The channels don't share state, so we can process all the channel-transactions in a block in parallel. So there is no limit to the volume of channels that can be closed and opened per second. Ethereum channels do not have this capability.
--------------------------------------------------------------------------------
/src/consensus/testnet_app.erl:
--------------------------------------------------------------------------------
1 | -module(testnet_app).
2 |
3 | -behaviour(application).
4 |
5 | %% Application callbacks
6 | -export([start/2, stop/1]).
7 |
8 | %% ===================================================================
9 | %% Application callbacks
10 | %% ===================================================================
11 |
12 | start(_StartType, _StartArgs) ->
13 | ssl:start(),
14 | application:start(inets),
15 | testnet_sup:start_link().
16 |
17 |
18 | stop(_State) ->
19 | ok.
20 |
21 |
22 |
--------------------------------------------------------------------------------
/signing_example.txt:
--------------------------------------------------------------------------------
1 |
2 | pubkeys
3 | BG1gMoweqvCGQLibo/PkAeictba6qGoyYNWcA8dEK29FaI0pktEwte6HKTCV/AT9IBLfc7iZ+fVlAB0YjdC09lM=
4 | BLO4W9Peld0TXMa1IcbQqGDS6O2BOJT8S3GECUMmcCyW46n3iarc6aVW6+DuiGJvKmmEP+Vp7l7i8rlBBnS2osM=
5 |
6 | privkeys
7 | 2kYbRu2TECMJzZy55fxdILBvM5wJM482lKLTRu2e42U=
8 | YJx/2r3S6+wOPhu7HHAt5g39a8LMsAC1m1Ff6Unvjzk=
9 |
10 | signed tx
11 | ["signed",["channel_block",0,1],"TUVVQ0lGUmdLQlZZcTlCdFJZOUdHNHR3b2JYc1hDYzJHSTRvQ0UzKytraExwemN1QWlFQS8zTFZEbDlaVmk3MC90aEx5SlJXYlc0NnRMakRUUldXb2FTdllHcmlnN3c9","TUVVQ0lRQzVsTW9VVWxFbmJ1blNiVGVCZUk0VlRzTDZ3UEVERnBwU1B0dDBwRnZOL1FJZ1drM21mKzhtcXJmTEVDYWZFYkdMTWRjVDFWMVBWSjYwK3U3RVNBckdIMTg9",[-6]]
12 |
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | #if you want to use a different port, then start like this:
2 | # sh start 3666
3 | #git pull origin experimental
4 |
5 | #./rebar get-deps
6 | sh clean.sh #this deletes the database so every time we re-start, we have 0 blocks again. only needed during testing.
7 | #./rebar compile #this line checks if any modules were modified, and recompiles them if they were. only needed during testing.
8 | sh update.sh
9 | #echo "GO TO THIS WEBSITE -------> http://localhost:8041/login.html"
10 | #sleep 1
11 | erl -pa ebin deps/*/ebin/ -eval "application:ensure_all_started(testnet), serve:pw($1), keys:unlock(\"abc\")"
12 | #peers:add({46,101,103,165}, 8080)"
13 |
--------------------------------------------------------------------------------
/src/external_web/explorer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/share_blocks_test.sh:
--------------------------------------------------------------------------------
1 | #First start the two servers:
2 | # sh start.sh 3010
3 | # sh start.sh 3020
4 | # Make sure each server is running from code copied into a different folder. It is important that they each maintain different trie databases, and don't share a trie.
5 |
6 | curl -i -d '["add_peer", [127,0,0,1], 3010]' http://localhost:3021
7 | curl -i -d '["add_peer", [127,0,0,1], 3020]' http://localhost:3011
8 |
9 | curl -i -d '["mine_block"]' http://localhost:3011
10 | curl -i -d '["sync", [127,0,0,1], 3020]' http://localhost:3011
11 |
12 | curl -i -d '["mine_block"]' http://localhost:3011
13 | curl -i -d '["sync", [127,0,0,1], 3010]' http://localhost:3021
14 |
15 |
16 |
--------------------------------------------------------------------------------
/docs/accounts_channels.md:
--------------------------------------------------------------------------------
1 | accounts and channels are things that live on the blockchain.
2 |
3 |
4 | Keeping an account or channel on the blockchain costs a small fee for every block that they exist.
5 |
6 |
7 | An account can store AE tokens, and any number of different kinds of shares. Accounts can store any number of different kinds of oracle bets.
8 |
9 | oracle bets are not transferable. When their oracle settles, they can be transformed into shares with an oracle_shares transaction.
10 | [You can read about shares here](docs/shares.md)
11 |
12 |
13 | Channels can only store AE tokens, but when a channel settles those AE tokens can be split apart into equal amounts of positive and negative forms of the same types of shares.
--------------------------------------------------------------------------------
/docs/off_chain_censored_orders.md:
--------------------------------------------------------------------------------
1 | problem: what if the market maker refused to add any orders to the market based on the price of the order?
2 |
3 | Each market should advertise a certain size trade, which is very small.
4 | All the traders should only make trades in this small size.
5 | If you want to buy shares of type 4 in a market that has 4 types, you should also buy and sell a few shares of the other 3 types to verify that the oracle isn't censoring any trades.
6 |
7 | What if the channel path isn't completed, and someone in between is censoring your trades?
8 | Every step of the channel path should be formed using only the hash of the contract, that way the participants in the path don't know what they agreed to until after they have agreed.
9 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #if you want to use a different port, then start like this:
2 | # sh start 3666
3 |
4 | #sh clean.sh #this deletes the database so every time we re-start, we have 0 blocks again. only needed during testing.
5 | ./rebar get-deps
6 | #sh clean.sh #this deletes the database so every time we re-start, we have 0 blocks again. only needed during testing.
7 | ./rebar compile #this line checks if any modules were modified, and recompiles them if they were. only needed during testing.
8 | #echo "GO TO THIS WEBSITE -------> http://localhost:3011/login.html"
9 | #sleep 1
10 | erl -pa ebin deps/*/ebin/ -eval "application:ensure_all_started(testnet), serve:pw($1), peers:add({46,101,103,165}, 8080), peers:add({127,0,0,1}, 3020), keys:unlock(\"abc\")"
11 |
--------------------------------------------------------------------------------
/docs/contributions.md:
--------------------------------------------------------------------------------
1 | Thank you for your interest in Aeternity.
2 | There are issues on the github that need taking care of: https://github.com/aeternity/testnet/issues
3 | Erlang is a rewarding language http://learnyousomeerlang.com/content
4 | You can help us plenty without learning Erlang, we have lots of javascript that needs to be written.
5 | If you have any questions about Aeternity or blockchain technology, feel free to email me: zack@aeternity.com
6 |
7 | If you type up a proposal for an improvement you can make, and send the proposal to me, then Aeternity can offer you a bitcoin or ether prize for writing the code.
8 |
9 | Simply installing the software and syncing up with the testnet can be a big help, if you find a bug.
10 |
11 | Cheers,
12 | Zack
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | # first install rebar package manager
2 | if [ -e "rebar" ]
3 | then
4 | echo "rebar already installed"
5 | elif [ `uname -s`==Linux ]
6 | then
7 | wget https://raw.githubusercontent.com/wiki/rebar/rebar/rebar && chmod u+x rebar
8 | #elif [ `uname -s`==Darwin ]
9 | #then
10 | # curl https://raw.githubusercontent.com/wiki/rebar/rebar/rebar -o rebar
11 | # chmod u+x rebar
12 |
13 | else
14 | echo "your computer cannot compile this"
15 | fi
16 |
17 | #use rebar to install other dependencies, explained in rebar.config
18 | ./rebar get
19 | ./rebar compile
20 |
21 | sh clean.sh #this deletes the database so every time we re-start, we have 0 blocks again. only needed during testing.
22 |
23 | echo "Successfully compiled Aeternity testnet".
24 |
25 |
--------------------------------------------------------------------------------
/src/consensus/testnet_hasher.erl:
--------------------------------------------------------------------------------
1 | -module(testnet_hasher).
2 | -export([doit/1, bin_to_hex/1, file/1]).
3 |
4 | doit(X) -> hash:doit(X, constants:hash_size()).
5 | bin_to_hex(<<>>) -> "";
6 | bin_to_hex(<>) ->
7 | byte_to_hex(<>) ++ bin_to_hex(B).
8 |
9 | byte_to_hex(<< N1:4, N2:4 >>) ->
10 |
11 | [erlang:integer_to_list(N1, 16), erlang:integer_to_list(N2, 16)].
12 | file(S) ->
13 | {ok, F} = file:open(S, [read, binary]),
14 | O = file(F, <<>>, 0),
15 | ok = file:close(F),
16 | O.
17 | file(F, O, S) ->
18 | B = 10000000,%need at least 10 megabytes of ram open
19 | case file:pread(F, B*S, B) of
20 | eof -> O;
21 | {ok, Out} -> file(F, doit(<>), S+1);
22 | {error, Reason} -> Reason
23 | end.
24 |
--------------------------------------------------------------------------------
/docs/dapp_developer_introduction.md:
--------------------------------------------------------------------------------
1 | Take a look at the dice smart contract for an idea of what state channel applications will look like.
2 | It is a slot machine where you gamble money at 50/50 odds.
3 | https://github.com/BumblebeeBat/plasmodial/blob/master/src/dice.erl
4 | https://github.com/BumblebeeBat/plasmodial/blob/master/src/networking/handler.erl#L104
5 | https://github.com/BumblebeeBat/plasmodial/blob/master/src/easy.erl#L96
6 | https://github.com/BumblebeeBat/plasmodial/blob/master/src/networking/internal_handler.erl#L55
7 |
8 | It is a little spread around now.
9 | We need a way to organize dapps so it is easy to upgrade a basic aeternity node to support whatever dapp you want.
10 |
11 | This is the language for writing smart contracts: https://github.com/BumblebeeBat/chalang
--------------------------------------------------------------------------------
/spend_test.sh:
--------------------------------------------------------------------------------
1 | #First start the two servers:
2 | # sh start.sh 3010
3 | # sh start.sh 3020
4 | # Make sure each server is running from code copied into a different folder. It is important that they each maintain different trie databases, and don't share a trie.
5 |
6 | curl -i -d '["add_peer", [127,0,0,1], 3010]' http://localhost:3021
7 | curl -i -d '["add_peer", [127,0,0,1], 3020]' http://localhost:3011
8 |
9 | curl -i -d '["create_account", "S1lSdUU4ZVFZejRkZFBSRzZW", 100, 27]' http://localhost:3011
10 | curl -i -d '["sync", [127,0,0,1], 3020]' http://localhost:3011
11 | sleep 1
12 | curl -i -d '["spend", 27, 100]' http://localhost:3011
13 | curl -i -d '["sync", [127,0,0,1], 3020]' http://localhost:3011
14 |
15 | curl -i -d '["mine_block"]' http://localhost:3011
16 | curl -i -d '["sync", [127,0,0,1], 3020]' http://localhost:3011
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/web/channel_spend.js:
--------------------------------------------------------------------------------
1 | spend1();
2 | function spend1() {
3 | var amount = document.createElement("INPUT");
4 | amount.setAttribute("type", "text");
5 | var amount_info = document.createElement("h8");
6 | amount_info.innerHTML = "channel spend amount: ";
7 | document.body.appendChild(document.createElement("br"));
8 | document.body.appendChild(amount_info);
9 | document.body.appendChild(amount);
10 |
11 | var spend_button = document.createElement("BUTTON");
12 | spend_button.id = "spend_button";
13 | var spend_button_text = document.createTextNode("channel spend");
14 | spend_button.appendChild(spend_button_text);
15 | spend_button.onclick = function() {
16 | local_get(["channel_spend", IP, Port, parseInt(amount.value, 10)]);
17 | };
18 | document.body.appendChild(spend_button);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/docs/channels_untrusted_slasher.md:
--------------------------------------------------------------------------------
1 | One desired property of channels is that users don't want to have to stay online 24/7 to be ready to provide evidence. They would prefer to pay other people to stay online with the evidence.
2 |
3 | This creates several new attack vectors we will need to consider while designing the channels.
4 | The old channels looked like this:
5 | new_channel_tx, grow_channel, grow_channel, channel_team_close.
6 |
7 | the grow_channels have to each increase the amount of money controlled by the channel.
8 |
9 | or like this:
10 | new_channel_tx, grow_channel, channel_solo_close, channel_slash
11 | or like this:
12 | new_channel, channel_solo_close, channel_timeout
13 |
14 | The new channels look like this:
15 | new_channel_tx, grow_channel, grow_channel, channel_solo_close, channel_slash, channel_slash, channel_slash, channel_timeout
16 |
17 |
--------------------------------------------------------------------------------
/src/web/register.js:
--------------------------------------------------------------------------------
1 | //register_doit();
2 | local_get(["sync", IP, Port]);
3 | variable_get(["channel_keys"], function(x) {register_doit(x)});
4 | function register_doit(x) {
5 | if (typeof x == 'undefined'){
6 | setTimeout(function() {variable_get(["channel_keys"], function(x) {register_doit(x)});}, 1000);
7 | } else if ( ( x.length == 1 ) && ( x.pop() == -6 ) ) {
8 | variable_get(["id"], new_channel);
9 | } else {
10 | console.log("did not work, x was");
11 | console.log(x);
12 | }
13 | }
14 | function new_channel(id) {
15 | if (id == -1) {variable_get(["id"], new_channel);}
16 | else {
17 | variable_get(["balance"], function(x) {new_channel2(id,x);})
18 | }
19 | }
20 | function new_channel2(id, bal) {
21 | C = Math.min(Math.floor(bal/2), 1000000);
22 | local_get(["new_channel", IP, Port, C, Math.floor(C/1.1), 50]);
23 | }
24 |
--------------------------------------------------------------------------------
/src/networking/port.erl:
--------------------------------------------------------------------------------
1 | -module(port).
2 | -behaviour(gen_server).
3 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2, check/0,change/1]).
4 | init(ok) -> {ok, constants:default_port()}.
5 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
6 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
7 | terminate(_, _) -> io:format("died!"), ok.
8 | handle_info(_, X) -> {noreply, X}.
9 | handle_cast({change, X}, _) ->
10 | {noreply, X}.
11 | handle_call(check, _From, X) -> {reply, X, X}.
12 |
13 | check() -> gen_server:call(?MODULE, check).
14 | change(X) ->
15 | io:fwrite("changing port!!!\n"),
16 | io:fwrite("changing port!!!\n"),
17 | if
18 | not is_integer(X) -> change(list_to_integer(X));
19 | true ->
20 | true = X > 0,
21 | gen_server:cast(?MODULE, {change, X}),
22 | X = check()
23 | end.
24 |
--------------------------------------------------------------------------------
/src/bets/dice.fs:
--------------------------------------------------------------------------------
1 |
2 | macro Draw int 1 int 0 int 0 crash ;
3 |
4 | : or_die not if Draw else then ;
5 |
6 | macro reveal ( Reveal Commit -- bool )
7 | swap dup tuck hash == or_die call drop drop ;
8 | % If a secret is improperly revealed, the contract defaults to case 0. a draw.
9 |
10 |
11 | % macro Commit1 int 1 hash ;
12 | % macro Commit2 int 2 hash ;
13 |
14 | macro Win1 int 0 Amount ;
15 | macro Win2 int 1 Amount ;
16 |
17 | macro player1revealed Commit1 reveal drop int 2 Win1 ;
18 | macro player2revealed Commit2 reveal drop int 2 Win2 ;
19 | : bothRevealed Commit2 reveal swap
20 | Commit1 reveal bxor int 2 rem
21 | int 3 swap
22 | if Win1 else Win2 then ;
23 |
24 | %syntax for case statements.
25 | macro -> == if drop drop ;
26 | macro -- crash else then drop ;
27 |
28 | macro main
29 | int 1 -> player1revealed --
30 | int 2 -> player2revealed --
31 | int 3 -> bothRevealed call --
32 | drop Draw ;
33 |
34 | main
--------------------------------------------------------------------------------
/src/networking/main_handler.erl:
--------------------------------------------------------------------------------
1 | -module(main_handler).
2 |
3 | -export([init/3, handle/2, terminate/3]).
4 | %example of talking to this handler:
5 | %httpc:request(post, {"http://127.0.0.1:3011/", [], "application/octet-stream", "echo"}, [], []).
6 | %curl -i -d '[-6,"test"]' http://localhost:3011
7 | handle(Req, _) ->
8 | {F, _} = cowboy_req:path(Req),
9 | File = << <<"src/web">>/binary, F/binary>>,
10 | {ok, _Data, _} = cowboy_req:body(Req),
11 | Headers = [{<<"content-type">>, <<"text/html">>},
12 | {<<"Access-Control-Allow-Origin">>, <<"*">>}],
13 | Text = read_file(File),
14 | {ok, Req2} = cowboy_req:reply(200, Headers, Text, Req),
15 | {ok, Req2, File}.
16 | read_file(F) ->
17 | {ok, File } = file:open(F, [read, binary, raw]),
18 | {ok, O} =file:pread(File, 0, filelib:file_size(F)),
19 | file:close(File),
20 | O.
21 | init(_Type, Req, _Opts) -> {ok, Req, []}.
22 | terminate(_Reason, _Req, _State) -> ok.
23 |
--------------------------------------------------------------------------------
/src/networking/external_handler.erl:
--------------------------------------------------------------------------------
1 | -module(external_handler).
2 |
3 | -export([init/3, handle/2, terminate/3]).
4 | %example of talking to this handler:
5 | %httpc:request(post, {"http://127.0.0.1:3011/", [], "application/octet-stream", "echo"}, [], []).
6 | %curl -i -d '[-6,"test"]' http://localhost:3011
7 | handle(Req, _) ->
8 | {F, _} = cowboy_req:path(Req),
9 | File = << <<"src/external_web">>/binary, F/binary>>,
10 | {ok, _Data, _} = cowboy_req:body(Req),
11 | Headers = [{<<"content-type">>, <<"text/html">>},
12 | {<<"Access-Control-Allow-Origin">>, <<"*">>}],
13 | Text = read_file(File),
14 | {ok, Req2} = cowboy_req:reply(200, Headers, Text, Req),
15 | {ok, Req2, File}.
16 | read_file(F) ->
17 | {ok, File } = file:open(F, [read, binary, raw]),
18 | {ok, O} =file:pread(File, 0, filelib:file_size(F)),
19 | file:close(File),
20 | O.
21 | init(_Type, Req, _Opts) -> {ok, Req, []}.
22 | terminate(_Reason, _Req, _State) -> ok.
23 |
--------------------------------------------------------------------------------
/src/consensus/txs/existence_tx.erl:
--------------------------------------------------------------------------------
1 | -module(existence_tx).
2 | -export([doit/3, make/4]).
3 | -record(ex, {from, nonce = 0, fee = 0, commit = 0}).
4 |
5 | make(From, Fee, C, AccountRoot) ->
6 | {_, Acc, Proof} = account:get(From, AccountRoot),
7 | Nonce = account:nonce(Acc) + 1,
8 | %C = existence:new(testnet_hasher:doit(Data)),
9 | Tx = #ex{from = From,fee=Fee,nonce=Nonce,commit=C},
10 | {Tx, [Proof]}.
11 | doit(Tx, Trees, NewHeight) ->
12 | Accounts = trees:accounts(Trees),
13 | Commits = trees:existence(Trees),
14 | From = Tx#ex.from,
15 | C = Tx#ex.commit,
16 | {_, empty, _} =existence:get(existence:hash(C),Commits),
17 | NewCommits = existence:write(C, Commits),
18 | Acc = account:update(From, Accounts, -Tx#ex.fee, Tx#ex.nonce, NewHeight),
19 | NewAccounts = account:write(Accounts, Acc),
20 | Trees2 = trees:update_accounts(Trees, NewAccounts),
21 | trees:update_existence(Trees2, NewCommits).
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/consensus/chain/backup.erl:
--------------------------------------------------------------------------------
1 | -module(backup).
2 | -export([hash/0, backup/0, backup_files/0, read/2, read_size/1, files/0]).
3 | files() -> [constants:blocks(), constants:block_pointers(), constants:accounts(), constants:all_secrets(), constants:d_accounts(), constants:channels(), constants:d_channels(), constants:entropy()].
4 |
5 | backup_files() -> tl(tl(files())).
6 | hash() -> hash(backup_files(), []).
7 | hash([], X) -> testnet_hasher:doit(X);
8 | hash([F|T], X) -> hash(T, [hash:file(F)|X]).
9 | -define(backup, "backup/").
10 | backup() -> backup(backup_files()).
11 | backup([]) -> ok;
12 | backup([F|T]) ->
13 | file:copy(F, ?backup++F),
14 | backup(T).
15 |
16 | -define(word, constants:word_size()).
17 | read_size(File) ->
18 | filelib:file_size(?backup++File) div ?word.
19 | read(File, N) ->
20 | {ok, RFile } = file:open(?backup++File, [read, binary, raw]),
21 | {ok, Out} = file:pread(RFile, N*?word, ?word),
22 | file:close(RFile),
23 | Out.
24 |
25 |
--------------------------------------------------------------------------------
/src/oracle_questions.erl:
--------------------------------------------------------------------------------
1 | -module(oracle_questions).
2 | -export([save/1, read/1, all/0]).
3 | -define(LOC, constants:oracle_questions()).
4 | all() ->
5 | {Trees, _, _} = tx_pool:data(),
6 | Oracles = trees:oracles(Trees),
7 | Oracles2 = trie:get_all(Oracles, oracles),
8 | all2(Oracles2).
9 | all2([]) -> ok;
10 | all2([Leaf|T]) ->
11 | Oracle = leaf:value(Leaf),
12 | Q = oracles:question(Oracle),
13 | [Q|all2(T)].
14 | binary_to_file(B) ->
15 | C = base58:binary_to_base58(B),
16 | "oracle_questions/"++C++".db".
17 | read(Hash) ->
18 | BF = binary_to_file(Hash),
19 | Z = db:read(BF),
20 | case Z of
21 | [] -> empty;
22 | A -> zlib:uncompress(A)
23 | end.
24 | save(Binary) ->
25 | Z = zlib:compress(Binary),
26 | Binary = zlib:uncompress(Z),%sanity check, not important for long-term.
27 | %Hash = testnet_hasher:doit(BlockPlus),
28 | Hash = block:hash(Binary),
29 | BF = binary_to_file(Hash),
30 | db:save(BF, Z).
31 |
32 |
--------------------------------------------------------------------------------
/docs/compile_ubuntu.md:
--------------------------------------------------------------------------------
1 | Make sure that that you are running Ubuntu 16 or later:
2 |
3 | (earlier versions of ubuntu requires manually installing the latest version of erlang, because the package manager installs an old version)
4 |
5 | Use this command to check your version number
6 | ```
7 | lsb_release -a
8 | ```
9 |
10 | Make sure that your system is up-to-date:
11 | ```
12 | sudo apt-get update
13 | ```
14 | and
15 | ```
16 | sudo apt-get upgrade
17 | ```
18 |
19 | For Ubuntu, install following dependencies:
20 |
21 | ```
22 | sudo apt-get install erlang libncurses5-dev libssl-dev unixodbc-dev g++ git erlang-base-hipe
23 | ```
24 |
25 | Next, download Aeternity Testnet. Optionally you can run next steps with a non-root user, for better security.
26 |
27 | ```
28 | git clone https://github.com/aeternity/testnet.git
29 | ```
30 | Now you can go into the directory, and compile the aeternity testnet.
31 |
32 | ```
33 | cd testnet/
34 | sh install.sh
35 | ```
36 |
37 | Now you can run your node. with ```sh start.sh```
--------------------------------------------------------------------------------
/src/dice.erl:
--------------------------------------------------------------------------------
1 | -module(dice).
2 | -export([make_ss/2, resolve_ss/3]).
3 |
4 | make_ss(SPK, Secret) ->
5 | Acc1 = spk:acc1(SPK),
6 | Acc2 = spk:acc2(SPK),
7 | MyID = keys:id(),
8 | N = case MyID of
9 | Acc1 -> 1;
10 | Acc2 -> 2;
11 | X -> X = Acc1
12 | end,
13 | S = size(Secret),
14 | compiler_chalang:doit("binary " ++ integer_to_list(S) ++ " " ++ binary_to_list(base64:encode(Secret)) ++ " int " ++ integer_to_list(N) ++ " ").
15 |
16 | resolve_ss(SPK, Secret, TheirSecret) ->
17 | Acc1 = spk:acc1(SPK),
18 | Acc2 = spk:acc2(SPK),
19 | MyID = keys:id(),
20 | {S1, S2} = case MyID of
21 | Acc1 -> {Secret, TheirSecret};
22 | Acc2 -> {TheirSecret, Secret};
23 | X -> X = Acc1
24 | end,
25 | T1 = integer_to_list(size(S1)),
26 | T2 = integer_to_list(size(S2)),
27 | S1s = binary_to_list(base64:encode(S1)),
28 | S2s = binary_to_list(base64:encode(S2)),
29 | S = " binary " ++ T1 ++ " " ++ S1s ++ " binary " ++ T2 ++ " "++ S2s ++ " int 3 ",
30 | compiler_chalang:doit(S).
31 |
--------------------------------------------------------------------------------
/src/networking/serve.erl:
--------------------------------------------------------------------------------
1 | -module(serve).
2 | -export([start/0, start/1, pw/0, pw/1]).
3 | start() -> start(port:check()).
4 | start(Port) ->
5 | io:fwrite("start server\n"),
6 |
7 | D_internal = [
8 | {'_', [
9 | {"/:file", main_handler, []},
10 | {"/", internal_handler, []}
11 | ]}
12 | ],
13 | D = [
14 | {'_', [
15 | {"/:file", external_handler, []},
16 | {"/", handler, []}
17 | ]}
18 | ],
19 | Dispatch_internal = cowboy_router:compile(D_internal),
20 | Dispatch = cowboy_router:compile(D),
21 | K_internal = [
22 | {env, [{dispatch, Dispatch_internal}]}
23 | ],
24 | K = [
25 | {env, [{dispatch, Dispatch}]}
26 | ],
27 | %{ok, _} = cowboy:start_http(http_internal, 100, [{ip, {127,0,0,1}},{port, Port+1}], K_internal),
28 | {ok, _} = cowboy:start_http(http_internal, 100, [{ip, {0,0,0,0}},{port, Port+1}], K_internal),
29 | {ok, _} = cowboy:start_http(http, 100, [{ip, {0,0,0,0}},{port, Port}], K).
30 |
31 | pw() -> start(port:check()).
32 | pw(X) ->
33 | port:change(X),
34 | pw().
35 |
36 |
--------------------------------------------------------------------------------
/docs/dominant_assurance_contract.md:
--------------------------------------------------------------------------------
1 | Tragedy of the commons is when people pay less than how much the public good would benefit them.
2 |
3 | For example, lets say Bob owns a store that will be $10,000 more valuable if a bridge gets built.
4 | There are 4 possibilities:
5 | 1) Bob doesn't contribute to the bridge, and the bridge is built. Bob is $10,000 richer.
6 | 2) Bob doesn't contribute to the bridge, and the bridge is not built. Bob gets $0.
7 | 3) Bob contributes $9000 and the bridge is built. Bob is $1000 richer.
8 | 4) Bob contributes $9000 and the bridge is not built. Bob gets his money back plus interest, he is $1000 richer.
9 | 5) Bob contributes $5000 and the bridge is built. Bob is $5000 richer.
10 | 6) Bob contributes $5000 and the bridge is not built. Bob is $500 richer.
11 |
12 | Option 1 is the most profitable, but it is risky because option 2 might happen.
13 | If Bob donates $9000, then he knows he will get $1000 profit either way. His risk is completely gone.
14 | I expect most people to do a mixed strategy that only partially gets rid of their risk, like paying only $5000.
--------------------------------------------------------------------------------
/src/consensus/txs/delete_account_tx.erl:
--------------------------------------------------------------------------------
1 | -module(delete_account_tx).
2 | -export([doit/3, make/4]).
3 | -record(da, {from = 0, nonce = 0, fee = 0, to = 0}).
4 | make(To, ID, Fee, Accounts) ->
5 | {_, Facc, Fproof} = account:get(ID, Accounts),
6 | {_, Tacc, Tproof} = account:get(To, Accounts),
7 | false = Tacc == empty,
8 | Tx = #da{from = ID, nonce = account:nonce(Facc) + 1,
9 | to = To, fee = Fee},
10 | {Tx, [Fproof, Tproof]}.
11 | doit(Tx, Trees, NewHeight) ->
12 | Accounts = trees:accounts(Trees),
13 | From = Tx#da.from,
14 | To = Tx#da.to,
15 | false = From == To,
16 | {_, Facc, _} = account:get(From, Accounts),
17 | A = account:balance(Facc),
18 | Amount = A-Tx#da.fee,
19 | true = Amount > 0,
20 | Tacc = account:update(To, Accounts, Amount+constants:delete_account_reward(), none, NewHeight),
21 | _ = account:update(From, Accounts, 0, Tx#da.nonce, NewHeight),
22 | Accounts2 = account:write(Accounts, Tacc),
23 | NewAccounts = account:delete(From, Accounts2),
24 | trees:update_accounts(Trees, NewAccounts).
25 |
26 |
--------------------------------------------------------------------------------
/docs/channels.md:
--------------------------------------------------------------------------------
1 | There are 5 transaction types:
2 | 2 that need to be signed by both: channel_new, channel_team_close,
3 | and 3 that are signed by one: channel_solo_close, channel_slash, channel_timeout
4 |
5 | If your partner disappears, and you want to close the channel, then you first publish a solo_close, which gives the current state of the channel, which is defined by a turing complete contract that outputs a nonce.
6 | The contract is split into 2 parts, the scriptpubkey part, which both participants signed, and the scripsig part, which only one signed.
7 |
8 | If your partner sees you publish a solo_close that doesn't output the highest nonce possible, then they can do a channel_slash transaction that outputs a higher nonce, and this becomes the final state that the channel gets closed at.
9 |
10 | If your partner doesn't slash you, then eventually you can do a channel_timeout transaction, which closes the channel at the state from the solo_close.
11 |
12 | The data that gets recorded on-chain for each channel is:
13 | How much money is in the channel. The 2 accounts ids that control the channel.
--------------------------------------------------------------------------------
/src/consensus/txs/spend_tx.erl:
--------------------------------------------------------------------------------
1 | -module(spend_tx).
2 | -export([doit/3, make/6]).
3 | -record(spend, {from = 0, nonce = 0, fee = 0, to = 0, amount = 0, shares = []}).
4 | make(To, Amount, Fee, Id, Accounts, Shares) ->
5 | {_, Acc, Proof} = account:get(Id, Accounts),
6 | {_, _Acc2, Proof2} = account:get(To, Accounts),
7 | Tx = #spend{from = Id, nonce = account:nonce(Acc) + 1, to = To, amount = Amount, shares = Shares, fee = Fee},
8 | {Tx, [Proof, Proof2]}.
9 | doit(Tx, Trees, NewHeight) ->
10 | Accounts = trees:accounts(Trees),
11 | From = Tx#spend.from,
12 | To = Tx#spend.to,
13 | false = From == To,
14 | A = Tx#spend.amount,
15 | Facc = account:update(From, Accounts, -A-Tx#spend.fee, Tx#spend.nonce, NewHeight),
16 | Facc2 = account:send_shares(Facc, Tx#spend.shares, NewHeight),
17 | Tacc = account:update(To, Accounts, A, none, NewHeight),
18 | Tacc2 = account:receive_shares(Tacc, Tx#spend.shares, NewHeight),
19 | Accounts2 = account:write(Accounts, Facc2),
20 | NewAccounts = account:write(Accounts2, Tacc2),
21 | trees:update_accounts(Trees, NewAccounts).
22 |
--------------------------------------------------------------------------------
/docs/town_crier.md:
--------------------------------------------------------------------------------
1 | Oracles are an crypto-economic problem. TC is a cryptographic solution that ignores the economic reality of oracles.
2 |
3 | Some problems with TC:
4 | 1) we are trusting intel to delete the private key. If intel does not, then intel can make the TC say anything.
5 | 2) we are trusting that it is actually impossible to extract the private key from the hardware, which is doubtful. To the best of my knowledge, it is always possible to find a piece of copper where if you measure the charge during signatures you can extract the private key.
6 | 3) we are trusting intel to honestly tell us the public keys that correspond with the private keys embedded in their hardware.
7 | 4) we are trusting the server operator not to man-in-the-middle attack his own server. For example, if SGX wants to access the price on website B, the server operator could man in the middle between his server and website B, so he could edit the data from website B to trick the server.
8 | 5) we are trusting the website operators not to lie to the SGX server.
9 | It is possible to make a website that says "true" to most people, but says "false" to one specific customer.
10 |
--------------------------------------------------------------------------------
/docs/community_roadmap.md:
--------------------------------------------------------------------------------
1 | This is a roadmap that is less precise than my todo list, and more exciting for the community to look at.
2 |
3 | * dots mark what is completed.
4 | * circles are what Aeternity is working on now.
5 |
6 | Core protocol
7 |
8 | * merkel trees
9 | * Virtual Machine for smart contracts
10 | * accounts
11 | * channels
12 | * account key holder
13 | * database for channel data
14 | * lightning payments
15 | * API
16 | - oracles
17 | - governance
18 | - fully encrypted api
19 |
20 | javascript SPV Wallet
21 |
22 | * library to communicate with node API
23 | * compatible hash function
24 | - merkel proofs
25 | - account key holder
26 | - database for channel data
27 | - gui
28 |
29 | block explorer
30 |
31 | * api to communicate with nodes
32 | - website
33 | - blocks
34 | - transactions
35 | - accounts
36 | - channels
37 | - oracles
38 |
39 | Trustless Markets
40 |
41 | * example smart contracts
42 | - smart contract for markets with batches at single price.
43 | - upgrade api to support market contracts.
44 | - website to display markets, integrated with javascript SPV wallets.
45 |
46 |
47 | Security Audit
48 | * none
49 | - Core
50 | - SPV javascript wallet
51 | - trustless markets
--------------------------------------------------------------------------------
/docs/using_channels.md:
--------------------------------------------------------------------------------
1 | making a channel with the server:
2 |
3 | ```
4 | easy:new_channel(Balance, ReceivingLimit).
5 | ```
6 |
7 | Balance is how much of your money you put into the channel.
8 | ReceivingLimit is how much money the server puts into the channel.
9 | This is the maximum amount of money that can be sent to you until the channel runs out of space. ReceivingLimit needs to be bigger than Balance, or the server wont let you make the channel.
10 | Fee is the transaction fee, so that this transaction will be included into a block soon.
11 |
12 |
13 | checking your balance in the channel:
14 | ```
15 | easy:channel_balance().
16 | ```
17 |
18 |
19 | gambling with the server:
20 | ```
21 | easy:dice(Amount).
22 | ```
23 |
24 |
25 | When you want to close the channel and get your money out:
26 | ```
27 | easy:close_channel().
28 | ```
29 | You need to sync with the network to see if your channel is closed.
30 | ```
31 | easy:sync().
32 | ```
33 |
34 | If your channel partner disappears, or breaks, you can still get your money without his help. Start with a solo-close transaction, then wait over 100 blocks, then do a channel timeout transaction
35 | ```
36 | easy:solo_close_channel().
37 | ```
38 | ```
39 | easy:channel_timeout().
40 | ```
--------------------------------------------------------------------------------
/src/consensus/txs/repo_tx.erl:
--------------------------------------------------------------------------------
1 | %similar to slasher.
2 | %Each account needs a minimum amount of money.
3 | %If you can provide evidence that someone doesn't have enough money left to validate, then you can get a reward for deleting their account.
4 |
5 | -module(repo_tx).
6 | -export([doit/3, make/4]).
7 | -record(repo, {from = 0, nonce = 0, fee = 0, target = 0}).
8 | make(Target, Fee, Id, Accounts) ->
9 | {_, A, Proof} = account:get(Id, Accounts),
10 | {_, _, Proof2} = account:get(Target, Accounts),
11 | %NB = account:now_balance(T, 0, Height),
12 | %true = NB < 0,
13 | Nonce = account:nonce(A),
14 | Tx = #repo{from = Id, nonce = Nonce + 1, target = Target, fee = Fee},
15 | {Tx, [Proof, Proof2]}.
16 | doit(Tx, Trees, NewHeight) ->
17 | Accounts = trees:accounts(Trees),
18 | From = Tx#repo.from,
19 | To = Tx#repo.target,
20 | false = From == To,
21 | {_, Tacc, _} = account:get(To, Accounts),
22 | NB = account:now_balance(Tacc, 0, NewHeight),
23 | true = NB =< 0,
24 | Facc = account:update(From, Accounts, constants:delete_account_reward(), Tx#repo.nonce, NewHeight),
25 | Accounts2 = account:write(Accounts, Facc),
26 | Accounts3 = account:delete(To, Accounts2),
27 | trees:update_accounts(Trees, Accounts3).
28 |
--------------------------------------------------------------------------------
/docs/new_account.md:
--------------------------------------------------------------------------------
1 | When you launch a full node, at first you don't have any aeons.
2 |
3 | Your address is automatically generated for you, and secured with the password "". The empty string is the default password.
4 |
5 | To generate an address with a better password, type following command:
6 |
7 | ```
8 | keys:new("DONT_USE_THIS_PASSWORD").
9 | ```
10 |
11 | Make sure to have replaced "DONT_USE_THIS_PASSWORD" with a better password.
12 | This password is used to encrypt the private key on your computer.
13 |
14 | If you want to change the password, do this:
15 | ```
16 | keys:change_password("Old_PASSWORD", "New Password").
17 | ```
18 |
19 | replace "Old_PASSWORD" with the password you are replacing.
20 | replace "New Password" with the new password you want.
21 |
22 |
23 | Before using your node, make sure your _account_ is unlocked:
24 |
25 | ```
26 | keys:unlock("DONT_USE_THIS_PASSWORD").
27 | ```
28 |
29 |
30 | One way to get aeons is to share your _address_ with someone who has already aeons, so that they send some to you.
31 |
32 |
33 | Generate your address:
34 | ```
35 | keys:address().
36 | ```
37 |
38 | To spend money to a brand new _account_, one needs to make a transaction:
39 | ```
40 | easy:create_account(Address, AmountOfMoney, Fee, NewID).
41 | ```
42 |
--------------------------------------------------------------------------------
/docs/voluntaryism.md:
--------------------------------------------------------------------------------
1 | You would like to learn about dominant assurance contracts. They are a way to provide funding for public goods. They solve the free rider problem. They don't involve taxation or the use of force, they are voluntary to participate in. The Nash equilibrium strategy is to give as much money to the dominant assurance contract as the public good can give you.
2 |
3 | So if we are using the dominant assurance contract to build a bridge over a river, and the bridge will make your restaurant worth $10,000 more, then the Nash equilibrium strategy for you is to give $10,000 to the contract.
4 |
5 | Another way this technology can help is by revealing the lies the state tells us.
6 | We can use prediction markets to get accurate predictions about the future.
7 | We can know the correlation between events like, who gets elected, and what the GDP will be.
8 | So we can know when the government makes promises it can't keep.
9 |
10 | This technology can be used to insure against the state, which is equivalent to a bet that the government will grow.
11 | This gives government people the option to make a bet that government will shrink, and since they have the power to make the government shrink, they will.
12 |
13 | It is a peaceful, voluntary, incentive compatible way to dismantle the state.
--------------------------------------------------------------------------------
/src/consensus/mine.erl:
--------------------------------------------------------------------------------
1 | -module(mine).
2 | -behaviour(gen_server).
3 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,
4 | handle_info/2,init/1,terminate/2,
5 | start/0,stop/0,is_on/0]).
6 | init(ok) -> {ok, stop}.
7 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
8 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
9 | terminate(_, _) -> io:format("died!"), ok.
10 | handle_info(_, X) -> {noreply, X}.
11 | handle_cast(mine, go) ->
12 | spawn(fun() ->
13 | block:mine_blocks(10, 50000),
14 | mine() end),
15 | spawn(fun() -> easy:sync() end),
16 | {noreply, go};
17 | handle_cast(start, _) ->
18 | Cores = block:guess_number_of_cpu_cores(),
19 | io:fwrite("start mining with "),
20 | io:fwrite(integer_to_list(Cores)),
21 | io:fwrite(" cores.\n"),
22 | {noreply, go};
23 | handle_cast(stop, _) ->
24 | {noreply, stop};
25 | handle_cast(_, X) -> {noreply, X}.
26 | handle_call(status, _From, X) -> {reply, X, X};
27 | handle_call(_, _From, X) -> {reply, X, X}.
28 |
29 | start() ->
30 | gen_server:cast(?MODULE, start),
31 | timer:sleep(100),
32 | mine().
33 | mine() ->
34 | gen_server:cast(?MODULE, mine).
35 |
36 | stop() -> gen_server:cast(?MODULE, stop).
37 | is_on() ->
38 | gen_server:call(?MODULE, status).
39 |
--------------------------------------------------------------------------------
/src/consensus/tx_pool_feeder.erl:
--------------------------------------------------------------------------------
1 | -module(tx_pool_feeder).
2 | -behaviour(gen_server).
3 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2, absorb/1]).
4 | init(ok) -> {ok, []}.
5 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
6 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
7 | terminate(_, _) -> io:format("died!"), ok.
8 | handle_info(_, X) -> {noreply, X}.
9 | handle_cast({absorb, SignedTx}, X) ->
10 | Tx = testnet_sign:data(SignedTx),
11 | Fee = element(4, Tx),
12 | true = Fee > free_constants:minimum_tx_fee(),
13 | {Trees, Height, Txs} = tx_pool:data(),
14 | Accounts = trees:accounts(Trees),
15 | true = testnet_sign:verify(SignedTx, Accounts),
16 | B = is_in(SignedTx, Txs), %this is very ugly. once we have a proper CLI we can get rid of this crutch.
17 | if
18 | B -> ok;
19 | true ->
20 | NewTrees =
21 | txs:digest([SignedTx], Trees, Height+1),
22 | tx_pool:absorb_tx(NewTrees, SignedTx)
23 | end,
24 | {noreply, X};
25 | handle_cast(_, X) -> {noreply, X}.
26 | handle_call(_, _From, X) -> {reply, X, X}.
27 |
28 | absorb(SignedTx) ->
29 | gen_server:cast(?MODULE, {absorb, SignedTx}).
30 |
31 | is_in(A, [A|_]) -> true;
32 | is_in(_, []) -> false;
33 | is_in(A, [_|T]) -> is_in(A, T).
34 |
--------------------------------------------------------------------------------
/src/consensus/txs/channel_repo_tx.erl:
--------------------------------------------------------------------------------
1 | %If a channel has less than 0 money, then anyone can delete it for a small reward.
2 |
3 | -module(channel_repo_tx).
4 | -export([make/5,doit/3]).
5 | -record(cr, {from = 0, nonce = 0, fee = 0, id = 0}).
6 |
7 | make(From, ID, Fee, Accounts, Channels) ->
8 | {_, Acc, Proof} = account:get(From, Accounts),
9 | {_, _, CProof} = channel:get(ID, Channels),
10 | N = account:nonce(Acc) + 1,
11 | Tx = #cr{from = From, nonce = N, fee = Fee, id = ID},
12 | {Tx, [Proof, CProof]}.
13 |
14 | doit(Tx, Trees, NewHeight) ->
15 | Channels = trees:channels(Trees),
16 | Accounts = trees:accounts(Trees),
17 | From = Tx#cr.from,
18 | CID = Tx#cr.id,
19 | A = constants:delete_channel_reward(),
20 | Facc = account:update(From, Accounts, A, Tx#cr.nonce, NewHeight),
21 | {_, Channel, _} = channel:get(CID, Channels),
22 | false = channel:closed(Channel),
23 | B = channel:bal1(Channel) + channel:bal2(Channel),
24 | DH = NewHeight - channel:last_modified(Channel),
25 | Rent = constants:channel_rent() * DH,
26 | true = B =< Rent,
27 | NewAccounts = account:write(Accounts, Facc),
28 | NewChannels = channel:delete(CID, Channels),
29 | Trees2 = trees:update_channels(Trees, NewChannels),
30 | trees:update_accounts(Trees2, NewAccounts).
31 |
32 |
--------------------------------------------------------------------------------
/src/web/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/web/lightning_spend.js:
--------------------------------------------------------------------------------
1 | //local_get(["lightning_spend", [127,0,0,1], 3020, partner, amount]);
2 |
3 | spend1();
4 | function spend1() {
5 | var spend_address = document.createElement("INPUT");
6 | spend_address.setAttribute("type", "text");
7 | var input_info = document.createElement("h8");
8 | input_info.innerHTML = "spend to: ";
9 | document.body.appendChild(document.createElement("br"));
10 | document.body.appendChild(input_info);
11 | document.body.appendChild(spend_address);
12 |
13 |
14 | var amount = document.createElement("INPUT");
15 | amount.setAttribute("type", "text");
16 | var amount_info = document.createElement("h8");
17 | amount_info.innerHTML = "channel spend amount: ";
18 | document.body.appendChild(amount_info);
19 | document.body.appendChild(amount);
20 |
21 | var spend_button = document.createElement("BUTTON");
22 | spend_button.id = "lightning_button";
23 | var spend_button_text = document.createTextNode("lightning spend");
24 | spend_button.appendChild(spend_button_text);
25 | spend_button.onclick = function() {
26 | var B = parseInt(amount.value, 10);
27 | var C = B + (B%2);
28 | local_get(["lightning_spend", IP, Port, parseInt(spend_address.value, 10), C]);
29 | };
30 | document.body.appendChild(spend_button);
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/external_web/signing.js:
--------------------------------------------------------------------------------
1 |
2 | //pubkeys
3 | pub1 = atob("BG1gMoweqvCGQLibo/PkAeictba6qGoyYNWcA8dEK29FaI0pktEwte6HKTCV/AT9IBLfc7iZ+fVlAB0YjdC09lM=");
4 | pub2 = atob("BLO4W9Peld0TXMa1IcbQqGDS6O2BOJT8S3GECUMmcCyW46n3iarc6aVW6+DuiGJvKmmEP+Vp7l7i8rlBBnS2osM=");
5 |
6 | //privkeys
7 | priv1 = atob("2kYbRu2TECMJzZy55fxdILBvM5wJM482lKLTRu2e42U=");
8 | priv2 = atob("YJx/2r3S6+wOPhu7HHAt5g39a8LMsAC1m1Ff6Unvjzk=");
9 |
10 | //signed tx
11 | stx = ["signed",["channel_block",0,1],"TUVVQ0lGUmdLQlZZcTlCdFJZOUdHNHR3b2JYc1hDYzJHSTRvQ0UzKytraExwemN1QWlFQS8zTFZEbDlaVmk3MC90aEx5SlJXYlc0NnRMakRUUldXb2FTdllHcmlnN3c9","TUVVQ0lRQzVsTW9VVWxFbmJ1blNiVGVCZUk0VlRzTDZ3UEVERnBwU1B0dDBwRnZOL1FJZ1drM21mKzhtcXJmTEVDYWZFYkdMTWRjVDFWMVBWSjYwK3U3RVNBckdIMTg9",[-6]];
12 | tx = stx[1];
13 | //console.log(JSON.stringify(tx));
14 | //console.log(pub1);
15 |
16 |
17 | //var EC = elliptic.ec
18 | //var ec = new EC('secp256k1');
19 | //var key = ec.genKeyPair();
20 |
21 | //var msg = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
22 | //var signature = key.sign(msg);
23 |
24 | //var key2 = ec.keyFromPublic(pub1, "");
25 |
26 | function toHex(str) {
27 | var hex = '';
28 | for(var i=0;i>).
32 | ```
33 |
34 | To find out your id, pubkey, and address, there are these three commands
35 | ```
36 | keys:id().
37 | keys:pubkey().
38 | keys:address().
39 | ```
40 |
41 | To calculate a shared_secret with a partner, you need a copy of their pubkey:
42 | ```
43 | keys:shared_secret(Pubkey).
44 | ```
45 |
46 | To load a private key into an existing node:
47 | ```
48 | keys:load(Pubkey, Privkey, "password").
49 | ```
50 |
51 |
52 | You can set the password for encryption like this:
53 | ```
54 | keys:change_password("old_password", "new_password").
55 | ```
56 | The default password on a new node is "", the empty string.
--------------------------------------------------------------------------------
/src/consensus/txs/oracle_unmatched_tx.erl:
--------------------------------------------------------------------------------
1 | -module(oracle_unmatched_tx).
2 | -export([test/0, make/5, doit/3]).
3 | %If you had money in orders in the oracle order book when the oracle_close transaction happened, this is how you get the money out.
4 | -record(unmatched, {from, nonce, fee, oracle_id, order_id}).
5 |
6 | make(From, Fee, OracleID, OrderID, Accounts) ->
7 | {_, Acc, Proof} = account:get(From, Accounts),
8 | Tx = #unmatched{from = From, nonce = account:nonce(Acc) + 1, fee = Fee, oracle_id = OracleID, order_id = OrderID},
9 | {Tx, [Proof]}.
10 |
11 | doit(Tx, Trees, NewHeight) ->
12 | OracleID = Tx#unmatched.oracle_id,
13 | OrderID = Tx#unmatched.order_id,
14 | AID = Tx#unmatched.from,
15 | Oracles = trees:oracles(Trees),
16 | {_, Oracle, _} = oracles:get(OracleID, Oracles),
17 | Orders = oracles:orders(Oracle),
18 | {_, Order, _} = orders:get(OrderID, Orders),
19 | Amount = orders:amount(Order),
20 | Orders2 = orders:remove(OrderID, Orders),
21 | Oracle2 = oracles:set_orders(Oracle, Orders2),
22 | Oracles2 = oracles:write(Oracle2, Oracles),
23 | Trees2 = trees:update_oracles(Trees, Oracles2),
24 |
25 | Accounts = trees:accounts(Trees),
26 | Facc = account:update(AID, Accounts, Amount-Tx#unmatched.fee, Tx#unmatched.nonce, NewHeight),
27 | Accounts2 = account:write(Accounts, Facc),
28 | trees:update_accounts(Trees2, Accounts2).
29 |
30 | test() ->
31 | success.
32 |
--------------------------------------------------------------------------------
/src/consensus/trees/trees.erl:
--------------------------------------------------------------------------------
1 | -module(trees).
2 | -export([accounts/1,channels/1,existence/1,burn/1,
3 | oracles/1,new/6,update_accounts/2,
4 | update_channels/2,update_existence/2,
5 | update_burn/2,update_oracles/2,
6 | update_governance/2, governance/1,
7 | root_hash/1]).
8 | -record(trees, {accounts, channels, existence,
9 | burn, oracles, governance}).
10 | accounts(X) -> X#trees.accounts.
11 | channels(X) -> X#trees.channels.
12 | existence(X) -> X#trees.existence.
13 | burn(X) -> X#trees.burn.
14 | oracles(X) -> X#trees.oracles.
15 | governance(X) -> X#trees.governance.
16 | new(A, C, E, B, O, G) ->
17 | #trees{accounts = A, channels = C,
18 | existence = E, burn = B,
19 | oracles = O, governance = G}.
20 | update_governance(X, A) ->
21 | X#trees{governance = A}.
22 | update_accounts(X, A) ->
23 | X#trees{accounts = A}.
24 | update_channels(X, A) ->
25 | X#trees{channels = A}.
26 | update_existence(X, E) ->
27 | X#trees{existence = E}.
28 | update_burn(X, E) ->
29 | X#trees{burn = E}.
30 | update_oracles(X, A) ->
31 | X#trees{oracles = A}.
32 | root_hash(Trees) ->
33 | A = account:root_hash(trees:accounts(Trees)),
34 | C = channel:root_hash(trees:channels(Trees)),
35 | E = existence:root_hash(trees:existence(Trees)),
36 | B = burn:root_hash(trees:burn(Trees)),
37 | O = oracles:root_hash(trees:oracles(Trees)),
38 | testnet_hasher:doit(<<
39 | A/binary,
40 | C/binary,
41 | E/binary,
42 | B/binary,
43 | O/binary
44 | >>).
45 |
--------------------------------------------------------------------------------
/src/consensus/txs/create_account_tx.erl:
--------------------------------------------------------------------------------
1 | -module(create_account_tx).
2 | -export([doit/3, make/6]).
3 | -record(ca, {from = 0, to = 0, fee = 0, nonce = 0, address = <<"">>, amount = 0}).
4 |
5 | make(Addr, Amount, Fee, From, To, Accounts) -> %To is a new ID. set it to any unused ID.
6 | A = if
7 | size(Addr) > 85 -> testnet_sign:pubkey2address(Addr);
8 | true -> Addr
9 | end,
10 | {_, Acc, Proof} = account:get(From, Accounts),
11 | Tx = #ca{from = From, to = To, nonce = account:nonce(Acc) + 1, address = A, amount = Amount, fee = Fee},
12 | {Tx, [Proof]}.
13 | doit(Tx, Trees, NewHeight) ->
14 | Accounts = trees:accounts(Trees),
15 | To = Tx#ca.to,
16 | {_RH, empty, _Proof} = account:get(To, Accounts),
17 | A = Tx#ca.amount,
18 | From = Tx#ca.from,
19 | Facc2 = account:update(From, Accounts, -A-Tx#ca.fee, Tx#ca.nonce, NewHeight),
20 | Nacc = account:new(To, Tx#ca.address, A, NewHeight),
21 | Accounts2 = account:write(Accounts, Nacc),
22 | NewAccounts = account:write(Accounts2, Facc2),
23 | MyAddress = keys:address(),
24 | if
25 | (Tx#ca.address) == MyAddress ->
26 | {_, MyAccount, _} = account:get(keys:id(), Accounts),
27 | case MyAccount of
28 | empty -> keys:update_id(Tx#ca.to);
29 | MA ->
30 | CurrentBal = account:balance(MA),
31 | if
32 | (A > CurrentBal) ->
33 | keys:update_id(Tx#ca.to);
34 | true -> ok
35 | end
36 | end;
37 |
38 | true -> ok
39 | end,
40 | trees:update_accounts(Trees, NewAccounts).
41 |
42 |
--------------------------------------------------------------------------------
/src/networking/talker.erl:
--------------------------------------------------------------------------------
1 | -module(talker).
2 | -export([talk/3, local_talk/1]).
3 |
4 | peer(IP, Port) ->
5 | %{ok, Address} = inet_parse:address(IP),
6 | L = size(IP),
7 | T = inet_parse:ntoa(IP),
8 | Z = case L of
9 | 4 -> T;
10 | 8 -> "[" ++ T ++ "]"
11 | end,
12 | "http://" ++ Z ++ ":" ++ integer_to_list(Port) ++ "/".
13 |
14 | local_talk(Msg) ->
15 | Peer = "http://127.0.0.1:3011/",
16 | talk(Msg, Peer).
17 | %talk(Msg) ->
18 | % Peer = "http://127.0.0.1:3010/",
19 | % talk(Msg, Peer).
20 | talk(Msg, Peer) ->
21 | talk_helper(Msg, Peer, 5).
22 | talk_helper(_, _, 0) -> {error, failed_connect};
23 | talk_helper(Msg, Peer, N) ->
24 | PM = packer:pack(Msg),
25 | case httpc:request(post, {Peer, [], "application/octet-stream", iolist_to_binary(PM)}, [{timeout, 1000}], []) of
26 | {ok, {_Status, _Headers, []}} ->
27 | %io:fwrite("talk error 1"),
28 | talk_helper(Msg, Peer, N-1);
29 | %io:fwrite({Status, Headers}),
30 | %{error, undefined};
31 | {error, socket_closed_remotely} ->
32 | %io:fwrite("socket closed remotely \n"),
33 | talk_helper(Msg, Peer, N-1);
34 | {ok, {_, _, R}} ->
35 | packer:unpack(R);
36 | {error, timeout} ->
37 | %io:fwrite("talk error timeout"),
38 | talk_helper(Msg, Peer, N-1);
39 | %{error, timeout};
40 | {error, {failed_connect, _}} ->
41 | %io:fwrite("talk error failed connect"),
42 | talk_helper(Msg, Peer, N-1)
43 |
44 | end.
45 | talk(Msg, IP, Port) -> talk(Msg, peer(IP, Port)).
46 |
--------------------------------------------------------------------------------
/src/consensus/tester.erl:
--------------------------------------------------------------------------------
1 | -module(tester).
2 | -export([test/0]).
3 | test() ->
4 | %this tests modules individually. To test all of them together, use block_tree:test() which adds some test blocks to the blocktree.
5 | %you need to run clean.sh to empty out the databases before running this test. Make sure you don't download anything from peers before running this test.
6 | %you need to run clean.sh after running this test, before you can run a Flying Fox node.
7 | case keys:status() of
8 | unlocked -> test1();
9 | _ -> "you need to unlock with keys:unlock(""password"") first"
10 | end.
11 |
12 | test1() ->
13 | S = success,
14 | io:fwrite("constants test\n"),
15 | S = constants:test(),
16 | S = db:test(),
17 | io:fwrite("sign test\n"),
18 | S = testnet_sign:test(),
19 | S = packer:test(),
20 | io:fwrite("encryption test\n"),
21 | S = encryption:test(),
22 | io:fwrite("fractions test\n"),
23 | S = fractions:test(),
24 | io:fwrite("merkel tree tests\n"),
25 | S = tree_test:test(),
26 | io:fwrite("block hashes test\n"),
27 | S = block_hashes:test(),
28 | io:fwrite("block test\n"),
29 | S = block:test(),
30 | io:fwrite("txs test\n"),
31 | S = test_txs:test(),
32 | S = existence:test(),
33 | S = block:mine_test(),
34 | %S = inbox:test(),
35 | %io:fwrite("chalang test\n"),
36 | %S = test_chalang:test("deps/chalang/examples/"),
37 | %S = channel_manager:test(),
38 | %S = arbitrage:test(),
39 | %S = mail:test(),
40 | S.
41 |
42 |
--------------------------------------------------------------------------------
/src/web/talk.js:
--------------------------------------------------------------------------------
1 | talk1();
2 | function talk1() {
3 | var talk_button = document.createElement("BUTTON");
4 | talk_button.id = "talk_button";
5 | var talk_text = document.createTextNode("send message");
6 | talk_button.appendChild(talk_text);
7 | talk_button.onclick = talk_func;
8 | document.body.appendChild(talk_button);
9 |
10 | var talk_address = document.createElement("input");
11 | talk_address.id = "talk_address";
12 | talk_address.setAttribute("type", "text");
13 | var address_info = document.createElement("h8");
14 | address_info.innerHTML = "talk at: ";
15 | document.body.appendChild(address_info);
16 | document.body.appendChild(talk_address);
17 | variable_get(["id"], talk2);
18 | }
19 | function talk2(id) {
20 | talk_address = document.getElementById("talk_address");
21 | talk_address.value = id;
22 | document.body.appendChild(document.createElement("br"));
23 |
24 | var talk_words = document.createElement("textarea");
25 | talk_words.id = "talk_words";
26 | talk_words.cols = 40;
27 | talk_words.rows = 3;
28 | talk_words.style = "overflow:auto;";
29 | document.body.appendChild(talk_words);
30 | }
31 | function talk_func() {
32 | console.log("talk func");
33 | var to = parseInt(document.getElementById("talk_address").value, 10);
34 | //var to2 = parseInt(to.value, 10);
35 | var msg = btoa(document.getElementById("talk_words").value);
36 | local_get(["send_msg", IP, Port, to, msg, 7]);//they have 7 seconds to read the messages.
37 | }
38 |
--------------------------------------------------------------------------------
/src/web/hash_javascript.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SHA256 demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
27 |
28 |
29 | Calculate SHA256 hash
30 |
31 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/web/spend.js:
--------------------------------------------------------------------------------
1 | spend1();
2 | function spend1() {
3 | var spend_address = document.createElement("INPUT");
4 | spend_address.setAttribute("type", "text");
5 | var input_info = document.createElement("h8");
6 | input_info.innerHTML = "spend to: ";
7 | document.body.appendChild(document.createElement("br"));
8 | document.body.appendChild(input_info);
9 | document.body.appendChild(spend_address);
10 |
11 | var spend_amount = document.createElement("INPUT");
12 | spend_amount.setAttribute("type", "text");
13 | var amount_info = document.createElement("h8");
14 | amount_info.innerHTML = "amount to spend: ";
15 | document.body.appendChild(amount_info);
16 | document.body.appendChild(spend_amount);
17 |
18 | var spend_fee = document.createElement("INPUT");
19 | spend_fee.setAttribute("type", "text");
20 | var fee_info = document.createElement("h8");
21 | fee_info.innerHTML = "fee: ";
22 | document.body.appendChild(fee_info);
23 | document.body.appendChild(spend_fee);
24 |
25 | var spend_button = document.createElement("BUTTON");
26 | spend_button.id = "spend_button";
27 | var spend_button_text = document.createTextNode("spend");
28 | spend_button.appendChild(spend_button_text);
29 | spend_button.onclick = function() {
30 | var to = parseInt(spend_address.value, 10);
31 | var amount = parseInt(spend_amount.value, 10);
32 | var fee = parseInt(spend_fee.value, 10);
33 | local_get(["spend", to, amount, fee]);
34 | };
35 | document.body.appendChild(spend_button);
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/web/to_channel.js:
--------------------------------------------------------------------------------
1 | to_channel1();
2 | function to_channel1() {
3 | var inc1 = document.createElement("INPUT");
4 | inc1.setAttribute("type", "text");
5 | var input_info = document.createElement("h8");
6 | input_info.innerHTML = "you pay: ";
7 | document.body.appendChild(document.createElement("br"));
8 | document.body.appendChild(input_info);
9 | document.body.appendChild(inc1);
10 |
11 | var inc2 = document.createElement("INPUT");
12 | inc2.setAttribute("type", "text");
13 | var amount_info = document.createElement("h8");
14 | amount_info.innerHTML = "server pays: ";
15 | document.body.appendChild(amount_info);
16 | document.body.appendChild(inc2);
17 |
18 | var spend_fee = document.createElement("INPUT");
19 | spend_fee.setAttribute("type", "text");
20 | var fee_info = document.createElement("h8");
21 | fee_info.innerHTML = "fee: ";
22 | document.body.appendChild(fee_info);
23 | document.body.appendChild(spend_fee);
24 |
25 | var spend_button = document.createElement("BUTTON");
26 | spend_button.id = "spend_button";
27 | var spend_button_text = document.createTextNode("pay money into channel");
28 | spend_button.appendChild(spend_button_text);
29 | spend_button.onclick = function() {
30 | var b1 = parseInt(inc1.value, 10);
31 | var b2 = parseInt(inc2.value, 10);
32 | var fee = parseInt(spend_fee.value, 10);
33 | local_get(["to_channel", IP, Port, b1, b2, fee]);
34 | local_get(["sync", IP, Port]);
35 | };
36 | document.body.appendChild(spend_button);
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/timing_experiment.erl:
--------------------------------------------------------------------------------
1 | -module(timing_experiment).
2 | -export([test/0, hashtable/2, tuple/2, hashtable_read/2, tuple_read/2]).
3 | %-define(size, 1024).
4 | -define(size, 128).
5 | -define(List4, ([0,0,0,0])).
6 | -define(List16, ?List4 ++ ?List4 ++ ?List4 ++ ?List4).
7 | -define(List64, ?List16 ++ ?List16 ++ ?List16 ++ ?List16).
8 | -define(List256, ?List64 ++ ?List64 ++ ?List64 ++ ?List64).
9 | -define(FullTuple, list_to_tuple(?List64 ++ ?List64)).
10 | %list_to_tuple(?List256 ++ ?List256 ++ ?List256 ++ ?List256)).
11 |
12 | full_hashtable(D, 0) -> D;
13 | full_hashtable(D, X) ->
14 | full_hashtable(dict:store(X, X, D), X-1).
15 | hashtable(_, 1) -> ok;
16 | hashtable(HT, Times) ->
17 | X = Times rem ?size,
18 | NHT = dict:store(X, X, HT),
19 | hashtable(NHT, Times - 1).
20 | hashtable_read(_, 1) -> ok;
21 | hashtable_read(HT, Times) ->
22 | X = Times rem ?size,
23 | %X = 5,
24 | dict:fetch(X, HT),
25 | hashtable_read(HT, Times - 1).
26 | tuple(_, 1) -> ok;
27 | tuple(Tuple, Times) ->
28 | X = Times rem ?size,
29 | NT = setelement(X, Tuple, X),
30 | tuple(NT, Times - 1).
31 | tuple_read(_, 1) -> ok;
32 | tuple_read(Tuple, Times) ->
33 | %X = Times rem ?size,
34 | %X = 5,
35 | %element(X, Tuple),
36 | tuple_read(Tuple, Times - 1).
37 | test() ->
38 | H = full_hashtable(dict:new(), ?size),
39 | [timer:tc(timing_experiment, hashtable, [H, ?size - 1]),
40 | timer:tc(timing_experiment, tuple, [?FullTuple, ?size - 1]),
41 | timer:tc(timing_experiment, hashtable_read, [H, ?size - 1]),
42 | timer:tc(timing_experiment, tuple_read, [?FullTuple, ?size - 1])
43 | ].
44 |
45 |
--------------------------------------------------------------------------------
/docs/shares.md:
--------------------------------------------------------------------------------
1 | There are many kinds of shares.
2 | Every kind of share can be found in 2 forms, positive and negative.
3 | If you own positive shares of form X, and receive negative shares of the same form, they cancel out and become normal AE tokens.
4 | Similarly, AE tokens can be transformed into equal amounts of positive and negative forms of the same share.
5 | Each kind of share has the same difficulty written on it.
6 |
7 | Positive shares are more valuable when the blockchain's difficulty is expected to stay above the difficulty written on the share.
8 | Negative shares are more valuable when the blockchain's difficulty is expected to stay below the difficulty written on the share.
9 |
10 | All shares have a half-life. They are either disappearing, or converting into AE tokens.
11 | If the difficulty is lower than the amount written on a share, only the negative form pays, the positive one disappears.
12 | If the difficulty is above the amount written on the a share, then only the positive form pays, the negative one disappears.
13 |
14 | Owning these shares is a way to hedge against fluctuations in the price of the token.
15 | Looking at the market for shares is a way to predict where the price of the token is going.
16 |
17 |
18 | Shares don't depend on the difficulty per block, rather they depend on how much work do you have to do to make one more token.
19 |
20 |
21 | If the difficulty is above e^(A*N) then the positive are converting to AE and the negative are disappearing.
22 | If the difficulty is below e^(A*N) then the positive are disappearing and the negative are converting to AE.
23 | A is a constant to set the step size between types.
24 |
--------------------------------------------------------------------------------
/src/web/balance.js:
--------------------------------------------------------------------------------
1 | var balance = document.createElement("div");
2 | balance.id = "balance";
3 | document.body.appendChild(balance);
4 |
5 | var channel_balance = document.createElement("div");
6 | channel_balance.id = "channel_balance";
7 | document.body.appendChild(channel_balance);
8 | var channel_balance2 = document.createElement("div");
9 | channel_balance2.id = "channel_balance2";
10 | document.body.appendChild(channel_balance2);
11 | var b_button = document.createElement("BUTTON");
12 | b_button.id = "balance_button";
13 | var button_text_node = document.createTextNode("update balance");
14 | b_button.appendChild(button_text_node);
15 | b_button.onclick = balance_update;
16 | document.body.appendChild(b_button);
17 |
18 | function balance_update() {
19 | console.log("update balance");
20 | variable_get(["balance"], balance_update2);
21 | }
22 | function balance_update2(bal) {
23 | console.log("update 2");
24 | console.log(bal);
25 | var balance = document.getElementById("balance");
26 | var b = (bal).toString();
27 | balance.innerHTML = "your balance ".concat(b);
28 | variable_get(["channel_balance", IP, Port], balance_update3);
29 | }
30 | function balance_update3(channel_balance) {
31 | var balance = document.getElementById("channel_balance");
32 | balance.innerHTML = "channel balance ".concat((channel_balance).toString());
33 | variable_get(["channel_balance2", IP, Port], balance_update4);
34 | }
35 | function balance_update4(channel_balance) {
36 | var balance = document.getElementById("channel_balance2");
37 | balance.innerHTML = "partner's channel balance ".concat((channel_balance).toString());
38 | }
39 |
--------------------------------------------------------------------------------
/api_examples.md:
--------------------------------------------------------------------------------
1 | example of how to add a node to the list of nodes you share blocks with. This is an example of accessing the local api on the same machine.
2 |
3 | ```
4 | curl -i -d '["add_peer", [127,0,0,1], 3010]' http://localhost:8041
5 | ```
6 |
7 | #example response
8 |
9 | ```
10 | HTTP/1.1 200 OK
11 | server: Cowboy
12 | date: Fri, 14 Apr 2017 09:49:41 GMT
13 | content-length: 4
14 | content-type: application/octet-stream
15 | Access-Control-Allow-Origin: *
16 |
17 | "ok"
18 | ```
19 |
20 | example of executing this api request from javascript
21 | first make sure that rpc.js is loaded, then you can do this:
22 |
23 | ```
24 | local_get(["add_peer", [127,0,0,1], 3010]);
25 | ```
26 |
27 | [The internal API is defined here](src/networking/internal_handler.erl)
28 |
29 | Now an example of accessing an api of a different node.
30 | This is how you request the header of the genesis block.
31 | Notice that the ip for external api is one lower than the ip for the api that is only on the same node.
32 |
33 | ```
34 | curl -i -d '["header", 0]' http://localhost:8040
35 | ```
36 |
37 | example response
38 |
39 | ```
40 | HTTP/1.1 200 OK
41 | server: Cowboy
42 | date: Fri, 14 Apr 2017 09:56:29 GMT
43 | content-length: 53
44 | content-type: application/octet-stream
45 | Access-Control-Allow-Origin: *
46 |
47 | ["ok","AAAAAAAAAAAAAAAA1oYN5PPka/zJxej5AAAAAAAP8AAB"]
48 | ```
49 |
50 | Here is an example of accessing the genesis block from javascript
51 |
52 | ```
53 | function callback(x) {
54 | console.log("the header is ");
55 | console.log(x);
56 | };
57 | get(["header", 0], callback);
58 | ```
59 |
60 | [The external API is defined here](src/networking/handler.erl)
--------------------------------------------------------------------------------
/docs/api_examples.md:
--------------------------------------------------------------------------------
1 | example of how to add a node to the list of nodes you share blocks with. This is an example of accessing the local api on the same machine.
2 |
3 | ```
4 | curl -i -d '["add_peer", [127,0,0,1], 3010]' http://localhost:8041
5 | ```
6 |
7 | #example response
8 |
9 | ```
10 | HTTP/1.1 200 OK
11 | server: Cowboy
12 | date: Fri, 14 Apr 2017 09:49:41 GMT
13 | content-length: 4
14 | content-type: application/octet-stream
15 | Access-Control-Allow-Origin: *
16 |
17 | "ok"
18 | ```
19 |
20 | example of executing this api request from javascript
21 | first make sure that rpc.js is loaded, then you can do this:
22 |
23 | ```
24 | local_get(["add_peer", [127,0,0,1], 3010]);
25 | ```
26 |
27 | [The internal API is defined here](src/networking/internal_handler.erl)
28 |
29 | Now an example of accessing an api of a different node.
30 | This is how you request the header of the genesis block.
31 | Notice that the ip for external api is one lower than the ip for the api that is only on the same node.
32 |
33 | ```
34 | curl -i -d '["header", 0]' http://localhost:8040
35 | ```
36 |
37 | example response
38 |
39 | ```
40 | HTTP/1.1 200 OK
41 | server: Cowboy
42 | date: Fri, 14 Apr 2017 09:56:29 GMT
43 | content-length: 53
44 | content-type: application/octet-stream
45 | Access-Control-Allow-Origin: *
46 |
47 | ["ok","AAAAAAAAAAAAAAAA1oYN5PPka/zJxej5AAAAAAAP8AAB"]
48 | ```
49 |
50 | Here is an example of accessing the genesis block from javascript
51 |
52 | ```
53 | function callback(x) {
54 | console.log("the header is ");
55 | console.log(x);
56 | };
57 | get(["header", 0], callback);
58 | ```
59 |
60 | [The external API is defined here](src/networking/handler.erl)
--------------------------------------------------------------------------------
/docs/oracle.md:
--------------------------------------------------------------------------------
1 | We use a trie to store all the questions that have been asked of the oracle.
2 | We use another trie to store the answers, for any questions that were answered.
3 | We use a trie to store all the questions that have been asked of the oracle.
4 | We use another trie to store the answers, for any questions that were answered.
5 |
6 | For questions that are in the process of being answered, we store a market in the on-chain state.
7 | The market remembers how many shares of each type have been sold, and it remembers what it's initial liquidity was, and it should have an order-book.
8 | The market has 4 possible outcomes:
9 | 1) difficulty goes up, and oracle's outcome is true
10 | 2) difficulty goes up, and oracle's outcome is false
11 | 3) difficulty goes down, and oracle's outcome is true
12 | 4) difficulty goes down, and oracle's outcome is false
13 |
14 |
15 |
16 | *** Somehow, we need to know how many shares of each type each account owns.
17 |
18 | The initial liquidity will be collected using an off-chain dominant assurance contract.
19 |
20 | the result of the oracle is measured by looking at the correlation between the outcomes.
21 | We sum the diagonals, and see which way is bigger.
22 | If the correlation is in the same direction for enough blocks, then that is the oracle's result.
23 | Since we use an order book, it is expensive for attackers to DDOS us by moving the price past 50% every time the oracle is almost done.
24 |
25 |
26 | We should pay the gambler all at once, because only one fork can survive.
27 | When a gambler wants to get paid, they need a way to prove how they bet.
28 | *** Maybe we should have a merkle root of bets in each account?
29 |
--------------------------------------------------------------------------------
/src/consensus/chain/block_absorber.erl:
--------------------------------------------------------------------------------
1 | -module(block_absorber).
2 | -behaviour(gen_server).
3 | -export([start_link/0,code_change/3,handle_call/3,
4 | handle_cast/2,handle_info/2,init/1,terminate/2,
5 | doit/1, save_helper/1]).
6 | init(ok) ->
7 | %save(block:genesis()),
8 | {ok, []}.
9 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
10 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
11 | terminate(_, _) -> io:format("died!"), ok.
12 | handle_info(_, X) -> {noreply, X}.
13 | handle_cast({doit, BP}, X) ->
14 | absorb(BP),
15 | {noreply, X};
16 | handle_cast(_, X) -> {noreply, X}.
17 | handle_call(_, _From, X) -> {reply, X, X}.
18 |
19 | doit(X) ->
20 | gen_server:cast(?MODULE, {doit, X}).
21 |
22 | absorb(BP) ->
23 | %BH = block:hash(BP),
24 | {BH, _} = block:check1(BP),
25 | case block_hashes:check(BH) of
26 | true -> ok;%If we have seen this block before, then don't process it again.
27 | false ->
28 | block_hashes:add(BH),%Don't waste time checking invalid blocks more than once.
29 | io:fwrite("absorb block "),
30 | io:fwrite(packer:pack(BP)),
31 | io:fwrite("\n"),
32 | BP2 = block:check2(BP),
33 | save(BP2)
34 | end.
35 | save_helper(BlockPlus) ->
36 | Z = zlib:compress(term_to_binary(BlockPlus)),
37 | binary_to_term(zlib:uncompress(Z)),%sanity check, not important for long-term.
38 | %Hash = testnet_hasher:doit(BlockPlus),
39 | Hash = block:hash(BlockPlus),
40 | BF = block:binary_to_file(Hash),
41 | db:save(BF, Z).
42 |
43 | save(BlockPlus) ->
44 | save_helper(BlockPlus),
45 | top:add(BlockPlus),
46 | block:hash(BlockPlus).
47 |
--------------------------------------------------------------------------------
/src/web/create_account.js:
--------------------------------------------------------------------------------
1 | create_account1();
2 | function create_account1() {
3 | var create_pubkey = document.createElement("INPUT");
4 | create_pubkey.setAttribute("type", "text");
5 | var pubkey_info = document.createElement("h8");
6 | pubkey_info.innerHTML = "new account address: ";
7 | document.body.appendChild(document.createElement("br"));
8 | document.body.appendChild(pubkey_info);
9 | document.body.appendChild(create_pubkey);
10 |
11 | var create_balance = document.createElement("INPUT");
12 | create_balance.setAttribute("type", "text");
13 | var balance_info = document.createElement("h8");
14 | balance_info.innerHTML = "new account balance: ";
15 | document.body.appendChild(balance_info);
16 | document.body.appendChild(create_balance);
17 |
18 | var spend_fee = document.createElement("INPUT");
19 | spend_fee.setAttribute("type", "text");
20 | var fee_info = document.createElement("h8");
21 | fee_info.innerHTML = "fee: ";
22 | document.body.appendChild(fee_info);
23 | document.body.appendChild(spend_fee);
24 |
25 |
26 | var create_button = document.createElement("BUTTON");
27 | create_button.id = "create_account_button";
28 | var button_text = document.createTextNode("create account");
29 | create_button.appendChild(button_text);
30 | create_button.onclick = function() {
31 | var to = create_pubkey.value;
32 | var amount = parseInt(create_balance.value, 10);
33 | var fee = parseInt(spend_fee.value, 10);
34 | local_get(["create_account", to, amount, fee]);
35 | local_get(["sync", IP, Port]);
36 | };
37 | document.body.appendChild(create_button);
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/consensus/chain/top.erl:
--------------------------------------------------------------------------------
1 | -module(top).
2 | -behaviour(gen_server).
3 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2, add/1,doit/0,test/0]).
4 | -define(LOC, constants:top()).
5 | init(ok) ->
6 | io:fwrite("start top"),
7 | X = db:read(?LOC),
8 | Ka = if
9 | X == "" ->
10 | G = block:genesis(),
11 | block_absorber:save_helper(G),
12 | add_internal(G),
13 | I = keys:pubkey(),
14 | M = constants:master_pub(),
15 | if
16 | I == M -> keys:update_id(1);
17 | true -> ok
18 | end,
19 | block:hash(G);
20 | true ->
21 | X
22 | end,
23 | {ok, Ka}.
24 | %{ok, []}.
25 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
26 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
27 | terminate(_, _) -> io:format("died!"), ok.
28 | handle_info(_, X) -> {noreply, X}.
29 | handle_cast(_, X) -> {noreply, X}.
30 | handle_call({add, Block}, _From, X) ->
31 | %check which block is higher and store it's hash as the top.
32 | %for tiebreakers, prefer the older block.
33 | OldBlock = block:read(X),
34 | NH = block:height(Block),
35 | OH = block:height(OldBlock),
36 | New = if
37 | NH > OH ->
38 | Trees = block:trees(Block),
39 | NH = block:height(Block),
40 | tx_pool:absorb(Trees, [], NH),
41 | add_internal(Block);
42 | true -> X
43 | end,
44 | {reply, 0, New};
45 | handle_call(_, _From, X) -> {reply, X, X}.
46 |
47 | add(Block) -> gen_server:call(?MODULE, {add, Block}).
48 | doit() -> gen_server:call(?MODULE, top).
49 | add_internal(Block) ->
50 | Y = block:hash(Block),
51 | db:save(?LOC, Y),
52 | Y.
53 |
54 | test() ->
55 | ok.
56 |
--------------------------------------------------------------------------------
/src/consensus/trees/burn.erl:
--------------------------------------------------------------------------------
1 | -module(burn).
2 | -export([test/0, new/1, get/2, write/2, address/1, amount/1, root_hash/1]).
3 | %The proof of burn tree stores by address. It stores the number of AE tokens that this address has burned.
4 | -record(burn, {address, amount = 0}).
5 | -define(name, burn).
6 | address(B) -> B#burn.address.
7 | amount(B) -> B#burn.amount.
8 | new(Address) -> #burn{address = Address}.
9 | serialize(E) ->
10 | HS = constants:hash_size(),
11 | BAL = constants:balance_bits(),
12 | A = testnet_sign:address2binary(E#burn.address),
13 | HS = size(A),
14 | <<(E#burn.amount):BAL,
15 | A/binary>>.
16 | deserialize(B) ->
17 | HS = constants:hash_size() * 8,
18 | BAL = constants:balance_bits(),
19 | <> = B,
20 | #burn{address = testnet_sign:binary2address(<>),
21 | amount = I}.
22 | get(Address, Tree) ->
23 | B = testnet_sign:address2binary(Address),
24 | Key = existence:hash2int(B),
25 | {X, Leaf, Proof} = trie:get(Key, Tree, ?name),
26 | V = case Leaf of
27 | empty -> empty;
28 | L -> deserialize(leaf:value(L))
29 | end,
30 | {X, V, Proof}.
31 | write(A, Tree) ->
32 | Address = A#burn.address,
33 | Binary = testnet_sign:address2binary(Address),
34 | Key = existence:hash2int(Binary),
35 | X = serialize(A),
36 | trie:put(Key, X, 0, Tree, ?name).
37 | root_hash(Root) ->
38 | trie:root_hash(?name, Root).
39 |
40 | test() ->
41 | Address = constants:master_address(),
42 | C = new(Address),
43 | {_, empty, _} = get(Address, 0),
44 | NewLoc = write(C, 0),
45 | {_, C, _} = get(Address, NewLoc),
46 | {_, empty, _} = get(Address, 0),
47 | success.
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/consensus/trees/existence.erl:
--------------------------------------------------------------------------------
1 | -module(existence).
2 | -export([get/2,write/2,new/1,hash2int/1,root_hash/1,hash/1, test/0]).
3 | %for accessing the proof of existence tree
4 | -record(exist, {hash}).
5 | -define(name, existence).
6 | hash(X) ->
7 | X#exist.hash.
8 | new(Hash) ->
9 | HS = constants:hash_size(),
10 | HS = size(Hash),
11 | #exist{hash = Hash}.
12 | serialize(E) ->
13 | HS = constants:hash_size(),
14 | Hash = E#exist.hash,
15 | HS = size(Hash),
16 | Hash.
17 | deserialize(B) ->
18 | HS = constants:hash_size()*8,
19 | <> = B,
20 | #exist{hash = <>}.
21 |
22 | get(Hash, Tree) ->
23 | true = is_binary(Hash),
24 | Key = hash2int(Hash),
25 | {X, Leaf, Proof} = trie:get(Key, Tree, ?name),
26 | V = case Leaf of
27 | empty -> empty;
28 | L ->
29 | Y = leaf:value(L),
30 | deserialize(Y)
31 | end,
32 | {X, V, Proof}.
33 | write(E, Tree) ->
34 | Hash = E#exist.hash,
35 | Key = hash2int(Hash),
36 | X = serialize(E),
37 | trie:put(Key, X, 0, Tree, ?name).
38 |
39 | hash2int(X) ->
40 | S = size(X),
41 | S = constants:hash_size(),
42 | hash2int(X, 0).
43 | hash2int(<<>>, N) -> N;
44 | hash2int(<>, N) ->
45 | M = (N*256) + X,
46 | hash2int(Y, M).
47 | root_hash(Root) ->
48 | trie:root_hash(?name, Root).
49 |
50 |
51 | test() ->
52 | Hash = testnet_hasher:doit(2),
53 | C = new(Hash),
54 | %C = testnet_hasher:doit(2),
55 | {_, empty, _} = get(Hash, 0),
56 | NewLoc = write(C, 0),
57 | C2 = new(testnet_hasher:doit(4)),
58 | NewLoc2 = write(C2, NewLoc),
59 | {_, C, _} = get(Hash, NewLoc2),
60 | {_, empty, _} = get(Hash, 0),
61 | success.
62 |
--------------------------------------------------------------------------------
/src/web/rpc.js:
--------------------------------------------------------------------------------
1 | var IP = [52, 36, 106, 100];// server
2 | //var IP = [127, 0, 0, 1];
3 | var Port = 3010;
4 | //var Port = 3030;
5 | function getter(t, u, callback){
6 | t = JSON.stringify(t);
7 | //console.log("getter ".concat(t));
8 | var xmlhttp=new XMLHttpRequest();
9 | xmlhttp.onreadystatechange = callback;
10 | xmlhttp.open("POST",u,true);
11 | xmlhttp.send(t);
12 | return xmlhttp
13 | }
14 | var PORT = parseInt(document.URL.substring(17, 21), 10);
15 | function get(t, callback) {
16 | u = url(PORT - 1, "localhost");
17 | return getter(t, u, callback);
18 | }
19 | function url(port, ip) { return "http://".concat(ip).concat(":").concat(port.toString().concat("/")); }
20 | //PORT = 3010;
21 | function local_get(t, callback) {
22 | u = url(PORT, "localhost");
23 | return getter(t, u, callback);
24 | }
25 | function xml_check(x) { return ((x.readyState === 4) && (x.status === 200)); };
26 | function xml_out(x) { return x.responseText; }
27 | function refresh_helper(x, callback) {
28 | if (xml_check(x)) {callback(xml_out(x));}
29 | else {setTimeout(function() {refresh_helper(x, callback);}, 1000);}
30 | };
31 |
32 | my_status = "nil";
33 | var x = local_get(["test"]);
34 |
35 | refresh_helper(x, function(){
36 | my_status = JSON.parse(xml_out(x));
37 | console.log("test response ".concat(JSON.stringify(my_status)));
38 | });
39 | function variable_get(cmd, callback) {
40 | var x = local_get(cmd);
41 | var_get(x, callback);
42 | }
43 | function variable_public_get(cmd, callback) {
44 | var x = get(cmd);
45 | var_get(x, callback);
46 | }
47 | function var_get(x, callback) {
48 | refresh_helper(x, function(){
49 | p = JSON.parse(xml_out(x));
50 | callback(p[1]);
51 | });
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/src/consensus/txs/oracle_shares_tx.erl:
--------------------------------------------------------------------------------
1 | -module(oracle_shares_tx).
2 | -export([test/0, doit/3, make/4]).
3 |
4 | %If you bet in an oracle, and the oracle has closed, this is how you get your shares out.
5 | %If you bet on the winning outcome, then you get positive shares. If you bet on one of the losing outcomes, then you get negative shares.
6 | %[you can read about shares here](docs/shares.md)
7 | %The difficulty of the shares was announced when the oracle was launched.
8 | -record(oracle_shares, {from, nonce, fee, oracle_id}).
9 | make(From, Fee, OID, Accounts) ->
10 | {_, Acc, Proof} = account:get(From, Accounts),
11 | Tx = #oracle_shares{from = From, nonce = account:nonce(Acc) + 1, fee = Fee, oracle_id = OID},
12 | {Tx, [Proof]}.
13 | doit(Tx, Trees, NewHeight) ->
14 | OID = Tx#oracle_shares.oracle_id,
15 | Oracles = trees:oracles(Trees),
16 | {_, Oracle, _} = oracles:get(OID, Oracles),
17 | Result = oracles:result(Oracle),
18 | DT = oracles:done_timer(Oracle),
19 | true = NewHeight - constants:minimum_oracle_time() < DT,
20 | AID = Tx#oracle_shares.from,
21 | Accounts = trees:accounts(Trees),
22 | Acc = account:update(AID, Accounts, -Tx#oracle_shares.fee, Tx#oracle_shares.nonce, NewHeight),
23 | %transform their bets into shares.
24 |
25 | Bets = account:bets(Acc),
26 | {_, Bet, _} = oracle_bets:get(OID, Bets),
27 | B2Shares =oracle_bets:to_shares(Bet, Result, NewHeight),
28 | %Shares = account:shares(Acc),
29 | Acc2 = account:receive_shares(Acc, B2Shares, NewHeight),
30 | Bets2 = oracle_bets:delete(OID, Bets),
31 | Acc3 = account:update_bets(Acc2, Bets2),
32 |
33 | Accounts2 = account:write(Accounts, Acc3),
34 | trees:update_accounts(Trees, Accounts2).
35 | test() ->
36 | success.
37 |
--------------------------------------------------------------------------------
/src/consensus/chain/block_hashes.erl:
--------------------------------------------------------------------------------
1 | -module(block_hashes).
2 | %each blockhash is about 12 bytes. We need to prepare for about 10000000 blocks. So that would be 12 megabytes of data. We can keep this all in ram.
3 | -behaviour(gen_server).
4 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2, add/1,check/1,test/0]).
5 | -define(LOC, constants:block_hashes()).
6 | init(ok) ->
7 | process_flag(trap_exit, true),
8 | X = db:read(?LOC),
9 | K = if
10 | X == "" ->
11 | i_new();
12 | true -> X
13 | end,
14 | {ok, K}.
15 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
16 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
17 | terminate(_, X) ->
18 | db:save(?LOC, X),
19 | io:format("block_hashes died!"), ok.
20 | handle_info(_, X) -> {noreply, X}.
21 | handle_cast({add, H}, X) ->
22 | N = i_insert(H, X),
23 | db:save(?LOC, N),%This line is only necessary for power failures
24 | {noreply, N};
25 | handle_cast(_, X) -> {noreply, X}.
26 | handle_call({check, H}, _From, X) ->
27 | B = i_check(H, X), %true means it doesn't exist.
28 | {reply, B, X} ;
29 | handle_call(_, _From, X) -> {reply, X, X}.
30 |
31 | add(X) ->
32 | true = size(X) == constants:hash_size(),
33 | gen_server:cast(?MODULE, {add, X}).
34 |
35 | check(X) ->
36 | true = size(X) == constants:hash_size(),
37 | gen_server:call(?MODULE, {check, X}).
38 |
39 | i_new() ->
40 | gb_sets:new().
41 | i_insert(H, X) ->
42 | gb_sets:insert(H, X).
43 | i_check(H, X) ->
44 | gb_sets:is_member(H, X).
45 |
46 | test() ->
47 | V1 = <<1:92>>,
48 | V2 = <<2:92>>,
49 | D = i_new(),
50 | D2 = i_insert(V1, D),
51 | false = i_check(V2, D2),
52 | true = i_check(V1, D2),
53 | success.
54 |
--------------------------------------------------------------------------------
/docs/anarchy.md:
--------------------------------------------------------------------------------
1 | I think that things like "ventures" and "companies" only exist because they were recently the most efficient way for people to pool their resources and work together towards common goals at a certain scale, and because you need to be as big as a company to justify the cost of jumping through the regulatory loopholes.
2 |
3 | Now that finance is unshackled from the government's restrictions, we have much better tools for pooling resources: unrestricted financial derivatives.
4 |
5 | The biggest advantage of the new system over companies is that each individual is in complete control of their own money. We are entering the trustless era.
6 |
7 | under the old system you had a publishing company. They had a legal right to the book, and gave the author some money for every copy sold. The publishing company has investors who own it and trust managers to act wisely.
8 |
9 | In the new system the book will be shared for free by torrent. The author will get paid before the book is published by an insured crowdfunding contract. No venture or company is involved.
10 |
11 | Insured crowdfunding, which is a type of financial derivative, lets people pool their resources for common goals without trusting anyone. It allows us to pay for the production of goods or services without hiring anyone.
12 |
13 | As an open source programmer, I expect to make my income from insured crowdfunding contracts. That way the money I am paid comes directly from the people my code benefits, and they each pay me exactly how much my software benefits them. I don't need a company or venture to employ me.
14 |
15 | Prediction markets allow us to come to consensus about how money should be spent without relying on a manager or company to make the decision.
16 | A decentralized world harnesses the wisdom of the crowd to make decisions.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Plasmodial
2 | ==========
3 |
4 | A blockchain for trustless markets and financial derivatives.
5 |
6 |
7 |
8 | Development has moved to here: https://github.com/zack-bitcoin/testnet
9 | ========
10 |
11 |
12 | .
13 |
14 | .
15 |
16 |
17 | .
18 |
19 |
20 | This code will be used by [æternity](https://aeternity.com).
21 |
22 | ### Compiling and Runing
23 | You will need Erlang and a couple of libraries. Please follow instructions:
24 | [For Ubuntu](docs/compile_ubuntu.md)
25 | [For Mac](docs/compile_mac.md)
26 |
27 |
28 | ### Commands
29 |
30 | #### Start the blockchain
31 | Start your node with following script:
32 | ```
33 | sh start.sh
34 | ```
35 |
36 | ### Commands
37 |
38 | #### Sync with the network
39 | To sync with the network and download the blockchain:
40 | ```
41 | easy:sync().
42 | ```
43 |
44 | #### Mining
45 | After fresh install, one can start mining.
46 |
47 | To start mining with all CPU cores:
48 | ```
49 | mine:start().
50 | ```
51 | To stop mining:
52 | ```
53 | mine:stop().
54 | ```
55 | to check if you are currently mining:
56 | ```
57 | mine:is_on().
58 | ```
59 |
60 | #### Spend
61 | ```
62 | easy:spend(To, Amount).
63 | ```
64 | To is the recipient's account ID
65 |
66 | #### Last transactions
67 | ```
68 | tx_pool:data().
69 | ```
70 |
71 | #### Find out your account ID
72 | ```
73 | keys:id().
74 | ```
75 | If it returns something less than 1, that means you don't have an account yet.
76 |
77 | #### Create an account
78 | (does get done automatically when no accocunt and mining starts)
79 | [Make an account](docs/new_account.md)
80 |
81 | #### Check your balance
82 | ```
83 | easy:balance().
84 | ```
85 |
86 | #### Stop a node
87 | To stop a node run:
88 | ```
89 | easy:off().
90 | ```
91 |
92 |
93 | ### Else
94 | If you want to know more, get in touch with us via [Gitter Chat](https://gitter.im/BumblebeeBat/plasmodial)
95 |
--------------------------------------------------------------------------------
/docs/go_game_predictions.md:
--------------------------------------------------------------------------------
1 | Blockchains allow for prediction markets, which let us prepare for the future.
2 | To show how powerful prediction markets can be, we will use a prediction market to play go against a professional, and we will win.
3 |
4 | First we will make the big market, it sells 2 kinds of shares. "AI" shares only pay out if the professional loses. "Human" shares only pay out if the professional wins.
5 |
6 | After every move, the relative price of "Human" and "AI" shares will change.
7 | For each move, we make 2 prediction markets, each with <=381 possible choices. The only difference between the two markets is that one is priced in "Human" shares, and the other in "AI" shares.
8 |
9 | So for example, lets say the current price is 60% likely that the human will win,
10 | and you think that if the AI plays 5C, that the odds will shift to 50% likely that the human will win.
11 | (Which would make your AI tokens 5/4 as valuable)
12 | You also think that if the AI plays anywhere besides 5C, that the odds of the human winning will increase to 80%.
13 | (which would make your AI tokens 1/2 as valuable)
14 |
15 | There are 4 locations in the Nash square defined by 2 questions:
16 | 1) Does the AI play on 5C?
17 | 2) Does the AI win?
18 |
19 | You don't bet on either of these questions directly, rather you bet on the correlation.
20 | You would buy shares of (yes, yes), and you would buy shares of (no, no). You would sell shares of (yes, no) and (no, yes).
21 | You keep doing this until the prices of shares are such that if the AI plays 5C, the price of AI shares increases to 50%, and if the AI plays anywhere else, the price of AI shares decreases to 20%.
22 |
23 |
24 |
25 |
26 |
27 | To decide the initial values of the <=381 outcomes for each market, we use the final values from the previous round of betting. Most of the good moves are still good, most of the bad moves are still bad.
--------------------------------------------------------------------------------
/src/channels/channel_manager.erl:
--------------------------------------------------------------------------------
1 | -module(channel_manager).
2 | -behaviour(gen_server).
3 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2,
4 | keys/0,read/1,delete/1,write/2]).
5 | -define(LOC, constants:channel_manager()).
6 | init(ok) ->
7 | X = db:read(?LOC),
8 | Ka = if
9 | X == "" -> dict:new();
10 | true -> X
11 | end,
12 | %process_flag(trap_exit, true),
13 | {ok, Ka}.
14 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
15 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
16 | terminate(_, K) ->
17 | db:save(?LOC, K),
18 | io:format("died!"), ok.
19 | handle_info(_, X) -> {noreply, X}.
20 | handle_cast({write, CID, Data}, X) ->
21 | NewX = dict:store(CID, Data, X),
22 | db:save(?LOC, NewX),
23 | %this db:save is only for power failures. Without it, you could lose channel data on power failure. This line can be removed to make the node update channels faster.
24 | {noreply, NewX};
25 | handle_cast({delete, CID}, X) ->
26 | NewX = dict:delete(CID, X),
27 | db:save(?LOC, NewX),
28 | %this db:save is only for power failures. Without it, you could lose channel data on power failure. This line can be removed to make the node update channels faster.
29 | {noreply, NewX};
30 | handle_cast(_, X) -> {noreply, X}.
31 | handle_call(keys, _From, X) ->
32 | {reply, dict:fetch_keys(X), X};
33 | handle_call({read, CID}, _From, X) ->
34 | {reply, dict:find(CID, X), X};
35 | handle_call(_, _From, X) -> {reply, X, X}.
36 |
37 | read(CID) -> gen_server:call(?MODULE, {read, CID}).
38 | keys() -> gen_server:call(?MODULE, keys).
39 | delete(CID) -> gen_server:cast(?MODULE, {delete, CID}).
40 | write(CID, Data) ->
41 | true = is_list(channel_feeder:script_sig_them(Data)),
42 | true = is_list(channel_feeder:script_sig_me(Data)),
43 | gen_server:cast(?MODULE, {write, CID, Data}).
44 |
45 |
--------------------------------------------------------------------------------
/docs/oracle_simple.md:
--------------------------------------------------------------------------------
1 | A simple way to do the oracle is like this:
2 | Every time a decision resolves, the blockchain forks. One side decides that the decision's outcome is "true", the other decides "false".
3 | Users know the truth. They prefer the chain that is more honest. So the coins on the honest chain are the ones that are valuable. So miners are only able to profitably mine on the honest chain.
4 |
5 | This simple oracle mechanism has a big drawback.
6 | There is a large cost to having the users manually answer each oracle decision while downloading the blockchain.
7 | An attacker could ask lots of questions, and waste our time answering them.
8 |
9 | So, we need to adjust the mechanism so that if an attacker makes us manually answer a question, the cost to the attacker exceeds the damage inflicted on the network.
10 |
11 | The way to make oracle spam expensive is to use a market that measures the correlation between the answer to the question and the blockchain's difficulty.
12 | The blockchain's difficulty is averaged over a long-enough period of time, that miners can't afford to mine at a loss long enough to manipulate the result.
13 | Miner's profit margin is as small as possible, so user's demand for new coins determine the difficulty, not the miners.
14 |
15 | If an attacker wants to spam the blockchain with questions, the attacker will also have to make large losing bets in the market. These losing bets cover the cost of other users having to manually answer the question.
16 |
17 | There is still one case where an attacker can make the oracle lie.
18 | The attacker commits to buying up enough coins on the lying fork, so that the difficulty of the lying fork is higher than the difficulty of the honest fork.
19 | A blockchain with higher difficulty is more valuable.
20 | So the result of this attack is a blockchain that is more valuable.
21 | Making the blockchain valuable is more important than making the oracle honest.
22 | So in this situation, it is acceptable for the oracle to lie.
23 |
--------------------------------------------------------------------------------
/docs/trees.md:
--------------------------------------------------------------------------------
1 | There are 8 merkel trees.
2 |
3 | * oracles+
4 | * orders
5 | * channels+
6 | * accounts+
7 | * oracle bets
8 | * shares
9 | * proof of burn+
10 | * proof of existence+
11 |
12 | (The ones with +'s are included in the generation of the state-hash that is recorded on the block's header)
13 |
14 | === oracles
15 |
16 | These are the oracles that exist right now. They are stored by integer oracle id. Oracles never reuse the same id.
17 | The hash of the text of the question is stored.
18 | These are the results of oracles that have existed. They are stored by id.
19 | This data is available to the VM.
20 | The result is stored in 1 byte. Either it is 0 for false, 1 for true, or 2 if the questions was bad, or a 4 if the question hasn't been answered yet.
21 |
22 | === Orders
23 |
24 | Every oracle has an order book. The order book is a linked list of orders. Each order has an amount, and the id of the owner.
25 |
26 | === channels
27 |
28 | This tree stores channels by an integer channel id.
29 |
30 | === accounts
31 |
32 | This tree stores accounts by integer id. Each account has 2 merkel roots written in them. One is for a shares tree, the other is for an oracle bets tree.
33 |
34 | === oracle bets
35 |
36 | Each account has a tree of oracle bets. Oracle bets are not transferable. Once an oracle is settled, the bets in it can be converted to shares.
37 |
38 | === shares
39 |
40 | Each account has a tree of shares. The shares are stored by share id. The id of a share determines it's difficulty. You can own either a negative, positive, or zero amount of each type of share. Shares are transferable
41 | [you can read about shares here](docs/shares.md)
42 |
43 | === proof of burn
44 |
45 | The proof of burn tree stores by address. It stores the number of AE tokens that this address has burned.
46 | This data is available to the VM.
47 |
48 | === proof of existence
49 |
50 | This tree stores by hash. It returns a 1 if the thing exists, a 0 otherwise.
51 | This data is available to the VM.
52 |
--------------------------------------------------------------------------------
/src/free_constants.erl:
--------------------------------------------------------------------------------
1 | -module(free_constants).
2 | %These constants can be different on every node in the network. You can adjust these variables to suit your own situation.
3 | -compile(export_all).
4 | cores_to_mine() ->
5 | 1000.%The maximum number of cores to use when mining.
6 | hashlock_time() -> 30.
7 | channel_delay() ->
8 | 100.
9 | max_channel() -> constants:initial_coins() div 100000.
10 | max_message_size() -> 10000.
11 | inbox_per_peer() -> 100.
12 | liquidity_ratio() -> fractions:new(2, 3).%if a user is willing to put 100 coins into a channel, then the server is willing to put 200 in.
13 | tx_fee() -> %when you make a tx, this is the fee you spend by default.
14 | 10.
15 | minimum_tx_fee() ->%only txs with this fee or higher get accepted into your mempool. If you are a miner, you are censoring all txs with lower fees.
16 | constants:initial_coins() div 1000000000000.
17 | fork_tolerance() ->
18 | %this is how long of a fork we can recover from. If this number is bigger, it takes longer to sync with the network because you download more unnecessary blocks.
19 | %It is best to keep this number low when you first sync, and make it bigger once you are synced with the network.
20 | %on nodes that are mining, this should probably be set very low.
21 | 20.
22 | min_channel_ratio() ->
23 | %So the customer needs to put in twice as much money as the server.
24 | 0.5.
25 | %{f, 1, 2}.
26 | bets() -> %tuple list. {Name, BetFile}
27 | [
28 | {dice, "src/bets/dice.fs"}
29 | ].
30 | gas_limit() ->
31 | constants:gas_limit().
32 | time_limit() ->
33 | %maximum number of miliseconds to wait for a channel contract to process.
34 | %if this number is too high, then it is easy to
35 | 100000.
36 | space_limit() ->
37 | 100000.
38 |
39 | vm(SS, State) ->
40 | chalang:vm(SS, time_limit(), space_limit(), constants:fun_limit(), constants:var_limit(), State).
41 |
42 | min_channel_delay() -> 4.
43 | max_channel_delay() -> 100.
44 |
45 |
--------------------------------------------------------------------------------
/src/consensus/txs/txs.erl:
--------------------------------------------------------------------------------
1 | -module(txs).
2 | -behaviour(gen_server).
3 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2, dump/0,txs/0,digest/3,fees/1]).
4 | init(ok) -> {ok, []}.
5 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
6 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
7 | terminate(_, _) -> io:format("txs died!"), ok.
8 | handle_info(_, X) -> {noreply, X}.
9 | handle_call(txs, _From, X) -> {reply, X, X}.
10 | handle_cast(dump, _) -> {noreply, []};
11 | handle_cast({add_tx, Tx}, X) -> {noreply, [Tx|X]}.
12 | dump() -> gen_server:cast(?MODULE, dump).
13 | txs() -> gen_server:call(?MODULE, txs).
14 | digest([], Trees, _) -> Trees;
15 | digest([SignedTx|Txs], Trees, Height) ->
16 | Accounts = trees:accounts(Trees),
17 | true = testnet_sign:verify(SignedTx, Accounts),
18 | Tx = testnet_sign:data(SignedTx),
19 | NewTrees = digest2(Tx, Trees, Height),
20 | digest(Txs, NewTrees, Height).
21 | digest2(Tx, Trees, H) ->
22 | case element(1, Tx) of
23 | ca -> create_account_tx:doit(Tx, Trees, H);
24 | spend -> spend_tx:doit(Tx, Trees, H);
25 | da -> delete_account_tx:doit(Tx, Trees, H);
26 | repo -> repo_tx:doit(Tx, Trees, H);
27 | nc -> new_channel_tx:doit(Tx, Trees, H);
28 | gc -> grow_channel_tx:doit(Tx, Trees, H);
29 | ctc -> channel_team_close_tx:doit(Tx, Trees, H);
30 | cr -> channel_repo_tx:doit(Tx, Trees, H);
31 | csc -> channel_solo_close:doit(Tx, Trees, H);
32 | timeout -> channel_timeout_tx:doit(Tx, Trees, H);
33 | cs -> channel_slash_tx:doit(Tx, Trees, H);
34 | ex -> existence_tx:doit(Tx, Trees, H);
35 | oracle_new -> oracle_new_tx:doit(Tx, Trees, H);
36 | oracle_bet -> oracle_bet_tx:doit(Tx, Trees, H);
37 | oracle_close -> oracle_close_tx:doit(Tx, Trees, H);
38 | unmatched -> oracle_unmatched_tx:doit(Tx, Trees,H);
39 | oracle_shares -> oracle_shares_tx:doit(Tx,Trees,H);
40 | X -> X=2
41 | end.
42 | fees([]) -> 0;
43 | fees([H|T]) ->
44 | element(4, element(2, H)) + fees(T).
45 |
--------------------------------------------------------------------------------
/docs/ethereum_comparison.md:
--------------------------------------------------------------------------------
1 | fifi80 published this on reddit.
2 |
3 | Here are my thoughts about why it is superior to ETH: – contracts almost always involve an element of uncertainty that would be disclosed in the future. Like for example if it will rain tomorrow. A contract without an element of uncertainty is basically a series of time locked transactions. The only exception is a contract with no uncertainty but an obligation like for example a rental contract. In that case however you really need the judicial system as you need the police to get involved for example if the person doesn't let you in the house or is the tenant sets the house on fire. If I understand correctly in ETH you need to define your oracles in advance rendering it not so different than multisig where you would have someone decide on the outcome of something. If I understand correctly in AE you can just let the system of built-in oracles worry about the future determination of the uncertain element. – ETH can be useful for managing tokens, but that won't bring any value to it. The value of a smart contract token comes from the fact that you need to buy it in order to make a contract with it to exchange value. For example the fact that you can store data in BTC block chain doesn't add any value to the token, may be a very very small bit simply because the block size is limited. The reason is that to store data in the block chain you only need a few cents. I assume the logic behind issuing tokens on ETH is similar
4 |
5 | I see immense value in AE, it could be used for any kind of financial and nonfinancial bets. If you exclude contracts that are simply a pure cash flow distribution without uncertainty (and can just be solved with time locked transactions) and contracts which required judicial enforcement (like renting a house) everything else is basically a bet. I believe the stock market will go up you believe it will go down. We make a contract and later an Oracle will decide the outcome. Smart contracts are basically self enforcing bets with no middleman required.
--------------------------------------------------------------------------------
/src/external_web/rpc.js:
--------------------------------------------------------------------------------
1 | var IP = [52, 36, 106, 100];// server
2 | //var IP = [127, 0, 0, 1];
3 | //var Port = 3011;
4 | //var Port = 3030;
5 | var IP = document.URL.split("/")[2].split(":")[0];
6 | function getter(t, u, callback){
7 | t = JSON.stringify(t);
8 | //console.log("getter ".concat(t));
9 | var xmlhttp=new XMLHttpRequest();
10 | xmlhttp.onreadystatechange = callback;
11 | xmlhttp.open("POST",u,true);
12 | xmlhttp.send(t);
13 | return xmlhttp
14 | }
15 | //var PORT = parseInt(document.URL.substring(17, 21), 10);
16 | var PORT = parseInt(document.URL.split(":")[2].substring(0, 4), 10);
17 | function get(t, callback) {
18 | //u = url(PORT - 1, "localhost");
19 | //u = url(PORT, "localhost");
20 | u = url(PORT, IP);
21 | return getter(t, u, callback);
22 | }
23 | function url(port, ip) { return "http://".concat(ip).concat(":").concat(port.toString().concat("/")); }
24 | function local_get(t, callback) {
25 | //u = url(PORT, "localhost");
26 | u = url(PORT, IP);
27 | return getter(t, u, callback);
28 | }
29 | function xml_check(x) { return ((x.readyState === 4) && (x.status === 200)); };
30 | function xml_out(x) { return x.responseText; }
31 | function refresh_helper(x, callback) {
32 | if (xml_check(x)) {callback(xml_out(x));}
33 | else {setTimeout(function() {refresh_helper(x, callback);}, 1000);}
34 | };
35 |
36 | my_status = "nil";
37 | //var x = local_get(["sync", [127,0,0,1], 3020]);
38 | //var x = local_get(["test"]);
39 |
40 | //refresh_helper(x, function(){
41 | // my_status = JSON.parse(xml_out(x));
42 | // console.log("test response ".concat(JSON.stringify(my_status)));
43 | //});
44 | function variable_get(cmd, callback) {
45 | var x = local_get(cmd);
46 | var_get(x, callback);
47 | }
48 | function variable_public_get(cmd, callback) {
49 | var x = get(cmd);
50 | var_get(x, callback);
51 | }
52 | function var_get(x, callback) {
53 | refresh_helper(x, function(){
54 | p = JSON.parse(xml_out(x));
55 | callback(p[1]);
56 | });
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/consensus/packer.erl:
--------------------------------------------------------------------------------
1 | %We should add some rules about which atoms can be used with tuples. If peers can trick us into decoding new atoms, they can overflow erlang with too many atoms.
2 | -module(packer).
3 | -export([pack/1,unpack/1,test/0, untup/1, unpack_helper/1]).
4 | -define(KEY, -6).
5 | untup(X) when is_tuple(X) -> lists:map(fun(Z) ->untup(Z) end, tuple_to_list(X));
6 | untup(X) when is_list(X) -> [?KEY|lists:map(fun(Z)->untup(Z) end,X)];
7 | untup(X) when is_binary(X) -> base64:encode(X);
8 | %untup(X) when is_binary(X) -> X; %bad
9 | untup(X) -> X.
10 | unpack(I) when is_integer(I) -> I;
11 | unpack(JSON) -> unpack_helper(jiffy:decode(JSON)).
12 | unpack_helper(J) when is_binary(J) -> base64:decode(J);
13 | %unpack_helper(J) when is_binary(J) -> J;%bad
14 | unpack_helper(J) when not is_list(J) -> J;
15 | unpack_helper(J) when hd(J) == ?KEY ->
16 | lists:map(fun(X) -> unpack_helper(X) end, tl(J));
17 | unpack_helper(J) ->
18 | K = hd(J),
19 | Out = if
20 | is_binary(K) -> binary_to_atom(K, latin1);
21 | is_integer(K) -> K
22 | end,
23 | list_to_tuple([Out|lists:map(fun(X) -> unpack_helper(X) end, tl(J))]).
24 | pack(X) -> jiffy:encode(untup(X)).
25 | -record(d, {a = "", b = "" }).
26 | test() ->
27 | Record = #d{a=[1, 2, <<"abc">>, [], #d{}], b = <<1,2,3,200, 0:80000>> },
28 | %ABC = {unlock, 24001,1,[{signed,{channel_block,0,3,-9500,3,[],24001,false,259,0,0,0],"TUVZQ0lRQzlwVkxjQ0hReXhpWE0zOU43bVFOS1pTV01WS0MxMkNUYjUwZSs4MkRnd3dJaEFPZG1lWlp0VXdjUXU0UjQzazhRWkREd29tb1BuQ05TWlhDSEl0QU5PemRj",[-6],[-6]],0]],
29 | %New = ["unlock2",24001,1,[["signed",["channel_block",0,3,-9500,3,[-6],24001,false,259,0,0,0],"TUVZQ0lRQzlwVkxjQ0hReXhpWE0zOU43bVFOS1pTV01WS0MxMkNUYjUwZSs4MkRnd3dJaEFPZG1lWlp0VXdjUXU0UjQzazhRWkREd29tb1BuQ05TWlhDSEl0QU5PemRj",[-6],[-6]],0]],
30 | List = [[],3,[4]],
31 | Int = 123,
32 | Int = unpack(pack(Int)),
33 | List = unpack(pack(List)),
34 | true = is_record(unpack(pack(Record)), d),
35 | Record = unpack(pack(Record)),
36 | true = is_binary(pack(Record)),
37 | success.
38 |
--------------------------------------------------------------------------------
/src/consensus/tx_pool.erl:
--------------------------------------------------------------------------------
1 | -module(tx_pool).
2 | -behaviour(gen_server).
3 | %this module holds the txs ready for the next block.
4 | %It needs to use txs:digest to keep track of the Accounts and Channels dicts. This module needs to be ready to share either of those dicts.
5 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2,
6 | absorb/3,absorb_tx/2,dump/0,data/0,test/0]).
7 | -record(f, {txs = [], trees, height = 0}).%block:genesis only stores a single account, so the first time account was updated should be a 1.
8 | init(ok) ->
9 | io:fwrite("tx pool started\n"),
10 | %process_flag(trap_exit, true),
11 | F = state_now(),
12 | {ok, F}.
13 | state_now() ->
14 | B = block:read(top:doit()),
15 | #f{trees = block:trees(B), height = block:height(B)}.
16 |
17 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
18 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
19 | terminate(_, _) -> io:format("tx pool died!"), ok.
20 | handle_info(_, X) -> {noreply, X}.
21 | handle_cast(_, X) -> {noreply, X}.
22 | handle_call(dump, _From, _) -> {reply, 0, state_now()};
23 | handle_call({absorb_tx, NewTrees, Tx}, _From, F) ->
24 | NewTxs = [Tx|F#f.txs],
25 | B = size(term_to_binary(NewTxs)),
26 | MBS = constants:max_block_size(),
27 | FinalTxs = if
28 | B > MBS -> F#f.txs;
29 | true -> NewTxs
30 | end,
31 | {reply, 0, F#f{txs = FinalTxs, trees = NewTrees}};
32 | handle_call({absorb, NewTrees, Txs, Height}, _From, _) ->
33 | {reply, 0, #f{txs = Txs, trees = NewTrees, height = Height}};
34 | handle_call(data, _From, F) -> {reply, {F#f.trees, F#f.height, flip(F#f.txs)}, F}.
35 | data() -> gen_server:call(?MODULE, data). %{accounts, channels, height, txs}
36 | dump() -> gen_server:call(?MODULE, dump).
37 | absorb_tx(Trees, Tx) ->
38 | gen_server:call(?MODULE, {absorb_tx, Trees, Tx}).
39 | absorb(Trees, Txs, Height) ->
40 | gen_server:call(?MODULE, {absorb, Trees, Txs, Height}).
41 | flip(X) -> flip(X, []).
42 | flip([], A) -> A;
43 | flip([H|T], A) -> flip(T, [H|A]).
44 |
45 | test() ->
46 | success.
47 |
--------------------------------------------------------------------------------
/src/web/chat.js:
--------------------------------------------------------------------------------
1 | var chat_button = document.createElement("BUTTON");
2 | chat_button.id = "chat_button";
3 | var t = document.createTextNode("load messages");
4 | chat_button.appendChild(t);
5 | function chat_func() {// variable_get(["msg_peers"], chat_func2); }
6 | //function chat_func2(peers) {
7 | start = -1;
8 | var chat_buddy = parseInt(document.getElementById("talk_address").value, 10);
9 | //console.log(peers[1]);
10 | variable_get(["msg_ids", chat_buddy], function(x) {chat_func3(x, chat_buddy)} );
11 | }
12 | function chat_func3(ids, partner) {
13 | console.log(ids);
14 | msgs = document.getElementById("messages");
15 | //replacedNode = msgs.replaceChild(ul, msgs);
16 | //msgs.replaceChild(ul, msgs);
17 | while (msgs.firstChild) {
18 | msgs.removeChild(msgs.firstChild);
19 | }
20 | chat_func4(ids, partner, 1, ids.length);
21 | }
22 | function chat_func4(ids, partner, N, M) {
23 | console.log("chat 4");
24 | console.log(N);
25 | if (N > M) {
26 | console.log("done");
27 | } else if (ids[N] < start ) {
28 | chat_func4(ids, partner, N + 1, M);
29 | } else if (ids[N] > start ) {
30 | start = ids[N];
31 | variable_get(["read_msg", partner, parseInt(ids[N])], function(msg) {
32 | msgs = document.getElementById("messages");
33 | var li = document.createElement("li");
34 | console.log(msg);
35 | li.innerHTML = atob(msg);
36 | msgs.appendChild(li);
37 | chat_func4(ids, partner, N + 1, M);
38 | }
39 | );
40 | } else {
41 | chat_func4(ids, partner, N+1, M);
42 | }
43 | }
44 |
45 | chat_button.onclick = chat_func;
46 | document.body.appendChild(document.createElement("br"));
47 | document.body.appendChild(chat_button);
48 | var scrollBox = document.createElement("div");
49 | //scrollBox.style = "height:120px;width:120px;border:1px solid #ccc;font:16px/26px Georgia, Garamond, Serif;overflow:auto;";
50 | scrollBox.style = "height:400px;width:500px;border:1px solid #ccc;font:14px/14px Georgia, Garamond, Serif;overflow:auto;";
51 | var ul = document.createElement("ul");
52 | ul.id = "messages";
53 | scrollBox.appendChild(ul);
54 | scrollBox.id = "scrollBox"
55 | document.body.appendChild(scrollBox);
56 |
--------------------------------------------------------------------------------
/src/consensus/testnet_sup.erl:
--------------------------------------------------------------------------------
1 | -module(testnet_sup).
2 | -behaviour(supervisor).
3 | -export([start_link/0,init/1,stop/0]).%,start_http/0]).
4 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
5 | %-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, infinity, Type, [I]}).
6 | start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
7 | -define(keys, [port, keys,
8 | block_hashes, top, block_absorber,
9 | tx_pool, peers, tx_pool_feeder,
10 | mine, channel_manager, channel_feeder]).
11 |
12 | child_maker([]) -> [];
13 | child_maker([H|T]) -> [?CHILD(H, worker)|child_maker(T)].
14 | child_killer([]) -> [];
15 | child_killer([H|T]) ->
16 | supervisor:terminate_child(testnet_sup, H),
17 | child_killer(T).
18 | stop() ->
19 | child_killer(?keys),
20 | halt().
21 | %exit(keys, kill).
22 | %supervisor:terminate_child(testnet_sup, keys).
23 | tree_child(Id, KeySize, Size) ->
24 | tree_child(Id, KeySize, Size, 0).
25 | tree_child(Id, KeySize, Size, Meta) ->
26 | Amount = constants:trie_size(),
27 | Sup = list_to_atom(atom_to_list(Id) ++ "_sup"),
28 | {Sup, {trie_sup, start_link, [KeySize, Size, Id, Amount, Meta, constants:hash_size(), hd]}, permanent, 5000, supervisor, [trie_sup]}.
29 | init([]) ->
30 | KL = constants:key_length(),
31 | HS = constants:hash_size(),
32 | FullLength = KL*2,
33 | BB = constants:balance_bits(),
34 | Children = child_maker(?keys),
35 | HB = constants:height_bits(),
36 | DB = constants:difficulty_bits(),
37 | Tries = [
38 | tree_child(accounts, KL, constants:account_size(), KL*2),
39 | tree_child(channels, KL, constants:channel_size(), KL),
40 | tree_child(existence, FullLength, HS),
41 | tree_child(oracles, KL, ((((KL*2)+(HB*2)+DB) div 8) + 4 + (2*HS)), (KL div 8)),
42 | tree_child(orders, KL, ((KL + (constants:orders_bits()*2) + BB) div 8)),
43 | tree_child(burn, FullLength, (BB div 8) + HS),
44 | tree_child(oracle_bets, KL, (KL + (3 * BB div 8))),
45 | tree_child(shares, KL, (KL + 1 + ((BB + HB) div 8))),
46 | tree_child(governance, 8, 4)
47 | ],
48 | {ok, { {one_for_one, 50000, 1}, Tries ++ Children} }.
49 |
50 |
--------------------------------------------------------------------------------
/docs/insured_crowdfund.md:
--------------------------------------------------------------------------------
1 | It was originally called a "dominant assurance contract".
2 | This is a contract made for raising money for public goods. It is not like an ICO, no new subcurrency is created.
3 |
4 | There are 2 popular ways to raise money for public goods today:
5 | 1) donation
6 | 2) taxation
7 |
8 | The problem with donation is called a "free rider problem".
9 | Some people who benefit from the public good's existence will refuse to donate.
10 |
11 | The problem with taxation is that it involves the threat of violence.
12 | Since violence is involved, some people will end up paying more for the good than how much it benefits them.
13 |
14 | With insured crowdfunding each person ends up paying slightly less for the public good than how much it benefits them, this is the ideal we want.
15 |
16 | There are 3 participants in insured crowdfunding:
17 | 1) entrepenuers
18 | 2) investors
19 | 3) builders
20 |
21 | There are 2 possible results of the crowdfund:
22 | 1) either the public good is successfully created,
23 | 2) or it is not
24 |
25 | Here is a graph showing how much cost and reward each participant can get in each situation.
26 |
27 | Participants Entrepenuer Investor Builders
28 | Cost to play 10 110 0
29 | success 20 0 100
30 | failure 0 120 0
31 |
32 | The entrepenuer's job is to do the research to realize that the benefit of the public good outweighs the cost. If the entrepenuer is wrong, then they lose their money.
33 |
34 | The investor is someone who personally benefits from the creation of the public good. For example, maybe the public good is a new road, and the investor owns a store next to the road. The new road makes the store more valuable.
35 | The investor participates because it is a way to hedge his risks. The investor is buying insurance against the possibility that the road does not get built. Whether the road is built or not, the investor still profits.
36 | If the road is built, then the investor's store is more valuable, a profit.
37 | If the road is not built, then the investor gets their money back plus interest, a profit.
38 |
39 | The builder only gets paid if they successfully build the public good.
--------------------------------------------------------------------------------
/src/consensus/txs/grow_channel_tx.erl:
--------------------------------------------------------------------------------
1 | -module(grow_channel_tx).
2 | -export([doit/3, make/7, good/1]).
3 | -record(gc, {acc1 = 0, acc2 = 0, fee = 0, nonce = 0, inc1 = 0, inc2 = 0, rent = 0, channel_nonce = none, id = -1}).
4 | good(_Tx) ->
5 | %make sure they aren't taking our money.
6 | %check that it is still meeting the min_channel_ratio.
7 | %check that it is a valid transaction.
8 | true.
9 |
10 | make(ID,Accounts,Channels,Inc1,Inc2,Rent,Fee) ->
11 | {_, C, CProof} = channel:get(ID, Channels),
12 | A1 = channel:acc1(C),
13 | A2 = channel:acc2(C),
14 | {_, Acc1, Proof1} = account:get(A1, Accounts),
15 | {_, _, Proof2} = account:get(A2, Accounts),
16 | Nonce = account:nonce(Acc1),
17 | Tx = #gc{id = ID, acc1 = A1, acc2 = A2,
18 | fee = Fee, nonce = Nonce+1, inc1 = Inc1,
19 | inc2 = Inc2, rent = Rent},
20 | {Tx, [CProof, Proof1, Proof2]}.
21 |
22 | doit(Tx,Trees,NewHeight) ->
23 | %it already exists with these two accounts.
24 | Channels = trees:channels(Trees),
25 | Accounts = trees:accounts(Trees),
26 | ID = Tx#gc.id,
27 | {_, OldChannel, _} = channel:get(ID, Channels),
28 | 0 = channel:slasher(OldChannel),
29 | false = channel:closed(OldChannel),
30 | 0 = channel:amount(OldChannel),
31 | Aid1 = channel:acc1(OldChannel),
32 | Aid2 = channel:acc2(OldChannel),
33 | ID = channel:id(OldChannel),
34 | Aid1 = Tx#gc.acc1,
35 | Aid2 = Tx#gc.acc2,
36 | false = Aid1 == Aid2,
37 | Inc1 = Tx#gc.inc1,
38 | Inc2 = Tx#gc.inc2,
39 | true = Inc1 + Inc2 >= 0,
40 | %Rent = Tx#gc.rent,
41 | CNonce = Tx#gc.channel_nonce,
42 | 0 = channel:amount(OldChannel),
43 | NewChannel = channel:update(0, ID, Channels, CNonce, Inc1, Inc2, 0, channel:delay(OldChannel), NewHeight, false, []),
44 | NewChannels = channel:write(NewChannel, Channels),
45 | Acc1 = account:update(Aid1, Accounts, -Inc1, Tx#gc.nonce, NewHeight),
46 | Acc2 = account:update(Aid2, Accounts, -Inc2, none, NewHeight),
47 | Accounts2 = account:write(Accounts, Acc1),
48 | NewAccounts = account:write(Accounts2, Acc2),
49 | Trees2 = trees:update_channels(Trees, NewChannels),
50 | trees:update_accounts(Trees2, NewAccounts).
51 |
52 |
53 |
--------------------------------------------------------------------------------
/channel_test.sh:
--------------------------------------------------------------------------------
1 |
2 | #First start the two servers:
3 | # sh start.sh 3010
4 | # sh start.sh 3020
5 | # Make sure each server is running from code copied into a different folder. It is important that they each maintain different trie databases, and don't share a trie.
6 |
7 | #test seperately with channel_solo_close or close_channel to finish it off.
8 | #test seperately with channel_solo_close and parts of internal_handler:doit({dice...}) missing.
9 |
10 |
11 | curl -i -d '["add_peer", [127,0,0,1], 3010]' http://localhost:3021
12 | curl -i -d '["add_peer", [127,0,0,1], 3020]' http://localhost:3011
13 |
14 | #curl -i -d '["mine_block", 1, 1000000]' http://localhost:3011
15 | #curl -i -d '["mine_block", 1, 1000000]' http://localhost:3011
16 | #sleep 1
17 | #curl -i -d '["sync", [127,0,0,1], 3020]' http://localhost:3011
18 | #sleep 1
19 | curl -i -d '["mine_block", 1, 1000000]' http://localhost:3021
20 | sleep 1
21 | curl -i -d '["sync", [127,0,0,1], 3020]' http://localhost:3011
22 | sleep 1
23 |
24 | curl -i -d '["new_channel", [127,0,0,1], 3020, 1, 10000, 4000, 0, 4, 5]' http://localhost:3011
25 | sleep 1
26 | #curl -i -d '["sync", [127,0,0,1], 3020]' http://localhost:3011
27 | #sleep 1
28 | #curl -i -d '["mine_block", 1, 1000000]' http://localhost:3011
29 | #sleep 1
30 |
31 |
32 | curl -i -d '["sync", [127,0,0,1], 3020]' http://localhost:3011
33 | sleep 1
34 | curl -i -d '["channel_spend", [127,0,0,1], 3020, 27]' http://localhost:3011
35 | sleep 1
36 | #curl -i -d '["sync", [127,0,0,1], 3020]' http://localhost:3011
37 | #sleep 1
38 |
39 | curl -i -d '["dice", 1000, [127,0,0,1], 3020]' http://localhost:3011 # "dice"
40 | sleep 1
41 |
42 | #curl -i -d '["channel_solo_close", 2]' http://localhost:3011 # "dice"
43 | #sleep 1
44 |
45 | #curl -i -d '["mine_block", 1, 1000000]' http://localhost:3021
46 | #sleep 1
47 |
48 | curl -i -d '["close_channel", [127,0,0,1], 3020]' http://localhost:3011
49 | #sleep 1
50 | #curl -i -d '["mine_block", 1, 1000000]' http://localhost:3021
51 | #sleep 1
52 | #curl -i -d '["sync", [127,0,0,1], 3010]' http://localhost:3021
53 | #sleep 1
54 |
55 | #At the end of this test, the channel managers should still be storing the channels as if they were not closed. That way, if we are on a fork and it turns out the channel never got closed, we still have the channel data available to try closing again.
56 | # Channel manager should have some sort of garbage collection to clean up old channels like this.
57 |
--------------------------------------------------------------------------------
/src/web/tester2.js:
--------------------------------------------------------------------------------
1 | function local_get2(t, callback) {
2 | u = url(PORT + 11, "localhost");
3 | return getter(t, u, callback);
4 | }
5 |
6 | local_get(["new_pubkey", btoa("abc")]);
7 |
8 | variable_get(["pubkey"], function(pubkey) {
9 | local_get2(["create_account", pubkey, 2000000, 50]);
10 | wait_for_id();
11 | })
12 | function wait_for_id() {
13 | local_get(["sync", [127,0,0,1], 3020]);
14 | console.log("wait for id");
15 | variable_get(["id"], new_channel);
16 | }
17 |
18 | function new_channel(id) {
19 | if (id == -1) {variable_get(["id"], new_channel);}
20 | else {
21 | console.log("new channel");
22 | console.log("id is ");
23 | console.log(id);
24 | local_get(["new_channel", [127,0,0,1], 3020, 1120000, 1100000, 50]);
25 | console.log("after new channel");
26 | variable_get(["channel_keys"], buy_block);
27 | }
28 | }
29 | function buy_block(keys) {
30 | if (keys == [-6]) {variable_get(["channel_keys"], channel_spend);}
31 | else {
32 | console.log("channel spend");
33 | console.log("keys ");
34 | console.log(keys);
35 | local_get2(["buy_block"]);
36 | setTimeout(sync, 100);
37 | }
38 | }
39 | function sync() {
40 | console.log("channel spend 2");
41 | local_get(["sync", [127,0,0,1], 3020]);
42 | //setTimeout(lightning_spend, 1000);
43 | setTimeout(function() {variable_get(["id"], message);}, 100);
44 | //setTimeout(message, 1000);
45 | }
46 | function message(id) {
47 | console.log("send message");
48 | local_get(["send_msg", [127,0,0,1], 3020, id, btoa("test message"), 6]);
49 | setTimeout(get_message, 100);
50 | }
51 | function get_message() {
52 | console.log("get message");
53 | local_get(["get_msg", [127,0,0,1], 3020]);
54 | setTimeout(function() {variable_get(["id"], read_message);}, 100);
55 | }
56 | function read_message(id) {
57 | console.log("read message");
58 | variable_get(["read_msg", id, 0], read_message2);
59 | }
60 | function read_message2(message) {
61 | console.log("read message 2");
62 | document.getElementById("main").innerHTML = atob(message);
63 | }
64 |
65 |
66 | function channel_spend() {
67 | //spends 100.
68 | local_get(["channel_spend", [127,0,0,1], 3020, 100]);
69 |
70 | }
71 | function lightning_spend() {
72 | var partner = 1;
73 | var amount = 200;
74 | console.log("channel spend 3");
75 | local_get(["lightning_spend", [127,0,0,1], 3020, partner, amount]);
76 | //message();
77 | }
78 |
--------------------------------------------------------------------------------
/src/consensus/txs/oracle_close_tx.erl:
--------------------------------------------------------------------------------
1 | -module(oracle_close_tx).
2 | -export([test/0, make/4, doit/3]).
3 | -record(oracle_close, {from, nonce, fee, oracle_id}).
4 | %If there is a lot of open orders for one type of share in an oracle for a long enough period of time, then this transaction can be done.
5 | %This ends betting in the market.
6 | %The fee that was used to start the oracle is the final bet included. It bets against the winning outcome.
7 | make(From, Fee, OID, Accounts) ->
8 | {_, Acc, _} = account:get(From, Accounts),
9 | Tx = #oracle_close{from = From, fee = Fee, oracle_id = OID, nonce = account:nonce(Acc) + 1},
10 | {Tx, []}.
11 | doit(Tx, Trees, NewHeight) ->
12 | Accounts = trees:accounts(Trees),
13 | Acc = account:update(Tx#oracle_close.from, Accounts, -Tx#oracle_close.fee, Tx#oracle_close.nonce, NewHeight),
14 | NewAccounts = account:write(Accounts, Acc),
15 |
16 | OID = Tx#oracle_close.oracle_id,
17 | Oracles = trees:oracles(Trees),
18 | {_, Oracle, _} = oracles:get(OID, Oracles),
19 | true = oracles:starts(Oracle) < NewHeight,
20 | %if the volume of orders in the oracle is too low, then set the oracle:type to 3.
21 | Result = oracles:type(Oracle),
22 | Oracle2 = oracles:set_result(Oracle, Result),
23 | Oracle3 = oracles:set_done_timer(Oracle2, NewHeight),
24 | io:fwrite("after setting result "),
25 | io:fwrite(packer:pack(Oracle3)),
26 | io:fwrite("\n"),
27 | Oracles2 = oracles:write(Oracle3, Oracles),
28 | Trees2 = trees:update_oracles(Trees, Oracles2),
29 | Gov = oracles:governance(Oracle3),
30 | Trees3 =
31 | case Gov of
32 | 0 -> Trees2;
33 | G ->
34 | GA = oracles:governance_amount(Oracle3),
35 | case Result of
36 | 1 ->
37 | true = oracles:done_timer(Oracle3) < NewHeight,
38 | Gov2=governance:change(G, GA, Gov),
39 | trees:update_governance(Gov2, Trees2);
40 | 2 ->
41 | true = oracles:done_timer(Oracle3) < NewHeight,
42 | Gov2=governance:change(G, -GA,Gov),
43 | trees:update_governance(Gov2, Trees2);
44 | 3 ->
45 | true = oracle:starts(Oracle3) + constants:maximum_oracle_time() < NewHeight,
46 | Trees2
47 | end
48 | end,
49 | OraclesEE = trees:oracles(Trees3),
50 | {_, Oracle4, _} = oracles:get(OID, OraclesEE),
51 | io:fwrite("after setting result 2 "),
52 | io:fwrite(packer:pack(Oracle4)),
53 | io:fwrite("\n"),
54 | trees:update_accounts(Trees3, NewAccounts).
55 | test() ->
56 | success.
57 |
--------------------------------------------------------------------------------
/lightning_test.sh:
--------------------------------------------------------------------------------
1 | #this quickly tests lightning payments. It is a lot faster and easier than using the browser to test the same thing.
2 |
3 | #first run lightning_test_setup to copy the software into 2 other directories.
4 | #Open up 3 terminals.
5 | #Copy the Flying Fox install directory so you have 3 copies.
6 | #Set all 3 passwords to "abc". Use the command keys:new("abc"), then copy keys.db to keys_backup.
7 | #Launch one using port 3010, one on 3020, and one on 3030.
8 | #Then run this script from a fourth terminal.
9 |
10 | #It lightning spends 4 coins one way, then spends the same 4 back.
11 |
12 | curl -i -d '["key_unlock", "YWJj"]' http://localhost:3011
13 | curl -i -d '["key_unlock", "YWJj"]' http://localhost:3021
14 | curl -i -d '["key_unlock", "YWJj"]' http://localhost:3031
15 |
16 | curl -i -d '["keys_id_update", 1]' http://localhost:3021
17 | curl -i -d '["keys_id_update", 2]' http://localhost:3031
18 | curl -i -d '["keys_id_update", 0]' http://localhost:3011
19 |
20 | curl -i -d '["create_account", "QkpiMzFWb2U2a0hkWGxFbVRkSzhueGVPeHAvVUs3enJUdFRaMWFWUmxFL2d4TldqMlJlYngrQm1IZ0RuVGU4MHNxMWZSTVBSWGpFNW55N2N3OWU0ckF3PQ==", 10000000, 0]' http://localhost:3011
21 | curl -i -d '["create_account", "QkpiMzFWb2U2a0hkWGxFbVRkSzhueGVPeHAvVUs3enJUdFRaMWFWUmxFL2d4TldqMlJlYngrQm1IZ0RuVGU4MHNxMWZSTVBSWGpFNW55N2N3OWU0ckF3PQ==", 10000000, 0]' http://localhost:3011
22 | curl -i -d '["sync", [127,0,0,1], 3030]' http://localhost:3011
23 | curl -i -d '["sync", [127,0,0,1], 3030]' http://localhost:3021
24 |
25 | curl -i -d '["new_channel", [127,0,0,1], 3030, 1000000, 900000, 50]' http://localhost:3011
26 | curl -i -d '["sync", [127,0,0,1], 3030]' http://localhost:3011
27 | curl -i -d '["sync", [127,0,0,1], 3030]' http://localhost:3021
28 | curl -i -d '["new_channel", [127,0,0,1], 3030, 500000, 450000, 50]' http://localhost:3021
29 | curl -i -d '["sync", [127,0,0,1], 3030]' http://localhost:3021
30 | curl -i -d '["sync", [127,0,0,1], 3030]' http://localhost:3011
31 |
32 |
33 | curl -i -d '["lightning_spend", [127,0,0,1], 3030, 1, 4]' http://localhost:3011
34 | sleep 1
35 | curl -i -d '["get_msg", [127,0,0,1], 3030]' http://localhost:3021
36 | sleep 1
37 | curl -i -d '["get_msg", [127,0,0,1], 3030]' http://localhost:3011
38 | sleep 1
39 |
40 | curl -i -d '["lightning_spend", [127,0,0,1], 3030, 0, 4]' http://localhost:3021
41 | sleep 1
42 | curl -i -d '["get_msg", [127,0,0,1], 3030]' http://localhost:3011
43 | sleep 1
44 | curl -i -d '["get_msg", [127,0,0,1], 3030]' http://localhost:3021
45 | sleep 1
46 |
47 |
--------------------------------------------------------------------------------
/src/consensus/txs/channel_team_close_tx.erl:
--------------------------------------------------------------------------------
1 | %If you did not get slashed, and you waited delay since channel_timeout, then this is how you close the channel and get the money out.
2 |
3 | -module(channel_team_close_tx).
4 | -export([doit/3, make/6, acc1/1, acc2/1, fee/1, amount/1,
5 | sum_share_amounts/1]).
6 | -record(ctc, {aid1 = 0, aid2 = 0, fee = 0,
7 | nonce = 0, id = 0, amount = 0,
8 | shares}).
9 | amount(Tx) -> Tx#ctc.amount.
10 | fee(Tx) -> Tx#ctc.fee.
11 | acc1(Tx) -> Tx#ctc.aid1.
12 | acc2(Tx) -> Tx#ctc.aid2.
13 | make(ID,Accounts,Channels,Amount,Shares,Fee) ->
14 | {_, C, CProof} = channel:get(ID, Channels),
15 | A1 = channel:acc1(C),
16 | A2 = channel:acc2(C),
17 | {_, Acc1, Proof1} = account:get(A1, Accounts),
18 | {_, _, Proof2} = account:get(A2, Accounts),
19 | Nonce = account:nonce(Acc1),
20 | Tx = #ctc{id = ID, aid1 = A1, aid2 = A2,
21 | fee = Fee, nonce = Nonce+1,
22 | amount = Amount, shares = Shares},
23 | {Tx, [CProof, Proof1, Proof2]}.
24 |
25 | doit(Tx,Trees,NewHeight) ->
26 | Channels = trees:channels(Trees),
27 | Accounts = trees:accounts(Trees),
28 | ID = Tx#ctc.id,
29 | {_, OldChannel, _} = channel:get(ID, Channels),
30 | false = channel:closed(OldChannel),
31 | Aid1 = channel:acc1(OldChannel),
32 | Aid2 = channel:acc2(OldChannel),
33 | %ID = channel:id(OldChannel),
34 | Aid1 = Tx#ctc.aid1,
35 | Aid2 = Tx#ctc.aid2,
36 | false = Aid1 == Aid2,
37 | NewChannels = channel:delete(ID, Channels),
38 | Bal1 = channel:bal1(OldChannel),
39 | Bal2 = channel:bal2(OldChannel),
40 | ShareAmount = sum_share_amounts(Tx#ctc.shares) div 2,
41 | Amount = Tx#ctc.amount,
42 | true = Bal1 + Bal2 >= ShareAmount * 2,
43 | true = Bal1 + Amount - ShareAmount > 0,
44 | true = Bal2 - Amount - ShareAmount > 0,
45 | Acc1 = account:update(Aid1, Accounts, Bal1 + Amount - ShareAmount, Tx#ctc.nonce, NewHeight),
46 | Acc1a = account:send_shares(Acc1, Tx#ctc.shares, NewHeight),
47 | Acc2 = account:update(Aid2, Accounts, Bal2 - Amount - ShareAmount, none, NewHeight),
48 | Acc2a = account:receive_shares(Acc2, Tx#ctc.shares, NewHeight),
49 | Accounts2 = account:write(Accounts, Acc1a),
50 | NewAccounts = account:write(Accounts2, Acc2a),
51 | Trees2 = trees:update_channels(Trees, NewChannels),
52 | trees:update_accounts(Trees2, NewAccounts).
53 |
54 | sum_share_amounts([]) -> 0;
55 | sum_share_amounts([H|T]) ->
56 | shares:amount(H)+
57 | sum_share_amounts(T).
58 |
--------------------------------------------------------------------------------
/src/consensus/fractions.erl:
--------------------------------------------------------------------------------
1 | -module(fractions).
2 | -export([new/2,negate/1,add/2,subtract/2,multiply/2,divide/2,to_int/1,test/0, multiply_int/2, exponent/2, less_than/2, equal/2, is_fraction/1,sqrt/1]).
3 | -record(f, {top = 0, bottom = 0}).
4 | is_fraction(X) when not is_record(X, f) -> false;
5 | is_fraction({f, _, Y}) when not is_integer(Y) -> false;
6 | is_fraction({f, Y, _}) when not is_integer(Y) -> false;
7 | is_fraction({f, _, Y}) when Y == 0 -> false;
8 | is_fraction(_) -> true.
9 | sqrt({f, A, B}) ->
10 | sqrt_helper({f, A, B}, {f, 1, 2}).
11 | sqrt_helper(A, Guess) ->
12 | B = subtract(A, multiply(Guess, Guess)),
13 | Bool = (less_than(B, {f, 1, 1000}) and (not less_than(B, {f, -1, 1000}))), %correct to 8 decimal places.
14 | if
15 | Bool -> Guess;
16 | true ->
17 | Sum = add(Guess, divide(A, Guess)),
18 | Improved = divide(Sum, {f, 2, 1}),
19 | sqrt_helper(A, Improved)
20 | end.
21 | equal(A, B) ->
22 | A#f.top * B#f.bottom == B#f.top * A#f.bottom.
23 | less_than(A, B) ->
24 | A#f.top * B#f.bottom < B#f.top * A#f.bottom.
25 | new(T,B) -> #f{top = T, bottom = B}.
26 | negate(A) -> #f{top = -A#f.top, bottom = A#f.bottom}.
27 | subtract(A, B) -> add(A, negate(B)).
28 | add(A, B) -> simplify(#f{top = (A#f.top * B#f.bottom) + (A#f.bottom * B#f.top) , bottom = A#f.bottom * B#f.bottom}).
29 | multiply(A, B) -> simplify(#f{top = A#f.top * B#f.top, bottom = A#f.bottom * B#f.bottom}).
30 | divide(A, B) -> simplify(#f{top = A#f.top * B#f.bottom, bottom = A#f.bottom * B#f.top}).
31 | to_int(A) -> A#f.top div A#f.bottom.
32 | multiply_int(F, I) -> F#f.top * I div F#f.bottom.
33 | simplify(F) -> simplify_lcd(simplify_size(F)).
34 | simplify_lcd(F) ->
35 | L = lcd(F#f.top, F#f.bottom),
36 | #f{top = F#f.top div L, bottom = F#f.bottom div L}.
37 | simplify_size(F) ->
38 | IC = 281474976710656,
39 | %X = F#f.bottom div IC,
40 | %Y = F#f.top div IC,
41 | Z = if
42 | ((F#f.bottom > IC) and (F#f.top > IC)) -> IC;
43 | true -> 1
44 | end,
45 | #f{top = F#f.top div Z, bottom = F#f.bottom div Z}.
46 | exponent(_, 0) -> #f{top = 1, bottom = 1};
47 | exponent(F, 1) -> F;
48 | exponent(F, N) when N rem 2 == 0 ->
49 | exponent(multiply(F, F), N div 2);
50 | exponent(F, N) -> multiply(F, exponent(F, N - 1)).
51 | lcd(A, 0) -> A;
52 | lcd(A, B) -> lcd(B, A rem B).
53 | test() ->
54 | A = new(1, 3),
55 | B = new(2, 5),
56 | C = multiply(A, B),
57 | C = new(2, 15),
58 | B = divide(C, A),
59 | 9 = lcd(27, 9),
60 | 5 = lcd(25, 15),
61 | success.
62 |
63 |
--------------------------------------------------------------------------------
/docs/oracle_off_chain.md:
--------------------------------------------------------------------------------
1 | In other documents, we discussed why the oracle will be a market, and how this market will work.
2 |
3 | We could build this market on-chain, but that would come with serious anti-scalability costs.
4 | We would have to remember the sorted list of open orders orders at many different block heights.
5 |
6 | Here you can read about how the oracle can be built off-chain.
7 |
8 | The steps are like this:
9 | 1) Someone commits to running the oracle market. They allow anyone to make channels with them and bet.
10 | 2) The miners make multiple bets in the market to cancel their risk. The miners do this anonymously.
11 | 3) The oracle periodically publishes the prices of the different kinds of shares in the oracle. The act of publishing a price closes many open orders made in many different channels. It closes all the orders that agreed to trade at that price.
12 | 4) Once the price stays stable for a long enough period of time, the coorelation of the oracle's resolution with the difficulty is computed, and from it the blockchain learns the true fact about our world.
13 |
14 |
15 |
16 | Attack vectors to analyse
17 | 1) What if the oracle refuses to take any bets in the market, then the oracle has no incentive to write the correct prices.
18 | * The miners make multiple bets in the market to cancel their risk. The miners do this anonymously via onion lightning. They do enough bets to have statistical confidence about the percentage of bets that are censored.
19 | * If too many of the miner's bets are censored (> ~95%), then the miners censor the oracle from publishing the result.
20 |
21 | 2) What if the oracle write the wrong price at the last minute, so the volume of trades is small?
22 | * The oracle doesn't settle until the price stays in one range for a long time.
23 |
24 | 3) What if the oracle censors just enough votes so that half the miners split onto one side of a fork, and the other half are on the other side of a fork?
25 | *There are 3 possible outcomes:
26 | *1 >95% of bets are censored, in which case we censor the oracle, miners refuse to build on top of blocks that include oracle transactions.
27 | *2 Less than 80% of bets are cesored, in which case we don't censor the oracle, and we have a strong preference for the side of the fork that publishes the oracle result. It doesn't matter the height at which a fork includes the oracle transactions.
28 | *3 Between 80% and 95% are censored, in which case the miners accept whichever side of the fork is longer without preference, and they censor the oracle txs from the blocks they mine.
--------------------------------------------------------------------------------
/src/consensus/txs/channel_slash_tx.erl:
--------------------------------------------------------------------------------
1 |
2 |
3 | -module(channel_slash_tx).
4 | -export([doit/3, make/6]).
5 | -record(cs, {from, nonce, fee = 0,
6 | scriptpubkey, scriptsig}).
7 | make(From, Fee, ScriptPubkey, ScriptSig, Accounts,Channels) ->
8 | SPK = testnet_sign:data(ScriptPubkey),
9 | CID = spk:cid(SPK),
10 | {_, Acc, Proof1} = account:get(From, Accounts),
11 | {_, Channel, Proofc} = channel:get(CID, Channels),
12 | Acc1 = channel:acc1(Channel),
13 | Acc2 = channel:acc2(Channel),
14 | Accb = case From of
15 | Acc1 -> Acc2;
16 | Acc2 -> Acc1
17 | end,
18 | {_, _, Proof2} = account:get(Accb, Accounts),
19 | Tx = #cs{from = From, nonce = account:nonce(Acc)+1,
20 | fee = Fee,
21 | scriptpubkey = ScriptPubkey,
22 | scriptsig = ScriptSig},
23 | {Tx, [Proof1, Proof2, Proofc]}.
24 |
25 | doit(Tx, Trees, NewHeight) ->
26 | Channels = trees:channels(Trees),
27 | Accounts = trees:accounts(Trees),
28 | From = Tx#cs.from,
29 | %CID = Tx#cs.cid,
30 | SignedSPK = Tx#cs.scriptpubkey,
31 | SPK = testnet_sign:data(SignedSPK),
32 | CID = spk:cid(SPK),
33 | {_, OldChannel, _} = channel:get(CID, Channels),
34 | CA = channel:amount(OldChannel),
35 | false = CA == 0,
36 | %Channel = channel:update(CID, Channels, none, 0,0,channel:mode(OldChannel), channel:delay(OldChannel), NewHeight),
37 | true = testnet_sign:verify(SignedSPK, Accounts),
38 | Acc1 = channel:acc1(OldChannel),
39 | Acc2 = channel:acc2(OldChannel),
40 | Acc1 = spk:acc1(SPK),
41 | Acc2 = spk:acc2(SPK),
42 | true = channel:entropy(OldChannel) == spk:entropy(SPK),
43 | %Mode = channel:mode(OldChannel),
44 | Fee = Tx#cs.fee,
45 | Nonce = Tx#cs.nonce,
46 | {Amount, NewCNonce, Shares} = spk:run(fast, Tx#cs.scriptsig, SPK, NewHeight, 1, Accounts, Channels),
47 | false = Amount == 0,
48 | true = NewCNonce > channel:nonce(OldChannel),
49 | %delete the channel. empty the channel into the accounts.
50 | %NewChannels = channel:delete(CID, Channels),
51 | true = (-1 < (channel:bal1(OldChannel)-Amount)),%channels can only delete money that was inside the channel.
52 | true = (-1 < (channel:bal2(OldChannel)+Amount)),
53 | NewChannel = channel:update(From, CID, Channels, NewCNonce, 0, 0, Amount, spk:delay(SPK), NewHeight, false, Shares),
54 | NewChannels = channel:write(NewChannel, Channels),
55 | ID = Tx#cs.from,
56 | Account = account:update(ID, Accounts, -Fee, Nonce, NewHeight),
57 | NewAccounts = account:write(Accounts, Account),
58 | Trees2 = trees:update_channels(Trees, NewChannels),
59 | trees:update_accounts(Trees2, NewAccounts).
60 |
61 |
--------------------------------------------------------------------------------
/src/channels/channel_notes.md:
--------------------------------------------------------------------------------
1 | Stuff:
2 |
3 | We need to remember the highest-nonced transaction that we signed for our peer. previously called "channel_partner.erl"
4 | We need to remember the highest-nonced transaction that our peer signed for us. previously called "channel_manager.erl" And the highest-nonced scriptpubkey that goes along with it.
5 | we need to remember pairs of channels that are linked by hashlocks. previously called "arbitrage.erl"
6 |
7 | We can't let any of this state to crash, so the gen_server should do the minimum computation possible.
8 | We also don't want to update our channel state in parallel, because an attacker could update the same state channel multiple times at once to steal our money.
9 | We need every update to include a before, and after state. That way we wont ever accept contradictory updates.
10 | So we need another gen_server to update the channels one at a time. previously called "channel_manager_feeder.erl"
11 |
12 | gen_servers:
13 | channel_manager, arbitrage, channel_feeder
14 |
15 | channel_manager:
16 | read/1, delete/1, store/2
17 |
18 | arbitrage:
19 | add{bet_hash, cid1, cid2}, del{bet_hash, cid1, cid2}, read/1{bet_hash}
20 |
21 | channel_feeder:
22 | make{Tx}, grow{Tx} current{cid}, spend{spk}, close{cid, script_sig}, lock-spend/1{spk}, bet/1{spk}
23 | %before doing anything to a channel, we need to double-check that the channel exists on the top chain. It is possible a fork could happen, and the channel manager is storing a channel that doesn't exist on the chain.
24 |
25 | %This would be a lot more efficient if we only recorded diffs and signatures, instead of passing the entire spk every time.
26 |
27 | Low-level actions:
28 |
29 | 1) request the minimum channel balance ratio supported by your partner.
30 | 2) make a new channel or grow an existing channel. The amount of money you put into the channel divided by how much they put in must be greater than their minimum channel balance ratio.
31 | 3) ask partner for a recent copy of the channel_state.
32 | 4) give your channel-partner money.
33 | 5) close a channel together.
34 | 6) make/modify a bet to give your partner >=0 value, and optionally have them forward the bet to someone else %locked payment
35 | 7) Show a scriptsig to your partner to prove that you can close the channel at a particular state. Then you and your partner cooperate to simplify the channel state. Your partner will use arbitrage to close any linked channels at the same height.
36 | 8) ask for the list of bets that this node would participate in.
37 | 9) make any of the bets from list (8).
38 |
39 | high-level actions (built on top of low-level actions above):
40 |
41 | * lightning payments.
42 | * bets by lightning.
43 | * moving a bet to a shorter path.
--------------------------------------------------------------------------------
/src/external_web/hexbase64.js:
--------------------------------------------------------------------------------
1 | if (!window.atob) {
2 | var tableStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3 | var table = tableStr.split("");
4 |
5 | window.atob = function (base64) {
6 | if (/(=[^=]+|={3,})$/.test(base64)) throw new Error("String contains an invalid character");
7 | base64 = base64.replace(/=/g, "");
8 | var n = base64.length & 3;
9 | if (n === 1) throw new Error("String contains an invalid character");
10 | for (var i = 0, j = 0, len = base64.length / 4, bin = []; i < len; ++i) {
11 | var a = tableStr.indexOf(base64[j++] || "A"), b = tableStr.indexOf(base64[j++] || "A");
12 | var c = tableStr.indexOf(base64[j++] || "A"), d = tableStr.indexOf(base64[j++] || "A");
13 | if ((a | b | c | d) < 0) throw new Error("String contains an invalid character");
14 | bin[bin.length] = ((a << 2) | (b >> 4)) & 255;
15 | bin[bin.length] = ((b << 4) | (c >> 2)) & 255;
16 | bin[bin.length] = ((c << 6) | d) & 255;
17 | };
18 | return String.fromCharCode.apply(null, bin).substr(0, bin.length + n - 4);
19 | };
20 |
21 | window.btoa = function (bin) {
22 | for (var i = 0, j = 0, len = bin.length / 3, base64 = []; i < len; ++i) {
23 | var a = bin.charCodeAt(j++), b = bin.charCodeAt(j++), c = bin.charCodeAt(j++);
24 | if ((a | b | c) > 255) throw new Error("String contains an invalid character");
25 | base64[base64.length] = table[a >> 2] + table[((a << 4) & 63) | (b >> 4)] +
26 | (isNaN(b) ? "=" : table[((b << 2) & 63) | (c >> 6)]) +
27 | (isNaN(b + c) ? "=" : table[c & 63]);
28 | }
29 | return base64.join("");
30 | };
31 |
32 | }
33 |
34 | function hexToBase64(str) {
35 | return btoa(String.fromCharCode.apply(null,
36 | str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))
37 | );
38 | }
39 |
40 | function base64ToHex(str) {
41 | for (var i = 0, bin = atob(str.replace(/[ \r\n]+$/, "")), hex = []; i < bin.length; ++i) {
42 | var tmp = bin.charCodeAt(i).toString(16);
43 | if (tmp.length === 1) tmp = "0" + tmp;
44 | hex[hex.length] = tmp;
45 | }
46 | return hex.join(" ");
47 | }
48 |
49 | onload = function () {
50 |
51 | var _d = document, elementCache = {}, $ = function (id) {
52 | return elementCache[id] ? elementCache[id] : elementCache[id] = _d.getElementById(id);
53 | };
54 |
55 | $("tobase64").onclick = function () {
56 | try {
57 | $("base64").value = hexToBase64($("hex").value);
58 | } catch (e) {
59 | alert("error");
60 | throw e;
61 | }
62 | };
63 |
64 | $("tohex").onclick = function () {
65 | try {
66 | var str = base64ToHex($("base64").value);
67 | $("hex").value = $("isuppercase").checked ? str.toUpperCase() : str;
68 | } catch (e) {
69 | alert("error");
70 | throw e;
71 | }
72 | };
73 |
74 | };
75 |
76 |
--------------------------------------------------------------------------------
/docs/questions.md:
--------------------------------------------------------------------------------
1 | How do state channels work?
2 | You can read this document I helped create: http://www.altheamesh.com/blog/universal-payment-channels/
3 | Also, Jeff Coleman has a good explanation: http://www.jeffcoleman.ca/state-channels/
4 |
5 | Each channel is capable of doing any type of smart contract.
6 | A typical user will have only one channel, and they will do all their smart contracts through the same channel.
7 |
8 | Only the 2 participants of a channel keep a record of the channel state, they can update it as many times as they want.
9 |
10 | How does the consensus mechanism of state channel works?
11 | How do miners distinguish the true contract in the case of disagreement?
12 | When you process the smart contract, it outputs 3 numbers:
13 | How much money goes to the first party,
14 | How much money goes to the second party,
15 | And a nonce.
16 | If both parties disagree about the final state, the blockchain prefers the state that has a higher nonce.
17 | So every time we make a payment in the channel, we increment the nonce to be one higher.
18 |
19 | will the miners, who don't reach a consensus with the majority be punished?
20 | The blockchain uses proof of work mining, similar to bitcoin. The incentive is to mine on the longest valid chain.
21 | If you mine on a shorter chain, then you probably wont get a block reward.
22 |
23 | Will the counterparty, who doesn't execute the contract be punished?
24 | The smart contract is turing complete. you can program it to punish users for failing to participate properly.
25 |
26 | How long will a dispute be solved?
27 | The state channel is programmable, you can decide how long you want the dispute period to last.
28 |
29 |
30 | How does the oracle consensus work? How can the oracle answer all kinds of questions?
31 | here are several documents describing the oracle:
32 | https://github.com/aeternity/testnet/blob/master/docs/oracle_motivations.md
33 | https://github.com/aeternity/testnet/blob/master/docs/oracle_simple.md
34 | https://github.com/aeternity/testnet/blob/master/docs/oracle_design.md
35 |
36 | The oracle does need to lock money. We can use dominant assurance contracts, which I like to call "insured crowdfunding" to raise money to run the oracle.
37 | An oracle is a public good, raising money for public goods is usually done in one of 2 ways: either by charity or taxation.
38 | The problem with charity is that people tend to pay less money than how much the good would benefit them.
39 | The problem with taxation is that people tend to pay more money than how much the good would benefit them.
40 | Insured crowdfunding is a contract designed so that each person is incentivized to give as much money to the good as how much the good benefits them.
41 |
42 | How do I solve the problem were if all the miners just follow the first one who give the answer?
43 | Miners don't answer these questions at all. It is a market based oracle. The price of the market determines the outcome of the oracle.
--------------------------------------------------------------------------------
/src/consensus/txs/new_channel_tx.erl:
--------------------------------------------------------------------------------
1 | -module(new_channel_tx).
2 | -export([doit/3, make/10, good/1, spk/2, cid/1,
3 | entropy/1, acc1/1, acc2/1, id/1]).
4 | -record(nc, {acc1 = 0, acc2 = 0, fee = 0, nonce = 0,
5 | bal1 = 0, bal2 = 0, rent = 0, entropy = 0,
6 | delay = 10, id = -1}).
7 |
8 | acc1(X) -> X#nc.acc1.
9 | acc2(X) -> X#nc.acc2.
10 | id(X) -> X#nc.id.
11 | good(Tx) ->
12 | %make sure that the money is a fair balance of ours and theirs.
13 | Delay = Tx#nc.delay,
14 | true = Delay > free_constants:min_channel_delay(),
15 | true = Delay < free_constants:max_channel_delay(),
16 | K = keys:id(),
17 | Acc1 = Tx#nc.acc1,
18 | Acc2 = Tx#nc.acc2,
19 | Bal1 = Tx#nc.bal1,
20 | Bal2 = Tx#nc.bal2,
21 | Top = case K of
22 | Acc1 ->
23 | Bal1;
24 | Acc2 ->
25 | Bal2;
26 | X -> X = Acc1
27 | end,
28 | Frac = Top / (Bal1 + Bal2),
29 | MCR = free_constants:min_channel_ratio(),
30 | Frac < MCR.
31 | cid(Tx) -> Tx#nc.id.
32 | entropy(Tx) -> Tx#nc.entropy.
33 | spk(Tx, Delay) -> spk:new(Tx#nc.acc1, Tx#nc.acc2, Tx#nc.id,
34 | [], 0,0, Delay, 0,
35 | Tx#nc.entropy).
36 | make(ID,Accounts,Acc1,Acc2,Inc1,Inc2,Rent,Entropy,Delay, Fee) ->
37 | {_, A, Proof} = account:get(Acc1, Accounts),
38 | Nonce = account:nonce(A),
39 | {_, _, Proof2} = account:get(Acc2, Accounts),
40 | %Entropy = channel_feeder:entropy(ID, [Acc1, Acc2])+1,
41 | true = (Rent == 0) or (Rent == 1),
42 | Tx = #nc{id = ID, acc1 = Acc1, acc2 = Acc2,
43 | fee = Fee, nonce = Nonce+1, bal1 = Inc1,
44 | bal2 = Inc2, entropy = Entropy, rent = Rent,
45 | delay = Delay
46 | },
47 | {Tx, [Proof, Proof2]}.
48 |
49 | doit(Tx, Trees, NewHeight) ->
50 | Channels = trees:channels(Trees),
51 | Accounts = trees:accounts(Trees),
52 | ID = Tx#nc.id,
53 | {_, OldChannel, _} = channel:get(ID, Channels),
54 | true = case OldChannel of
55 | empty -> true;
56 | X ->
57 | channel:closed(OldChannel)
58 | and ((NewHeight - channel:last_modified(OldChannel)) > constants:channel_closed_time())
59 | end,
60 | Aid1 = Tx#nc.acc1,
61 | Aid2 = Tx#nc.acc2,
62 | false = Aid1 == Aid2,
63 | Bal1 = Tx#nc.bal1,
64 | true = Bal1 >= 0,
65 | Bal2 = Tx#nc.bal2,
66 | true = Bal2 >= 0,
67 | Rent = Tx#nc.rent,
68 | Entropy = Tx#nc.entropy,
69 | Delay = Tx#nc.delay,
70 | NewChannel = channel:new(ID, Aid1, Aid2, Bal1, Bal2, NewHeight, Entropy, Delay),
71 | NewChannels = channel:write(NewChannel, Channels),
72 | CCFee = constants:create_channel_fee() div 2,
73 | Acc1 = account:update(Aid1, Accounts, -Bal1-CCFee, Tx#nc.nonce, NewHeight),
74 | Acc2 = account:update(Aid2, Accounts, -Bal2-CCFee, none, NewHeight),
75 | Accounts2 = account:write(Accounts, Acc1),
76 | NewAccounts = account:write(Accounts2, Acc2),
77 | Trees2 = trees:update_channels(Trees, NewChannels),
78 | trees:update_accounts(Trees2, NewAccounts).
79 |
--------------------------------------------------------------------------------
/docs/threshold_signature_anonimity.md:
--------------------------------------------------------------------------------
1 | Democracy is a popular idea today. Many people want to use voting in their cryptocurrency consensus mechanisms.
2 | The reason we haven't been able to use voting so far is because the incentive to vote honestly decreases by the square of the portion of coins that you own. So the cost of bribing a voter to be malicious is much smaller than how many coins the voter controls.
3 | Threshold signature schemes have been proposed as a way to overcome this limitation. If you cannot know how a person voted, then you cannot know whether to bribe them.
4 |
5 | The goal of this paper is to prove that threshold signatures scheme can not be good enough.
6 | It is mathematically impossible for them to have the anonymity properties we need.
7 |
8 | Example: Alice, Bob, Charlie, Dan, and Evan are participating in a 3 out of 5 threshold signature scheme. Alice, Bob, and Charlie want to sign, Dan and Evan do not want to sign. Alice, Bob, and Charlie want to prove that they signed.
9 | If threshold signatures were good enough for anonymous voting, that would mean it is impossible for any of Alice, Bob, Charlie, Dan or Evan to prove how they participated, even if they all reveal their private keys.
10 |
11 | Each person can choose to contribute to the signature. So there are up to 5 signature pieces that are created, which when combined form a valid signature. Lets call them: A, B, C, D, E.
12 |
13 | Because Alice, Bob, and Charlie want to prove that they signed, they show their signature pieces A, B, and C to the attacker first. Before they even show these pieces to each other.
14 | That way the attacker can use A, B, and C to generate the signature. The attacker can publish the signature that is stored on-chain, so the attacker is certain that A, B, and C were the pieces used to make the signature. The attacker is certain that A, B, and C weren't generated by users who were too late to matter.
15 |
16 | If Alice, Bob, and Charlie's private keys are revealed, then it is possible for the attacker to re-create the signature pieces. During this recreation process the attacker learns which signature pieces were generated from which user, and he learns which private keys were not used to generate any of the pieces he knows about.
17 |
18 | The threshold signature scheme involves an algorithm that combines signature pieces into a signature.
19 | We can keep testing this algorithm on different subsets of signature pieces.
20 | Combine(A, B, C, D, E) produces a valid signature.
21 | Combine(A, B, C) produces a valid signature.
22 | Combine(A, B, D) produces an invalid signature.
23 | Combine(A, B, E) produces an invalid signature.
24 |
25 | So, we can prove which signature pieces are valid, and which are not.
26 |
27 | The attacker knows which valid signature piece came from which person,
28 | and he also knows which signature pieces are valid,
29 | therefore it is possible for participants to prove that they participated.
30 | therefore threshold signatures are not anonymous enough to be used for blockchain consensus.
--------------------------------------------------------------------------------
/docs/oracle_manipulation.md:
--------------------------------------------------------------------------------
1 | What if someone payed money into the market that we use as an oracle to try to get it to lie?
2 |
3 | Example: we were betting on a football game, but now the game is over and we all know that Alice won.
4 | The correct outcome is that decision A is 99.99% likely to be the fork of the blockchain with high difficulty, and decision B is 0.01% likely to be the fork with high difficulty.
5 | An attacker keeps buying B to push the price above 50%.
6 | Defenders show up to buy shares of A at one-to-one odds.
7 | The attackers own lots of tokens on fork A, and update their software to use fork A.
8 | The defenders own lots of tokens on fork B, and update their software to use fork B.
9 | Everyone else prefers the fork that is honest, because they want a fork with a working oracle.
10 | It is the shelling point.
11 |
12 | When the price is 50-50, the act of defending has this return rate:
13 |
14 | `1-(interest_rate*finality)`
15 |
16 | Where interest_rate is the market rate you need to pay someone to be willing to hold Aeons for a time and be unable to access them.
17 | finality is the amount of time until the bets pay off.
18 | Defenders will only defend if they earn a profit, so, the oracle is only secure when
19 |
20 | `(interest_rate*finality) < 1` .
21 |
22 |
23 | Blockchains are small and unstable, so the interest rate should be a very high amount of 10% per month, which is about 0.3% per day.
24 |
25 | The amount of time until bets can pay off needs to be longer than the difficulty retargetting period.
26 | That way the blockchain can know the demand for coins.
27 | In bitcoin, the retargetting period is 2000 blocks, which is about a week.
28 | So lets set our finality to 7 days = about 2.5% interest
29 |
30 | Plugging in to the formula:
31 |
32 | `(0.025 * 1) < 1`
33 |
34 | Which is true. So it is secure.
35 |
36 |
37 |
38 |
39 | Besides using markets for telling us true/false binary things, we also ask the oracle questions with less certainty. For example: "If we double the size of blocks, will that make the coins more or less valuable?"
40 | Depending on the price, we update the protocol.
41 | How valuable does a change need to be for us to be able to measure it?
42 |
43 | Lets say the price should be X, which is between 0.5 and 0.
44 | An attacker keeps pushing the price to 0.51, so change the outcome and force the protocol to update how he likes.
45 | The defenders profit for defending is
46 |
47 | `1 - (2 * X) - (interest_rate*finality)`
48 |
49 | `= 1 - (2 * X) - 0.025`
50 |
51 | The defender will only defend if this is positive, so
52 |
53 | `0.975 - 2X > 0`
54 |
55 | `X < 0.975 / 2`
56 |
57 | `X < 0.4875`
58 |
59 | So the attacker can only push the price a maximum of `(interest_rate*finality)/2`
60 | If he pushes more than that, then it becomes profitable to defend against the manipulation.
61 |
62 | If we only have < 2% confidence that decision A is better than B, then it isn't important which decision we make. It doesn't matter if we cannot prevent such tiny manipulations.
63 |
--------------------------------------------------------------------------------
/src/web/login.js:
--------------------------------------------------------------------------------
1 | document.body.appendChild(document.createElement("br"));
2 | variable_get(["key_status"], function(x) {login_1(x)});
3 | function login_1(x) {
4 | if ( x == btoa("locked") ) {
5 | login_locked();
6 | } else if ( x == btoa("empty") ) {
7 | login_new();
8 | } else if ( x == btoa("unlocked") ) {
9 | login_unlocked();
10 | }
11 | }
12 | function login_0(x) {
13 | if ( x == btoa("locked") ) {
14 | var con = document.createElement("p");
15 | con.innerHTML = "password no good";
16 | document.body.appendChild(con);
17 | } else if ( x == btoa("unlocked") ) {
18 | login_unlocked();
19 | }
20 | }
21 | function login_new() {
22 | console.log("empty option");
23 | var new_password = document.createElement("INPUT");
24 | new_password.setAttribute("type", "text");
25 | var amount_info = document.createElement("h8");
26 | amount_info.innerHTML = "new password: ";
27 | document.body.appendChild(amount_info);
28 | document.body.appendChild(new_password);
29 |
30 | var new_password_check = document.createElement("INPUT");
31 | new_password_check.setAttribute("type", "text");
32 | var fee_info = document.createElement("h8");
33 | fee_info.innerHTML = "confirm new password: ";
34 | document.body.appendChild(fee_info);
35 | document.body.appendChild(new_password_check);
36 |
37 | var spend_button = document.createElement("BUTTON");
38 | spend_button.id = "spend_button";
39 | var spend_button_text = document.createTextNode("spend");
40 | spend_button.appendChild(spend_button_text);
41 | spend_button.onclick = function() {
42 | if (new_password.value == new_password_check.value) {
43 | local_get(["key_new", btoa(new_password.value)]);
44 | variable_get(["key_status"], function(x) {login_0(x)});
45 | } else {
46 | var con = document.createElement("p");
47 | con.innerHTML = "passwords don't match";
48 | document.body.appendChild(con);
49 | }
50 | };
51 | document.body.appendChild(spend_button);
52 | }
53 | function login_unlocked() {
54 | console.log("unlocked option");
55 | var con = document.createElement("a");
56 | con.href = "/main.html"
57 | con.innerHTML = "continue";
58 | document.body.appendChild(document.createElement("br"));
59 | document.body.appendChild(con);
60 | }
61 | function login_locked() {
62 | var password = document.createElement("INPUT");
63 | password.setAttribute("type", "text");
64 | var password_info = document.createElement("h8");
65 | password_info.innerHTML = "password: ";
66 | document.body.appendChild(password_info);
67 | document.body.appendChild(password);
68 |
69 | var login_button = document.createElement("BUTTON");
70 | login_button.id = "login_button";
71 | var login_button_text = document.createTextNode("continue");
72 | login_button.appendChild(login_button_text);
73 | login_button.onclick = function() {
74 | local_get(["key_unlock", btoa(password.value)]);
75 | variable_get(["key_status"], function(x) {login_0(x)});
76 | };
77 | document.body.appendChild(login_button);
78 | }
79 |
--------------------------------------------------------------------------------
/src/consensus/trees/oracle_bets.erl:
--------------------------------------------------------------------------------
1 | -module(oracle_bets).
2 | -export([test/0, new/3, increase/3, id/1,
3 | true/1, false/1, bad/1,
4 | write/2, get/2, root_hash/1, add_bet/4,
5 | to_shares/3, delete/2,
6 | remove_bet/3]).
7 | %Each account has a tree of oracle bets. Oracle bets are not transferable. Once an oracle is settled, the bets in it can be converted to shares.
8 | -record(bet, {id, true, false, bad}).%true, false, and bad are the 3 types of shares that can be purchased from an oracle
9 | -define(name, oracle_bets).
10 | to_shares(Bet, Correct, NewHeight) ->
11 | %returns {Shares, Tokens}
12 | ID = Bet#bet.id,
13 | {Positive, Negative} =
14 | case Correct of
15 | 1->{Bet#bet.true,Bet#bet.false+Bet#bet.bad};
16 | 2->{Bet#bet.false,Bet#bet.true+Bet#bet.bad};
17 | 3->{Bet#bet.bad,Bet#bet.true+Bet#bet.false}
18 | end,
19 | [shares:new(ID, Positive, NewHeight), shares:new(ID, -Negative, NewHeight)].
20 | id(X) ->
21 | X#bet.id.
22 | true(X) ->
23 | X#bet.true.
24 | false(X) ->
25 | X#bet.false.
26 | bad(X) ->
27 | X#bet.bad.
28 | increase(X, Type, A) ->
29 | case Type of
30 | true -> X#bet{true = X#bet.true + A};
31 | false -> X#bet{false = X#bet.false + A};
32 | bad -> X#bet{bad = X#bet.bad + A}
33 | end.
34 | new(ID, Type, Amount) ->
35 | {A, B, C} =
36 | case Type of
37 | 1 -> {Amount, 0, 0};
38 | 2 -> {0, Amount, 0};
39 | 3 -> {0, 0, Amount}
40 | end,
41 | new(ID, A, B, C).
42 |
43 | new(OracleID, True, False, Bad) ->
44 | %{_, X, _} = active_oracles:read(OracleID, AORoot),
45 | %false = X == empty,
46 | #bet{id = OracleID, true = True, false = False,
47 | bad = Bad}.
48 | serialize(X) ->
49 | KL = constants:key_length()*8,
50 | BAL = constants:balance_bits(),
51 | <<(X#bet.id):KL,
52 | (X#bet.true):BAL,
53 | (X#bet.false):BAL,
54 | (X#bet.bad):BAL>>.
55 | deserialize(B) ->
56 | KL = constants:key_length()*8,
57 | BAL = constants:balance_bits(),
58 | <> = B,
59 | #bet{true = True, false = False, bad = Bad, id = ID}.
60 | write(X, Tree) ->
61 | Key = X#bet.id,
62 | Z = serialize(X),
63 | trie:put(Key, Z, 0, Tree, ?name).
64 | get(ID, Tree) ->
65 | {X, Leaf, Proof} = trie:get(ID, Tree, ?name),
66 | V = case Leaf of
67 | empty -> empty;
68 | L -> deserialize(leaf:value(L))
69 | end,
70 | {X, V, Proof}.
71 | delete(ID, Tree) ->
72 | trie:delete(ID, Tree, ?name).
73 | add_bet(Id, Type, Amount, Tree) ->
74 | {_, X, _} = get(Id, Tree),
75 | Y = case X of
76 | empty -> new(Id, Type, Amount);
77 | Bet -> increase(Bet, Type, Amount)
78 | end,
79 | write(Y, Tree).
80 | remove_bet(_, _, _) ->
81 | ok.
82 |
83 | root_hash(A) ->
84 | trie:root_hash(?name, A).
85 |
86 | test() ->
87 | C = new(1, 3, 100),
88 | ID = C#bet.id,
89 | {_, empty, _} = get(ID, 0),
90 | Root = write(C, 0),
91 | {_, C, _} = get(ID, Root),
92 | {_, empty, _} = get(ID, 0),
93 | Tree2 = add_bet(ID, true, 100, Root),
94 | {_, Bet2, _} = get(ID, Tree2),
95 | Bet2 = increase(C, true, 100),
96 | success.
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/consensus/txs/spk.erl:
--------------------------------------------------------------------------------
1 | -module(spk).
2 | -export([acc1/1,acc2/1,entropy/1,
3 | bets/1,space_gas/1,time_gas/1,
4 | new/10,delay/1,cid/1,amount/1,
5 | nonce/1,apply_bet/4,get_paid/3,
6 | run/7,settle_bet/3, slash_reward/1]).
7 | -record(spk, {acc1, acc2, entropy,
8 | bets, space_gas, time_gas,
9 | delay, cid, amount = 0, nonce = 0,
10 | slash_reward = 0}).
11 | %scriptpubkey is the name that Satoshi gave to this part of the transactions in bitcoin.
12 | %This is where we hold the channel contracts. They are turing complete smart contracts.
13 | %Besides the SPK, there is the ScriptSig. Both participants of the channel sign the SPK, only one signs the SS.
14 |
15 | acc1(X) -> X#spk.acc1.
16 | acc2(X) -> X#spk.acc2.
17 | bets(X) -> X#spk.bets.
18 | delay(X) -> X#spk.delay.
19 | entropy(X) -> X#spk.entropy.
20 | space_gas(X) -> X#spk.space_gas.
21 | time_gas(X) -> X#spk.time_gas.
22 | cid(X) -> X#spk.cid.
23 | amount(X) -> X#spk.amount.
24 | nonce(X) -> X#spk.nonce.
25 | slash_reward(X) -> X#spk.slash_reward.
26 |
27 |
28 | new(Acc1, Acc2, CID, Bets, SG, TG, Delay, Nonce, Entropy, SR) ->
29 | %Entropy = chnnel_feeder:entropy(CID, [Acc1, Acc2])+1,
30 | #spk{acc1 = Acc1, acc2 = Acc2, entropy = Entropy,
31 | bets = Bets, space_gas = SG, time_gas = TG,
32 | delay = Delay, cid = CID, nonce = Nonce,
33 | slash_reward = SR}.
34 |
35 | apply_bet(Bet, SPK, Time, Space) ->
36 | %bet is binary, the SPK portion of the script.
37 | %SPK is the old SPK, we output the new one.
38 | SPK#spk{bets = [Bet|SPK#spk.bets],
39 | nonce = SPK#spk.nonce + 1,
40 | time_gas = SPK#spk.time_gas + Time,
41 | space_gas = SPK#spk.space_gas + Space}.
42 | settle_bet(SPK, Bets, Amount) ->
43 | SPK#spk{bets = Bets, amount = Amount, nonce = SPK#spk.nonce + 1}.
44 | get_paid(SPK, MyID, Amount) -> %if Amount is positive, that means money is going to Aid2.
45 | Aid1 = SPK#spk.acc1,
46 | Aid2 = SPK#spk.acc2,
47 | D = case MyID of
48 | Aid1 -> -1;
49 | Aid2 -> 1;
50 | _ -> MyID = Aid1
51 | end,
52 | SPK#spk{amount = (SPK#spk.amount + (D*Amount)),
53 | nonce = SPK#spk.nonce + 1}.
54 |
55 | run(Mode, SS, SPK, Height, Slash, Accounts, Channels) ->
56 | State = chalang:new_state(0, Height, Slash, 0, Accounts, Channels),
57 | {Amount, NewNonce, CodeShares, _, _} = run2(Mode, SS, SPK, State),
58 | true = NewNonce < 1000,
59 | Shares = shares:from_code(CodeShares),
60 | {Amount + SPK#spk.amount, NewNonce + (1000 * SPK#spk.nonce), Shares}.
61 | run2(fast, SS, SPK, State) ->
62 | chalang:run(SS,
63 | SPK#spk.bets,
64 | SPK#spk.time_gas,
65 | SPK#spk.space_gas,
66 | constants:fun_limit(),
67 | constants:var_limit(),
68 | State);
69 | run2(safe, SS, SPK, State) ->
70 | %will not crash. if the thread that runs the code crashes, or takes too long, then it returns {-1,-1,-1,-1}
71 | S = self(),
72 | spawn(fun() ->
73 | X = run2(fast, SS, SPK, State),
74 | S ! X
75 | end),
76 | spawn(fun() ->
77 | timer:sleep(5000),%wait enough time for the chalang contracts to finish
78 | S ! {-1,-1,-1,-1, -1}
79 | end),
80 | receive
81 | Z -> Z
82 | end.
83 |
84 |
85 |
--------------------------------------------------------------------------------
/src/networking/peers.erl:
--------------------------------------------------------------------------------
1 | -module(peers).
2 | -behaviour(gen_server).
3 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2,
4 | update/4,all/0,add/2,add/1,read/2,remove/2,
5 | update_score/3, initial_score/0,
6 | cid/1,set_cid/3]).
7 | -record(r, {height =0, hash=0, cid, score=100000}).%lower score is better.
8 | cid(X) -> X#r.cid.
9 | init(ok) -> {ok, default_peers()}.
10 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
11 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
12 | terminate(_, _) -> io:format("died!"), ok.
13 | handle_info(_, X) -> {noreply, X}.
14 | handle_cast({remove, IP, Port}, X) ->
15 | K = key(IP, Port),
16 | NewX = dict:remove(K, X),
17 | {noreply, NewX};
18 | handle_cast({add, IP, Port}, X) ->
19 | NewX = load_peers([{IP, Port}], X),
20 | {noreply, NewX};
21 | handle_cast({score, IP, Port, N}, X) ->
22 | K = key(IP, Port),
23 | {ok, Old} = dict:find(K, X),
24 | %A = Old#r.score,
25 | %B = (A*99+N) div 100,
26 | NewR = Old#r{score = N},
27 | NewX = dict:store(K, NewR, X),
28 | {noreply, NewX};
29 | handle_cast({set_cid, IP, Port, CID}, X) ->
30 | K = key(IP, Port),
31 | {ok, Old} = dict:find(K, X),
32 | New = Old#r{cid = CID},
33 | NewX = dict:store(K, New, X),
34 | {noreply, NewX};
35 | handle_cast({update, IP, Port, Height, Hash}, X) ->
36 | K = key(IP, Port),
37 | {ok, Old} = dict:find(K, X),
38 | NewX = if
39 | Height > Old#r.height ->
40 | A = Old#r{height = Height, hash = Hash},
41 | dict:store(K, A, X);
42 | true ->
43 | X
44 | end,
45 | {noreply, NewX}.
46 | handle_call(all, _From, X) ->
47 | {reply, dict:fetch_keys(X), X};
48 | handle_call({read, IP, Port}, _From, X) ->
49 | K = key(IP, Port),
50 | O = case dict:find(K, X) of
51 | error -> <<"none">>;
52 | {ok, Val} -> Val
53 | end,
54 | {reply, O, X}.
55 | set_cid(IP, Port, CID) ->
56 | gen_server:cast(?MODULE, {set_cid, IP, Port, CID}).
57 | key(IP, Port) -> {IP, Port}.
58 | all() -> gen_server:call(?MODULE, all).
59 | add([]) -> ok;
60 | add([[IP, Port]|T]) ->
61 | add(IP, Port),
62 | add(T);
63 | add([{IP, Port}|T]) ->
64 | add(IP, Port),
65 | add(T).
66 | add(IP, Port) ->
67 | NIP = if
68 | is_tuple(IP) -> IP;
69 | is_list(IP) -> list_to_tuple(IP)
70 | end,
71 | gen_server:cast(?MODULE, {add, NIP, Port}).
72 | update_score(IP, Port, N) ->
73 | gen_server:cast(?MODULE, {score, IP, Port, N}).
74 |
75 | update(IP, Port, Height, Hash) ->
76 | gen_server:cast(?MODULE, {update, IP, Port, Height, Hash}).
77 | remove(IP, Port) -> gen_server:cast(?MODULE, {remove, IP, Port}).
78 | read(IP, Port) -> gen_server:call(?MODULE, {read, IP, Port}).
79 |
80 | default_peers() ->
81 | D = constants:peers(),
82 | load_peers(D, dict:new()).
83 | load_peers([], D) -> D;
84 | load_peers([{IP, Port}|T], Dict) ->
85 | K = key(IP, Port),
86 | %don't re-write the same peer twice.
87 | D2 = case dict:find(K, Dict) of
88 | error ->
89 | dict:store(K, #r{}, Dict);
90 | _ -> Dict
91 | end,
92 | load_peers(T, D2).
93 |
94 | initial_score() -> 100000.
95 |
--------------------------------------------------------------------------------
/docs/oracle_design.md:
--------------------------------------------------------------------------------
1 | We use a trie to store all the questions that have been asked of the oracle.
2 | We use another trie to store the answers, for any questions that were answered.
3 |
4 | For questions that are in the process of being answered, we store a market in the on-chain state.
5 | The market remembers how many shares of each type have been sold, and it remembers what it's initial liquidity was, and it should have an order-book.
6 | The market has 6 possible outcomes:
7 | 1) difficulty goes up, and oracle's outcome is true
8 | 2) difficulty goes up, and oracle's outcome is false
9 | 3) difficulty goes down, and oracle's outcome is true
10 | 4) difficulty goes down, and oracle's outcome is false
11 | 5) difficulty goes up, and the oracle's outcome is bad_question
12 | 6) difficulty goes down, and the oracle's outcome is bad_question
13 |
14 | Each market should have a trie for storing orders in the order book, and it should have an accounts trie, to remember how much money each account has.
15 |
16 | All the market trie roots should be combined as a merkle trie into the block, so that as the number of markets grows by N, the increase in cost is only O(log(N)).
17 |
18 | We also need to keep a record of how many open orders exist at every price, that way the block maker can't censor any trades.
19 |
20 | in total, that was 6 tries:
21 | * questions. each block has a root
22 | * answers. each block has a root of this
23 | * order book trades
24 | * how many shares each account owns
25 | * a trie that stores pairs, containing one each of the previous 2 types of trie roots, an order book trie root, and a shares-accounts trie root. each block has a root of this.
26 | * how many open orders at each price
27 |
28 | The roots in each block should combine to make a single root that is stored in the block hash.
29 |
30 |
31 |
32 | the result of the oracle is measured by looking at the correlation between the outcomes.
33 | We sum the diagonals, and see which way is bigger.
34 | If the correlation is in the same direction for enough blocks, then that is the oracle's result.
35 | Since we use an order book, it is expensive for attackers to DDOS us by moving the price past 50% every time the oracle is almost done.
36 |
37 |
38 | tx types:
39 | AskQuestion
40 | OracleBet
41 | OracleFinish
42 | CollectReward
43 |
44 | AskQuestion
45 | This costs a big deposit. If the question is answered in the expected way, you get most of the deposit back.
46 | If they decide it is a bad question, then your deposit is deleted.
47 |
48 | OracleBet
49 | This is how you make a bet in the on-chain market, which measures the correlation between the answers to oracle questions, and the mining difficulty. It is a LMSR order book, so you say how many shares for the lowest price you are willing to sell at, or how many shares for the highest price you are willing to buy at.
50 |
51 | AnswerQuestion
52 | If the oracle's correlation has shown with high enough margin for a long enough period of time that one answer is preferable, then anyone can do this transaction to permanently record the answer to the question.
53 |
54 | CollectReward
55 | If you made a winning bet, this is how you collect your winnings.
56 |
57 |
58 |
59 |
60 | The initial liquidity will be collected using an off-chain dominant assurance contract.
--------------------------------------------------------------------------------
/src/consensus/txs/channel_timeout_tx.erl:
--------------------------------------------------------------------------------
1 | -module(channel_timeout_tx).
2 | -export([doit/3, make/6]).
3 | -record(timeout, {aid = 0, nonce = 0, fee = 0, cid = 0, shares}).
4 | %If your partner is not helping you, this is how you start the process of closing the channel.
5 | %You should only use the final channel-state, or else your partner can punish you for cheating.
6 | make(ID,Accounts,Channels,CID,Shares,Fee) ->
7 | %shares is a list of shares.
8 | %The root hash of this list must match the hash stored in the channel
9 | {_, Acc, Proof} = account:get(ID, Accounts),
10 | {_, Channel, Proofc} = channel:get(CID, Channels),
11 | Acc1 = channel:acc1(Channel),
12 | Acc2 = channel:acc2(Channel),
13 | Accb = case ID of
14 | Acc1 -> Acc2;
15 | Acc2 -> Acc1
16 | end,
17 | {_, _, Proof2} = account:get(Accb, Accounts),
18 | Nonce = account:nonce(Acc),
19 | Tx = #timeout{aid = ID, nonce = Nonce + 1,
20 | fee = Fee, cid = CID, shares = Shares},
21 | {Tx, [Proof, Proof2, Proofc]}.
22 |
23 | doit(Tx, Trees, NewHeight) ->
24 | Accounts = trees:accounts(Trees),
25 | Channels = trees:channels(Trees),
26 | From = Tx#timeout.aid,
27 | CID = Tx#timeout.cid,
28 | {_, Channel, _} = channel:get(CID, Channels),
29 | CS = channel:shares(Channel),
30 | true = shares:root_hash(CS)
31 | == shares:root_hash(shares:write_many(Tx#timeout.shares, 0, CS)),
32 | %make sure that the sum of shares in Tx#timeout.shares and the amount spent by the channel sum up to less than how much money was in the channel.
33 | false = channel:closed(Channel),
34 | CA = channel:amount(Channel),
35 | %false = CA == 0,
36 | LM = channel:last_modified(Channel),
37 | TD = NewHeight - LM,
38 | true = TD >= channel:delay(Channel),
39 | %Mode = channel:mode(Channel),
40 | Aid1 = channel:acc1(Channel),
41 | Aid2 = channel:acc2(Channel),
42 | Amount = channel:amount(Channel),
43 | Fee = Tx#timeout.fee,
44 | %SR = channel:slash_reward(Channel),
45 | ShareAmount = channel_team_close_tx:sum_share_amounts(Tx#timeout.shares) div 2,
46 | Bal1 = channel:bal1(Channel),
47 | Bal2 = channel:bal2(Channel),
48 | %io:fwrite("channel timeout "),
49 | %io:fwrite({ShareAmount, Bal1, Bal2}),
50 | true = Bal1 + Bal2 >= ShareAmount * 2,
51 | true = Bal1 + Amount - ShareAmount > 0,
52 | true = Bal2 - Amount - ShareAmount > 0,
53 | Acc1 = account:update(Aid1, Accounts, Bal1-Amount - ShareAmount, none, NewHeight),
54 | Acc1a = account:send_shares(Acc1, Tx#timeout.shares, NewHeight),
55 | Acc2 = account:update(Aid2, Accounts, Bal2+Amount - ShareAmount, none, NewHeight),
56 | Acc2a = account:receive_shares(Acc2, Tx#timeout.shares, NewHeight),
57 | Accounts2 = account:write(Accounts, Acc1a),
58 | Accounts3 = account:write(Accounts2, Acc2a),
59 | Slasher = channel:slasher(Channel),
60 | Acc4 = account:update(From, Accounts3, -Fee, none, NewHeight),
61 | NewAccounts = account:write(Accounts3, Acc4),
62 | NewChannel = channel:update(Slasher, CID, Channels, none, 0, 0, CA, channel:delay(Channel), NewHeight, true, []),
63 | NewChannels = channel:write(NewChannel, Channels),
64 | %NewChannels = channel:delete(CID, Channels),
65 | Trees2 = trees:update_channels(Trees, NewChannels),
66 | trees:update_accounts(Trees2, NewAccounts).
67 |
68 |
--------------------------------------------------------------------------------
/src/web/tester.js:
--------------------------------------------------------------------------------
1 | //It is messy because each server has 2 ports. One for internal commands, and one for external.
2 | //The test is only between 2 nodes for now.
3 | /*
4 | function get2(t, callback) {
5 | u = url(PORT + 10, "localhost");
6 | return getter(t, u, callback);
7 | }
8 | function local_get2(t, callback) {
9 | u = url(PORT + 11, "localhost");
10 | return getter(t, u, callback);
11 | }
12 |
13 | function variable_get2(cmd, callback) {
14 | var x = local_get2(cmd);
15 | var_get(x, callback);
16 | }
17 | function variable_public_get2(cmd, callback) {
18 | var x = get2(cmd);
19 | var_get(x, callback);
20 | }
21 | */
22 | local_get(["new_pubkey", btoa("abc")]);
23 |
24 | variable_get(["pubkey"], function(pubkey) {
25 | local_get2(["create_account", pubkey, 1000000, 50]);
26 | wait_for_id();
27 | })
28 | function wait_for_id() {
29 | local_get(["sync", [127,0,0,1], 3020]);
30 | console.log("wait for id");
31 | variable_get(["id"], new_channel);
32 | }
33 | function new_channel(id) {
34 | if (id == -1) {variable_get(["id"], new_channel);}
35 | else {
36 | console.log("new channel creator");
37 | console.log("new id ".concat(id));
38 | //the channel needs to be signed by both participants before it can be published.
39 | variable_get2(["create_channel", id, 112030, 0, btoa("delegated_1"), 50], function(ch) {
40 | console.log("talking to 2");
41 | console.log(ch);
42 | variable_get(["sign", ch], function(ch2) {
43 | console.log(ch2);
44 | variable_get2(["sign", ch2], function(ch3) {
45 | console.log(ch3);
46 | get(["tx_absorb", ch3]);
47 | get2(["tx_absorb", ch3]);
48 | wait_for_channel_id();
49 | });
50 | });
51 | });
52 | }
53 | }
54 |
55 | function wait_for_channel_id() {
56 | local_get(["sync", [127,0,0,1], 3020]);
57 | console.log("wait for channel id");
58 | variable_get(["channel_id", 0], function(chid) {
59 | console.log("channel id ".concat(chid));
60 | if (chid == [-6]) {wait_for_channel_id();}
61 | else {channel_spend(chid[1]);}
62 | });
63 | }
64 |
65 | function channel_spend(chid) {
66 | console.log("channel spend id ".concat(chid));
67 | variable_get(["channel_spend", chid, -10000], function(ch) {
68 | console.log("channel spend ".concat(ch));
69 | variable_public_get2(["channel_recieve", chid, -10001, ch],
70 | function(return_ch) {
71 | console.log("return_ch ".concat(return_ch));
72 | get(["channel_recieve", chid, 9999, return_ch]);
73 | hashlock(chid);
74 | })
75 | });
76 | }
77 |
78 | function hashlock(chid) {
79 | //hash(1) == "qfPbi+pbLu+SN+VICd2kPwwhj4DQ0yijP1tM//8zSOY="
80 | console.log("hashlock");
81 | variable_get(
82 | ["hashlock", chid, 1000, "+5mXXEquHXT+Xs6TOGU8lH/GbQbiH2+4IdQq0pe5EtA="],
83 | //"qfPbi+pbLu+SN+VICd2kPwwhj4DQ0yijP1tM//8zSOY="],
84 | function(ch) {
85 | console.log("hashlock 2 ".concat(ch));
86 | variable_public_get2(
87 | ["channel_locked_payment", chid, ch],
88 | function(return_ch) {
89 | console.log("return_ch ".concat(return_ch));
90 | local_get(["spend_locked_payment", chid, return_ch]);
91 | unlock(chid);
92 | })
93 | });
94 | }
95 | function unlock(chid) {
96 | console.log("unlock1");
97 | secret = "AQIDBA==";
98 | variable_public_get2(
99 | ["unlock", chid, secret],
100 | function(ch) {
101 | console.log("unlock2");
102 | console.log(ch);
103 | variable_public_get(
104 | ["unlock2", chid, secret, ch],
105 | function(ch2) {
106 | console.log("unlock3");
107 | console.log(ch2);
108 | get2(["unlock2", chid, secret, ch2]);
109 | });
110 | });
111 | }
112 |
--------------------------------------------------------------------------------
/docs/oracle_motivations.md:
--------------------------------------------------------------------------------
1 |
2 | Existing oracle mechanisms are built on top of voting. Voting can not adequately solve the oracle problem in a useful way. Betting mechanisms do not suffer this same limitations.
3 |
4 |
5 |
6 | Users want to use blockchains to gamble on future events. They want to gamble on the price of goods, for example.
7 |
8 | An oracle consensus mechanism is a game on a blockchain designed so that the players are incentivized to reveal true facts about the world to the blockchain. Once the blockchain learns facts about the world, it can resolve the bets. Winning gamblers get their money, losing gamblers lose their money.
9 |
10 | The oracle consensus mechanism's rewards and punishments are all measured in tokens.
11 |
12 | USEFUL PRINCIPLE: For an oracle to be useful, it needs to give accurate information about the outside world, even when the amount of money being gambled on the oracle's result is much bigger than the amount of money in the oracle mechanism.
13 |
14 | Existing oracle consensus mechanisms are built as voting mechanisms that can punish/reward the voters.
15 | Voting mechanisms do not work.
16 |
17 | Lets examine a voter Bob who controls 10% of the money in the oracle.
18 | His vote controls 10% of the oracle's decision.
19 | At most, his punishment for lying is the amount of money he has in the oracle.
20 |
21 | If we offer him a bribe that is bigger than how much he could lose from lying, he will lie.
22 | So, if we are willing to bribe more than the total amount of money in the oracle, we could get the oracle to lie.
23 | By the useful principle, the total money in the oracle is much less than the amount being gambled.
24 | So, it is profitable to bribe the validators this way and attack the oracle.
25 |
26 |
27 |
28 | Now lets examine betting mechanisms.
29 | There is a famous experiment. A prediction market with only N money of initial liquidity in it is created.
30 | The outcome of the prediction market will be determined by the flip of a coin.
31 | Some betters are offered a prize. If they can keep the price above 70% chance of heads, then they get 10N of prize money.
32 | The prediction market would still say 50% chance of heads.
33 |
34 | Every time the betters where were bribed moved the price up to 70%, it would create an opportunity.
35 | For $0.40, anyone could buy an asset that has 50% chance of being worth $1. On average, a 20% profit.
36 |
37 | The manipulators in comparison are paying $0.60 for an asset that has a 50% chance of being worth $1.
38 | Every time they manipulate the price, they are giving money away as a prize to undo their manipulation.
39 |
40 | So, a betting market can give accurate answers, even if some users are incentivized to make it lie.
41 | Even if the incentive to make the market lie are bigger than the total amount of money in the market.
42 | Betting markets satisfy the useful principle, so it is possible that they can be used as an oracle mechanism.
43 |
44 |
45 | There are limitations of using betting as an oracle.
46 | The only things we can bet on are facts that the blockchain already comes to know.
47 | The only thing that the blockchain knows that is correlated with the accuracy of oracle decisions is the difficulty of finding blocks.
48 |
49 |
50 | We can overcome these limitations.
51 | If the oracle lied, then the users could do a hard-fork to fix the oracle's answer. So the attackers would lose all the money in the attack, and that money would reward the users who participated in defense.
52 | If the miners prefer the honest chain, then the difficulty of finding blocks on the dishonest chain will go very low. So the attackers would lose their money on both forks, and defenders would be rewarded on both forks.
53 |
54 |
55 | The big picture:
56 | The network decides the outcome of oracles by hard forking the code.
57 | Betting is used to cover the cost of making these expensive hard forks.
--------------------------------------------------------------------------------
/docs/oracle_order_book.md:
--------------------------------------------------------------------------------
1 | LMSR order book which is suitable for blockchains.
2 |
3 |
4 | Every full node does not need to remember all the open orders.
5 | The miners can choose to remember some of the open orders to collect additional transaction fees.
6 |
7 | The miners who choose to do this need to be able to recall the open orders they choose to keep track of, in sorted order, from each side of forks that may occur, from any block in history that has a reasonable chance of being built upon.
8 |
9 | The trick is that each block can only have one price, and the miners can only include transactions that agreed to trade at that price. Then the nash equilibrium is for the miner to remember all the trades with a high enough fee, that stand a reasonable chance of being matched in the near future. The miners would include as many transactions as possible to collect as many fees as possible.
10 |
11 | maybe accounts should have an additional nonce, and if this nonce is raised, it cancels your open orders?
12 |
13 | One way to cancel orders is by moving all your money to a new account. But this has the unfortunate side effect that you get a new account id. Which means that users would need seperate accounts for accepting payments and for participating in the oracle.
14 |
15 | Maybe orders should come with a timelimit, and if they aren't met by that block, then they can't be included in any block.
16 |
17 | This technique does work with channels, because everyone who has a channel with open orders that can trade at the current price can be posted on-chain to close at that price, even if the market maker doesn't want us to.
18 |
19 | So, one or two of the biggest, most connected miners will make channels with everyone who wants to bet in the oracle. We will enforce non-censorship in miners by making commitments to include trades in the channels.
20 |
21 |
22 | Each miner can choose to keep track of different oracles, that way the number of channels to any individual miner can be small.
23 |
24 | The miner should commit to including the order before knowing what the order's price is.
25 | What if the user refuses to reveal the price?
26 | -Then he can use a channel_solo_close transaction to get his trade matched anyway.
27 | This is bad, if the miner doesn't know all the open orders' prices, then he can't pick the right price for the batch.
28 |
29 | If the miner takes on risk, can he quickly get rid of his risk by the next block?
30 | not necessarily.
31 |
32 | The miner can refuse to do oracle bets with people who have refused to reveal in the past.
33 |
34 | The miner can accept bets a little at a time, that way the volume of unrevealed bet at any time can be arbitrarily small.
35 |
36 |
37 | What is the on-chain evidence that any betting occured?
38 | Can't the miner just write whatever price they want on the block without accepting any trades at all?
39 | How is a light client that doesn't bet at all supposed to know if the honest price was written on the block?
40 |
41 | The miners should all make little bets in each other's oracles, and refuse to mine on top of blocks that were made by an oracle that doesn't accept trades.
42 | So long as the majority is following this rule, you save money by following this rule.
43 |
44 | What if the oracle manager looks at all the little bets and realizes that a customer wants to bet big in one direction, and then the oracle manager censors all trades from that person?
45 | Maybe oracle bets should go through lightning instead of being direct, that way the oracle manager doesn't know who's trade it is.
46 |
47 | What if the oracle manager bribes the lightning hubs to censor certain customers?
48 | If the lightning has enough layers, then it will be hard to censor.
49 |
50 | Also, customers could try to tick the oracle manager into thinking a bigger trade will happen than they actually want, so the manager can't infer much data from looking at trades.
--------------------------------------------------------------------------------
/docs/todo.md:
--------------------------------------------------------------------------------
1 | oracle_close_tx needs to set the result to 3 if the trading volume is too low.
2 | oracle_close_tx needs to add one more trade in before closing. The creator of the oracle is making a bet against whatever orders are left in the order book.
3 |
4 |
5 | download blocks should be more asynchronous. replace talker:talk with talk.
6 |
7 |
8 | Maybe we should stop users from updating the same governance variables twice at once? Alternatively the community could just label the second governance oracle as a bad question.
9 |
10 | the oracle needs to be able to update the variables that define the protocol
11 |
12 |
13 | It is possible to use channel_slash to store data in a channel such that the channel can't be closed with a channel_timeout.
14 | Maybe this is a mistake.
15 |
16 |
17 | We should download headers in batches, it would be much faster.
18 |
19 | javascript light wallets need to be able to do all the channel stuff that full nodes do.
20 |
21 | blog post about channels that use untrusted third parties to be secure. So you don't have to stay online all the time.
22 |
23 |
24 | It is currently possible for an attacker to trick us into ignoring a good block. They trick us into storing a good blocks hash into block_hashes. They give us a good block's header, but mix in some bad transactions, or censor a transaction, or they don't give us some of the merkel tree we need to verify the transactions.
25 | To fix this, each header should contain a hash of all the transactions. We should include the signatures in this hash. This gives us a guarantee that all the data is available at block:check1.
26 |
27 | We need a way to close a channel when your partner refuses to sign any SPK. It can either be a new tx type, or we can make solo_close more complex.
28 |
29 |
30 | constants:difficulty_bits() might be too big.
31 |
32 |
33 | the new way of doing channel slash has a different problem.
34 | We need the channel to finish at the highest nonce possible. The third party could be bribed to choose a different final state.
35 | We need some way to do the channel_slash transaction again and again, until a higher nonce cannot be found.
36 | Each slasher puts up a deposit, and takes the deposit of the previous.
37 | If the same channel_slash exists for a long enough time period, then anyone can do a channel_timeout transaction, to close the channel.
38 | We need to also add a way for the two parties to work together to close the channel early, so they don't have to wait to do a timeout_tx. We can either make a new tx, or make channel_team_close more complicated.
39 |
40 |
41 | We should use a CLI program to talk to the node instead of using erlang directly.
42 | It should be able to access everything in /src/networking/internal_handler.erl
43 | We should add everything from easy to internal_handler.erl
44 |
45 | We need to update download_blocks so that peers get ranked, and we spend more time talking to higher-ranked peers.
46 |
47 | There is a problem where if you crash while syncing with a peer, then you skip trying to sync with any peer lower on the list. this is very bad.
48 |
49 | make the api networking/handler be entirely encrypted. This is to protect information about the channels. https://github.com/BumblebeeBat/pink_crypto/blob/master/src/encryption.erl
50 |
51 | download_blocks could be more efficient.
52 |
53 | maybe nodes need to advertise their own IP/port combo as a peer?
54 |
55 | It would be nice if there were some macros for chalang/src/compiler_lisp2.erl that did backtracking. that way we wouldn't have to think about control flow when making smart contracts.
56 |
57 |
58 | in the white paper we should explain the centralized and trustless exchanges.
59 |
60 |
61 | Updates for next time we restart at a genesis block:
62 |
63 |
64 | each tx with a fee needs a to reference a recent hash. Everyone needs to be incentivized to make the hash as recent as possible. This stops transactions from being reused on multiple forks.
65 |
66 |
67 | Make sure that if something was garbage collected from a merkel tree, and we try accessing the thing, it gives a different message than trying to access something that doesn't exist.
--------------------------------------------------------------------------------
/src/inbox.erl:
--------------------------------------------------------------------------------
1 | %This module keeps track of messages you receive.
2 | -module(inbox).
3 | -behaviour(gen_server).
4 | -export([start_link/0,code_change/3,handle_call/3,handle_cast/2,handle_info/2,init/1,terminate/2, peers/0,msg_ids/1,read/2,delete/2,delete/1,get/1,get_helper/2,test/0]).
5 | -record(f, {next = 0, msgs = dict:new()}).
6 | init(ok) -> {ok, dict:new()}.
7 | start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
8 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
9 | terminate(_, _) -> io:format("died!"), ok.
10 | handle_info(_, X) -> {noreply, X}.
11 | handle_cast({delete, Id}, X) ->
12 | NewX = case dict:find(Id, X) of
13 | error -> X;
14 | {ok, _} ->
15 | dict:erase(Id, X)
16 | end,
17 | {noreply, NewX};
18 | handle_cast({delete, Id, Index}, X) ->
19 | NewX = case dict:find(Id, X) of
20 | error -> X;
21 | {ok, A} ->
22 | case dict:find(Index, A#f.msgs) of
23 | error -> X;
24 | {ok, _} ->
25 | C = dict:erase(Index, A#f.msgs),
26 | dict:store(Id, #f{msgs = C, next = A#f.next}, X)
27 | end
28 | end,
29 | {noreply, NewX};
30 | handle_cast({get, Id, Msg}, X) ->
31 | F = case dict:find(Id, X) of
32 | error -> #f{};
33 | {ok, Z} -> Z
34 | end,
35 | N = F#f.next,
36 | D = dict:store(N, Msg, F#f.msgs),
37 | E = dict:erase(N-free_constants:inbox_per_peer(), D),
38 | S = size(Msg),
39 | true = S < free_constants:max_message_size(),
40 | NewX = dict:store(Id, #f{next = N+1, msgs = E}, X),
41 | {noreply, NewX}.
42 | handle_call(peers, _From, X) ->
43 | {reply, dict:fetch_keys(X), X};
44 | handle_call({msg_ids, Id}, _From, X) ->
45 | B = case dict:find(Id, X) of
46 | error -> no_peer;
47 | {ok, A} -> dict:fetch_keys(A#f.msgs)
48 | end,
49 | {reply, B, X};
50 | handle_call({read, Id, Index}, _From, X) ->
51 | B = case dict:find(Id, X) of
52 | error -> no_peer;
53 | {ok, A} ->
54 | case dict:find(Index, A#f.msgs) of
55 | error -> no_message;
56 | {ok, C} -> C
57 | end
58 | end,
59 | {reply, B, X}.
60 |
61 | delete(Id, Index) -> gen_server:cast(?MODULE, {delete, Id, Index}).
62 | delete(Id) -> gen_server:cast(?MODULE, {delete, Id}).
63 | get(Msg) ->
64 | M = encryption:get_msg(Msg),
65 | FromId = encryption:id(M),
66 | EM = encryption:msg(M),
67 | B = << <<"~>">>/binary, EM/binary >>,
68 | get_helper(FromId, B).
69 | get_helper(From, Message) -> gen_server:cast(?MODULE, {get, From, Message}).
70 | peers() -> gen_server:call(?MODULE, peers).
71 | msg_ids(Id) -> merge_sort(gen_server:call(?MODULE, {msg_ids, Id})).
72 | read(Id, Index) -> gen_server:call(?MODULE, {read, Id, Index}).
73 | merge_sort(no_peer) -> <<"no peer">>;
74 | merge_sort(L) -> ms2(L, []).
75 | ms2([], Out) -> ms3(Out);
76 | ms2([H|T], Out) -> ms2(T, [[H]|Out]).
77 | ms3(X) when length(X) == 1 -> hd(X);
78 | ms3(X) -> ms3(ms4(X, [])).
79 | ms4([], Out) -> Out;
80 | ms4([X|[]], Out) -> [X|Out];
81 | ms4([A|[B|X]], Out) -> ms4(X, [merge(A, B)|Out]).
82 | merge(X, Y) -> merge(flip(X), flip(Y), []).
83 | merge([], [], Out) -> Out;
84 | merge([], [Y|Yt], Out) -> merge([], Yt, [Y|Out]);
85 | merge([X|Xt], [], Out) -> merge(Xt, [], [X|Out]);
86 | merge([X|Xt], [Y|Yt], Out) when X > Y -> merge(Xt, [Y|Yt], [X|Out]);
87 | merge([X|Xt], [Y|Yt], Out) -> merge([X|Xt], Yt, [Y|Out]).
88 | flip(X) -> flip(X, []).
89 | flip([], Out) -> Out;
90 | flip([H|T], Out) -> flip(T, [H|Out]).
91 | test() ->
92 | Peer = 1,
93 | Sorted = [1,2,3,4,5,6,7,8,9],
94 | Sorted = merge_sort([3,6,5,2,4,8,7,9,1]),
95 | get_helper(Peer, <<"hello">>),
96 | get_helper(Peer, <<"hello2">>),
97 | get_helper(Peer, <<"hello3">>),
98 | P = peers(),
99 | P = [Peer],
100 | X = msg_ids(Peer),
101 | X = [0,1,2],
102 | H = read(Peer, 0),
103 | H = <<"hello">>,
104 | delete(Peer, 0),
105 | Y = msg_ids(Peer),
106 | Y = [1,2],
107 | delete(Peer),
108 | P2 = peers(),
109 | P2 = [],
110 | success.
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/src/gas_price.erl:
--------------------------------------------------------------------------------
1 | -module(gas_price).
2 | -export([test/0, timer0/0, timer1/0, timer2/0, timer3/0, timer4/0, timer5/0, timer6/0, timer7/0]).
3 | timer0() -> %4 microseconds
4 | [{integer, 1}].
5 | timer1() -> %17
6 | compiler:compile(<<"
7 | 1 2 swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap swap
8 | ">>).
9 | timer2() -> %352
10 | compiler:compile(<<"
11 | :b YWJj
12 | hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash
13 | ">>).
14 | timer3() -> %24
15 | compiler:compile(<<"
16 | :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1 :i 1
17 | ">>).
18 | timer4() -> %550
19 | Swaps = compiler:compile(<<"
20 | : a 1 ;
21 | : b 1 ;
22 | : ac 1 ;
23 | : bc 1 ;
24 | : da 1 ;
25 | : db 1 ;
26 | : dac 1 ;
27 | : dbc 1 ;
28 | : ea 1 ;
29 | : eb 1 ;
30 | : eac 1 ;
31 | : ebc 1 ;
32 | : eda 1 ;
33 | : edb 1 ;
34 | : edac 1 ;
35 | : edbc 1 ;
36 | : af 1 ;
37 | : fb 1 ;
38 | : fac 1 ;
39 | : fbc 1 ;
40 | : fda 1 ;
41 | : fdb 1 ;
42 | : fdac 1 ;
43 | : fdbc 1 ;
44 | : fea 1 ;
45 | : feb 1 ;
46 | : feac 1 ;
47 | : febc 1 ;
48 | : edfa 1 ;
49 | : edfb 1 ;
50 | : fedac 1 ;
51 | : fedbc 1 ;
52 | : ag 1 ;
53 | : gb 1 ;
54 | : gac 1 ;
55 | : gbc 1 ;
56 | : gda 1 ;
57 | : gdb 1 ;
58 | : gdac 1 ;
59 | : gdbc 1 ;
60 | : gea 1 ;
61 | : geb 1 ;
62 | : geac 1 ;
63 | : gebc 1 ;
64 | : geda 1 ;
65 | : gedb 1 ;
66 | : gedac 1 ;
67 | : gedbc 1 ;
68 | : gaf 1 ;
69 | : gfb 1 ;
70 | : gfac 1 ;
71 | : gfbc 1 ;
72 | : gfda 1 ;
73 | : gfdb 1 ;
74 | : gfdac 1 ;
75 | : gfdbc 1 ;
76 | : gfea 1 ;
77 | : gfeb 1 ;
78 | : gfeac 1 ;
79 | : gfebc 1 ;
80 | : gedfa 1 ;
81 | : gedfb 1 ;
82 | : gfedac 1 ;
83 | : gfedbc 1 ;
84 | ">>),
85 | Swaps.
86 | timer5() -> %257
87 | Swaps = compiler:compile(<<"
88 | : a 1 ;
89 | : b 1 ;
90 | : ac 1 ;
91 | : bc 1 ;
92 | : da 1 ;
93 | : db 1 ;
94 | : dac 1 ;
95 | : dbc 1 ;
96 | : ea 1 ;
97 | : eb 1 ;
98 | : eac 1 ;
99 | : ebc 1 ;
100 | : eda 1 ;
101 | : edb 1 ;
102 | : edac 1 ;
103 | : edbc 1 ;
104 | : af 1 ;
105 | : fb 1 ;
106 | : fac 1 ;
107 | : fbc 1 ;
108 | : fda 1 ;
109 | : fdb 1 ;
110 | : fdac 1 ;
111 | : fdbc 1 ;
112 | : fea 1 ;
113 | : feb 1 ;
114 | : feac 1 ;
115 | : febc 1 ;
116 | : edfa 1 ;
117 | : edfb 1 ;
118 | : fedac 1 ;
119 | : fedbc 1 ;
120 | ">>),
121 | Swaps.
122 | timer6() -> %334
123 | compiler:compile(<<"
124 | : func dup :i 0 == if else :i 1 - recurse call then ;
125 | :i 64 func call
126 | ">>).
127 | timer7() -> %27
128 | X = <<":b QkpiMzFWb2U2a0hkWGxFbVRkSzhueGVPeHAvVUs3enJUdFRaMWFWUmxFL2d4TldqMlJlYngrQm1IZ0RuVGU4MHNxMWZSTVBSWGpFNW55N2N3OWU0ckF3PQ==
129 | :b QkpiMzFWb2U2a0hkWGxFbVRkSzhueGVPeHAvVUs3enJUdFRaMWFWUmxFL2d4TldqMlJlYngrQm1IZ0RuVGU4MHNxMWZSTVBSWGpFNW55N2N3OWU0ckF3PQ==
130 | :b QkpiMzFWb2U2a0hkWGxFbVRkSzhueGVPeHAvVUs3enJUdFRaMWFWUmxFL2d4TldqMlJlYngrQm1IZ0RuVGU4MHNxMWZSTVBSWGpFNW55N2N3OWU0ckF3PQ==
131 | :b QkpiMzFWb2U2a0hkWGxFbVRkSzhueGVPeHAvVUs3enJUdFRaMWFWUmxFL2d4TldqMlJlYngrQm1IZ0RuVGU4MHNxMWZSTVBSWGpFNW55N2N3OWU0ckF3PQ==
132 | ">>,
133 | Y = << X/binary, X/binary, X/binary, X/binary >>,
134 | Z = << Y/binary, Y/binary, Y/binary, Y/binary >>,
135 | compiler:compile(Z).
136 |
137 | test() ->
138 | [timer:tc(language, run, [timer0(), 10000]),%3 microseconds
139 | timer:tc(language, run, [timer1(), 10000]),%14
140 | timer:tc(language, run, [timer2(), 10000]),%338
141 | timer:tc(language, run, [timer3(), 10000]),%21
142 | timer:tc(language, run, [timer4(), 10000]),%686
143 | timer:tc(language, run, [timer5(), 10000]),%296
144 | timer:tc(language, run, [timer6(), 10000]),%364
145 | timer:tc(language, run, [timer7(), 10000])%28
146 | ].
147 |
--------------------------------------------------------------------------------
/src/consensus/txs/oracle_new_tx.erl:
--------------------------------------------------------------------------------
1 | -module(oracle_new_tx).
2 | -export([test/0, doit/3, make/10]).
3 | -record(oracle_new, {from = 0,
4 | nonce = 0,
5 | fee = 0,
6 | question = <<>>,
7 | start,
8 | id,
9 | recent_price, %if this is a governance oracle, or if it is asking a question, then we need to reference another oracle that closed recently with the state "bad". We reference it so we know the current price of shares.
10 | difficulty,
11 | governance,
12 | governance_amount}).
13 | %This asks the oracle a question.
14 | %The oracle can only answer true/false questions.
15 | %Running the oracle costs a fee which is used as a reward to get people to use the oracle.
16 | %The fact that an oracle exists is recorded on the blockchain in a way that is accessible to the VM. So we can use channels to make smart contracts to raise funds to run the oracle.
17 | %The entire text of the question is written into the transaction, but only the hash of the text is stored into a consensus state merkel tree.
18 | %The oracle has a start-date written in it. Trading doesn't start until the start-date.
19 | %The oracle can be published before we know the outcome of the question, that way the oracle id can be used to make channel contracts that bet on the eventual outcome of the oracle.
20 | make(From, Fee, Question, Start, ID, Difficulty, Recent, Governance, GovAmount, Trees) ->
21 | Accounts = trees:accounts(Trees),
22 | {_, Acc, _Proof} = account:get(From, Accounts),
23 | Tx = #oracle_new{from = From, nonce = account:nonce(Acc) + 1, fee = Fee, question = Question, start = Start, id = ID, recent_price = Recent, difficulty = Difficulty, governance = Governance, governance_amount = GovAmount},
24 | {Tx, []}.
25 | doit(Tx, Trees0, NewHeight) ->
26 | %If the question is <<"">>, let it run.
27 | %If the question is not <<"">>, then they need to show that a different oracle with the question "" recently returned "bad", and the difficulty of this oracle is 1/2 as high as that oracle.
28 | Oracles = trees:oracles(Trees0),
29 | Gov = Tx#oracle_new.governance,
30 | GovAmount = Tx#oracle_new.governance_amount,
31 | true = GovAmount > -1,
32 | true = GovAmount < constants:governance_change_limit(),
33 | Question = Tx#oracle_new.question,
34 | {_, Recent, _} = oracles:get(Tx#oracle_new.recent_price,Oracles),
35 | io:fwrite("recent oracle "),
36 | io:fwrite(packer:pack(Recent)),
37 | io:fwrite("\n"),
38 | Trees =
39 | case Gov of
40 | 0 -> Trees0;
41 | G ->
42 | true = GovAmount > 0,
43 | 3 = oracles:result(Recent),
44 | true = NewHeight - oracles:done_timer(Recent) < constants:governance_delay(),
45 | Dif = oracles:difficulty(Recent),
46 | Dif = Tx#oracle_new.difficulty,
47 | Question = <<"">>,
48 | GovTree = trees:governance(Trees0),
49 | {_, GVar, _} = governance:get(G, GovTree),
50 | false = governance:is_locked(GVar),
51 | NewGVar = governance:lock(GVar),
52 | NewGovTree = governance:write(NewGVar, GovTree),
53 | trees:update_governance(Trees0, NewGovTree)
54 | end,
55 | ok = case Question of
56 | <<"">>-> ok;
57 | _Q ->
58 | %get the recent oracle, make sure it's question was <<"">>, make sure our difficulty is half as high as that difficulty.
59 | %true = size(Q) < constants:maximum_question_size(),
60 | 0 = GovAmount,
61 | Di = oracles:difficulty(Recent) div 2,
62 | Di = Tx#oracle_new.difficulty,
63 | 3 = oracles:result(Recent),
64 | true = NewHeight - oracles:done_timer(Recent) < constants:question_delay(),
65 | ok
66 | end,
67 | Accounts = trees:accounts(Trees),
68 | From = Tx#oracle_new.from,
69 | Facc = account:update(From, Accounts, -Tx#oracle_new.fee-constants:oracle_initial_liquidity(), Tx#oracle_new.nonce, NewHeight),
70 | NewAccounts = account:write(Accounts, Facc),
71 | Starts = Tx#oracle_new.start,
72 | true = (Starts - NewHeight) < constants:oracle_future_limit(),
73 | ID = Tx#oracle_new.id,
74 | Question = Tx#oracle_new.question,
75 | true = is_binary(Question),
76 | QH = testnet_hasher:doit(Question),
77 | Diff = Tx#oracle_new.difficulty,
78 | ON = oracles:new(ID, QH, Starts, From, Diff, Gov, GovAmount),
79 | {_, empty, _} = oracles:get(ID, Oracles),
80 | NewOracles = oracles:write(ON, Oracles),
81 | Trees2 = trees:update_oracles(Trees, NewOracles),
82 | trees:update_accounts(Trees2, NewAccounts).
83 |
84 | test() ->
85 | success.
86 |
--------------------------------------------------------------------------------
/src/consensus/trees/oracles.erl:
--------------------------------------------------------------------------------
1 | -module(oracles).
2 | -export([new/7,write/2,get/2,id/1,result/1,
3 | question/1,starts/1,root_hash/1,
4 | type/1, difficulty/1, orders/1,
5 | set_orders/2, done_timer/1, set_done_timer/2,
6 | set_result/2, set_type/2, governance/1,
7 | governance_amount/1,
8 | test/0]).
9 | -define(name, oracles).
10 | -record(oracle, {id,
11 | result,
12 | question,
13 | starts,
14 | type, %0 means order book is empty, 1 means the order book is holding shares of true, 2 means it holds false, 3 means that it holds shares of "bad question".
15 | orders,
16 | creator,
17 | difficulty,
18 | done_timer,
19 | governance = 0,%if it is non-zero, then this is a governance oracle which can update the value of the variables that define the protocol.
20 | governance_amount = 0}).
21 | %we need to store a pointer to the orders tree in the meta data.
22 |
23 | governance(X) -> X#oracle.governance.
24 | governance_amount(X) -> X#oracle.governance_amount.
25 | id(X) -> X#oracle.id.
26 | result(X) -> X#oracle.result.
27 | question(X) -> X#oracle.question.
28 | starts(X) -> X#oracle.starts.
29 | type(X) -> X#oracle.type.
30 | difficulty(X) -> X#oracle.difficulty.
31 | orders(X) -> X#oracle.orders.
32 | done_timer(X) -> X#oracle.done_timer.
33 | set_orders(X, Orders) ->
34 | X#oracle{orders = Orders}.
35 | set_done_timer(X, H) ->
36 | X#oracle{done_timer = H}.
37 | set_result(X, R) ->
38 | X#oracle{result = R}.
39 | set_type(X, T) ->
40 | true = is_integer(T),
41 | true = T > -1,
42 | true = T < 5,
43 | X#oracle{type = T}.
44 | new(ID, Question, Starts, Creator, Difficulty, Governance, GovAmount) ->
45 | true = (Governance > -1) and (Governance < governance:max()),
46 | Orders = orders:empty_book(),
47 | %Orders = OrdersTree,
48 | #oracle{id = ID,
49 | result = 0,
50 | question = Question,
51 | starts = Starts,
52 | type = 3,%1 means we are storing orders of true, 2 is false, 3 is bad.
53 | orders = Orders,
54 | creator = Creator,
55 | difficulty = Difficulty,
56 | done_timer = Starts + constants:minimum_oracle_time(),
57 | governance = Governance,
58 | governance_amount = GovAmount
59 | }.
60 | root_hash(Root) ->
61 | trie:root_hash(?name, Root).
62 | serialize(X) ->
63 | KL = constants:key_length(),
64 | HS = constants:hash_size(),
65 | Question = X#oracle.question,
66 | Orders = orders:root_hash(X#oracle.orders),
67 | %Orders = X#oracle.orders,
68 | HS = size(Question),
69 | HS = size(Orders),
70 | HB = constants:height_bits(),
71 | DB = constants:difficulty_bits(),
72 | <<(X#oracle.id):KL,
73 | (X#oracle.result):8,
74 | (X#oracle.type):8,
75 | (X#oracle.starts):HB,
76 | (X#oracle.creator):KL,
77 | (X#oracle.difficulty):DB,
78 | (X#oracle.done_timer):HB,
79 | (X#oracle.governance):8,
80 | (X#oracle.governance_amount):8,
81 | Question/binary,
82 | Orders/binary>>.
83 | deserialize(X) ->
84 | KL = constants:key_length(),
85 | HS = constants:hash_size()*8,
86 | HEI = constants:height_bits(),
87 | DB = constants:difficulty_bits(),
88 | <> = X,
100 | #oracle{
101 | id = ID,
102 | type = Type,
103 | result = Result,
104 | starts = Starts,
105 | question = <>,
106 | creator = Creator,
107 | difficulty = Diff,
108 | done_timer = DT,
109 | governance = Gov,
110 | governance_amount = GovAmount
111 | }.
112 | write(Oracle, Root) ->
113 | %meta is a pointer to the orders tree.
114 | V = serialize(Oracle),
115 | Key = Oracle#oracle.id,
116 | Meta = Oracle#oracle.orders,
117 | trie:put(Key, V, Meta, Root, ?name).
118 | get(ID, Root) ->
119 | {RH, Leaf, Proof} = trie:get(ID, Root, ?name),
120 | V = case Leaf of
121 | empty -> empty;
122 | L ->
123 | X = deserialize(leaf:value(L)),
124 | M = leaf:meta(L),
125 | X#oracle{orders = M}
126 | end,
127 | {RH, V, Proof}.
128 |
129 |
130 | test() ->
131 | Root = 0,
132 | X0 = new(1, testnet_hasher:doit(1), 2, 1, constants:initial_difficulty(), 0, 0),
133 | X = set_result(X0, 3),
134 | X2 = deserialize(serialize(X)),
135 | X = X2#oracle{orders = X#oracle.orders},
136 | NewLoc = write(X, Root),
137 | {_, X, _} = get(X#oracle.id, NewLoc),
138 | %io:fwrite({X, X3}),
139 | {_, empty, _} = get(X#oracle.id, 0),
140 | success.
141 |
142 |
--------------------------------------------------------------------------------