diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b71eb1d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: "CI" +on: ["push", "pull_request"] + +jobs: + test_and_build: + name: "Build and lint" + runs-on: "ubuntu-latest" + steps: + - name: Install deps + run: sudo apt-get install -y libboost-dev libboost-system-dev + + - name: Setup elixir + uses: erlef/setup-elixir@v1 + with: + otp-version: 24.3.3 + elixir-version: 1.13.4 + + - uses: actions/checkout@v1 + - run: | + mix local.hex --force + mix local.rebar --force + mix deps.get + mix lint + # mix test diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..2ae369a --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +python 3.9.18 +erlang 25.3 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7bb1ee7..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: elixir -elixir: - - 1.10.2 -otp_release: - - 22.0 -sudo: true -addons: - apt: - update: true - sources: - - sourceline: 'ppa:mhier/libboost-latest' - - ubuntu-toolchain-r-test - packages: - - gcc-8 - - g++-8 - - boost1.67 - -script: - - sudo ln -s /usr/bin/gcc-8 /usr/local/bin/gcc - - sudo ln -s /usr/bin/g++-8 /usr/local/bin/g++ - - make test diff --git a/deployment/fabfile.py b/deployment/fabfile.py index c93ef8d..5143482 100644 --- a/deployment/fabfile.py +++ b/deployment/fabfile.py @@ -18,14 +18,17 @@ def install(): #put(".inputrc", "~") # Elixir + Base System - if not exists("/usr/bin/elixir"): - run("wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb") - run("dpkg -i erlang-solutions_1.0_all.deb") + if not exists("~/.asdf/asdf.sh"): run("apt update") run("apt upgrade -y") - run("apt install -y screen git snap g++ make autoconf esl-erlang elixir libtool libgmp-dev daemontools libboost-system-dev libsqlite3-dev") + run("apt install -y libncurses-dev screen git snap g++ make unzip autoconf libtool libgmp-dev daemontools libboost-system-dev libsqlite3-dev") run("apt autoremove") + run("git clone https://github.com/asdf-vm/asdf.git ~/.asdf") + run("echo '. ~/.asdf/asdf.sh' >> ~/.bashrc") + run(". ~/.asdf/asdf.sh && asdf plugin add erlang") + run(". ~/.asdf/asdf.sh && asdf plugin add elixir") + # Application run("mkdir -p {}".format(env.diode)) with cd(env.diode): @@ -33,6 +36,9 @@ def install(): run("git config receive.denyCurrentBranch ignore") local("git push -f ssh://{user}@{host}{path} master".format(user=env.user, host=env.host, path=env.diode)) run("git checkout master") + #run("echo 'elixir 1.14' > .tool-versions") + #run("echo 'erlang 24.0.4' >> .tool-versions") + #run("export KERL_CONFIGURE_OPTIONS=--without-wx && . ~/.asdf/asdf.sh && asdf install") run("cp githooks/post-receive .git/hooks/") run("cp deployment/diode.service /etc/systemd/system/diode.service") run("HOME=`pwd` mix local.hex --force") diff --git a/docker b/docker deleted file mode 100755 index 4c72be9..0000000 --- a/docker +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -# Diode Server -# Copyright 2021 Diode -# Licensed under the Diode License, Version 1.1 -mkdir -p data_prod -MD5=md5sum -if [ -f "$HOME/.erlang.cookie" ]; then - COOKIE=`cat $HOME/.erlang.cookie` -else - if [[ "$OSTYPE" == "darwin"* ]] - then - MD5=md5 - fi - COOKIE=`echo $RANDOM | $MD5 | head -c 20` - echo $COOKIE > "$HOME/.erlang.cookie" -fi - -docker build . -t diode && \ - exec docker run \ - -d \ - --restart unless-stopped \ - --mount type=bind,source="$(pwd)/data_prod",target=/app/data_prod \ - --name diode --network=host -ti \ - -e ELIXIR_ERL_OPTIONS="+sbwt none -noinput -noshell -sname diode +A 8 -setcookie $COOKIE" \ - -e MIX_ENV=prod \ - -e PORT=8080 \ - diode - diff --git a/lib/chain.ex b/lib/chain.ex index dbf618a..d84012f 100644 --- a/lib/chain.ex +++ b/lib/chain.ex @@ -37,15 +37,16 @@ defmodule Chain do def init(ets_extra) do _create(ets_extra) BlockProcess.start_link() - - OnCrash.call(fn reason -> - Logger.error("Chain exited with reason #{inspect(reason)}. Halting system") - System.halt(1) - end) - + OnCrash.call(&on_crash/1) {:ok, load_blocks()} end + @spec on_crash(any()) :: no_return() + def on_crash(reason) do + Logger.error("Chain exited with reason #{inspect(reason)}. Halting system") + System.halt(1) + end + def window_size() do 100 end @@ -370,53 +371,6 @@ defmodule Chain do fun.(state, from) end - def export_blocks(filename \\ "block_export.sq3", blocks \\ Chain.blocks()) do - Sqlitex.with_db(filename, fn db -> - Sqlitex.query!(db, """ - CREATE TABLE IF NOT EXISTS block_export ( - number INTEGER PRIMARY KEY, - data BLOB - ) WITHOUT ROWID; - """) - - start = - case Sqlitex.query!(db, "SELECT MAX(number) as max FROM block_export") do - [[max: nil]] -> 0 - [[max: max]] -> max - end - - IO.puts("start: #{start}") - - Stream.take_while(blocks, fn block -> Block.number(block) > start end) - |> Stream.chunk_every(100) - |> Task.async_stream(fn blocks -> - IO.puts("Writing block #{Block.number(hd(blocks))}") - - Enum.map(blocks, fn block -> - data = - Block.export(block) - |> BertInt.encode!() - - [Block.number(block), data] - end) - end) - |> Stream.each(fn {:ok, blocks} -> - :ok = Sqlitex.exec(db, "BEGIN") - - Enum.each(blocks, fn [num, data] -> - Sqlitex.query!( - db, - "INSERT INTO block_export (number, data) VALUES(?1, CAST(?2 AS BLOB))", - bind: [num, data] - ) - end) - - :ok = Sqlitex.exec(db, "COMMIT") - end) - |> Stream.run() - end) - end - defp decode_blocks("") do [] end diff --git a/lib/chain/block.ex b/lib/chain/block.ex index 43e4171..b31a005 100644 --- a/lib/chain/block.ex +++ b/lib/chain/block.ex @@ -624,7 +624,6 @@ defmodule Chain.Block do case last_final(block) do block = %Block{} -> Block.hash(block) hash when is_binary(hash) -> hash - nil -> nil end end diff --git a/lib/diode.ex b/lib/diode.ex index 1b5fba4..362e4b0 100644 --- a/lib/diode.ex +++ b/lib/diode.ex @@ -42,6 +42,12 @@ defmodule Diode do end puts("Data Dir : #{data_dir()}") + + if System.get_env("COOKIE") do + :erlang.set_cookie(String.to_atom(System.get_env("COOKIE"))) + puts("Cookie : #{System.get_env("COOKIE")}") + end + puts("") if dev_mode?() and [] == wallets() do @@ -77,11 +83,12 @@ defmodule Diode do worker(Stats, []), supervisor(Model.Sql), supervisor(Channels), - worker(PubSub, [args]), worker(Chain.BlockCache, [ets_extra]), worker(Chain, [ets_extra]), + worker(PubSub, [args]), worker(Chain.Pool, [args]), - worker(TicketStore, [ets_extra]) + worker(TicketStore, [ets_extra]), + worker(Moonbeam.NonceProvider) ] children = diff --git a/lib/eip712.ex b/lib/eip712.ex new file mode 100644 index 0000000..872484b --- /dev/null +++ b/lib/eip712.ex @@ -0,0 +1,98 @@ +defmodule EIP712 do + defstruct [:primary_type, :types, :message] + + def encode(domain_separator, primary_type, type_data, message) do + Hash.keccak_256( + "\x19\x01" <> domain_separator <> hash_struct(primary_type, type_data, message) + ) + end + + # Special case for a flat EIP712 (no nested user types) + def encode(domain_separator, primary_type, message) do + {types, values} = split_fields(message) + encode(domain_separator, primary_type, %{primary_type => types}, values) + end + + def encode_type(primary_type, type_data) do + prefix = encode_single_type(primary_type, Map.get(type_data, primary_type)) + + types = + Enum.map(type_data[primary_type], fn + {_name, type} -> type + {_name, type, _value} -> type + end) + |> Enum.uniq() + + postfix = + Enum.filter(type_data, fn + {name, _} -> name != primary_type and name in types + end) + |> Enum.map(fn {name, fields} -> encode_single_type(name, fields) end) + |> Enum.sort() + |> Enum.join() + + prefix <> postfix + end + + def encode_single_type(type, fields) do + names = + Enum.map(fields, fn + {name, type} -> "#{type} #{name}" + {name, type, _value} -> "#{type} #{name}" + end) + + type <> "(" <> Enum.join(names, ",") <> ")" + end + + def type_hash(primary_type, type_data) when is_map(type_data) do + encode_type(primary_type, type_data) |> Hash.keccak_256() + end + + @spec hash_struct(binary(), [{String.t(), String.t(), any()}]) :: binary() + def hash_struct(primary_type, type_data) do + {types, values} = split_fields(type_data) + hash_struct(primary_type, %{primary_type => types}, values) + end + + def hash_struct(primary_type, type_data, message) do + encode_data = + Enum.map(type_data[primary_type], fn {name, type} when is_binary(type) -> + value = Map.get(message, name) + + cond do + type == "bytes" or type == "string" -> + ABI.encode("bytes32", Hash.keccak_256(value)) + + Map.has_key?(type_data, type) -> + hash_struct(type, type_data, value) + + true -> + ABI.encode(type, value) + end + end) + |> Enum.join() + + Hash.keccak_256(type_hash(primary_type, type_data) <> encode_data) + end + + def hash_domain_separator(name, version, chain_id, verifying_contract) + when is_binary(name) and is_binary(version) and is_integer(chain_id) and + is_binary(verifying_contract) do + hash_struct("EIP712Domain", [ + {"name", "string", name}, + {"version", "string", version}, + {"chainId", "uint256", chain_id}, + {"verifyingContract", "address", verifying_contract} + ]) + end + + defp split_fields(fields) do + {types, values} = + Enum.map(fields, fn {name, type, value} -> + {{name, type}, {name, value}} + end) + |> Enum.unzip() + + {types, Map.new(values)} + end +end diff --git a/lib/kademlia.ex b/lib/kademlia.ex index 98337ee..ad732b9 100644 --- a/lib/kademlia.ex +++ b/lib/kademlia.ex @@ -24,7 +24,6 @@ defmodule Kademlia do GenServer.start_link(__MODULE__, :ok, name: __MODULE__, hibernate_after: 5_000) end - @spec ping(any) :: any def ping(node_id) do rpc(find_node(node_id), [Client.ping()]) end @@ -51,7 +50,6 @@ defmodule Kademlia do @doc """ store/1 same as store/2 but usees Object.key/1 and Object.encode/1 """ - @spec store(tuple()) :: any() def store(object) when is_tuple(object) do key = Object.key(object) value = Object.encode!(object) @@ -62,7 +60,6 @@ defmodule Kademlia do store() stores the given key-value pair in the @k nodes that are closest to the key """ - @spec store(binary(), binary()) :: any() def store(key, value) when is_binary(value) do nodes = find_nodes(key) @@ -76,7 +73,6 @@ defmodule Kademlia do find_value() is different from store() in that it might return an earlier result """ - @spec find_value(binary()) :: any() def find_value(key) do key = hash(key) nodes = do_find_nodes(key, KBuckets.k(), Client.find_value()) @@ -132,7 +128,6 @@ defmodule Kademlia do end end - @spec find_node(Wallet.address()) :: nil | KBuckets.Item.t() def find_node(address) do case find_nodes(address) do [] -> @@ -146,7 +141,6 @@ defmodule Kademlia do end end - @spec find_nodes(any()) :: [KBuckets.Item.t()] def find_nodes(key) do key = hash(key) visited = do_find_nodes(key, KBuckets.k(), Client.find_node()) @@ -175,7 +169,6 @@ defmodule Kademlia do Retrieves for the target key either the last cached values or the nearest k entries from the KBuckets store """ - @spec find_node_lookup(any()) :: [KBuckets.Item.t()] def find_node_lookup(key) do get_cached(&nearest_n/1, key) end diff --git a/lib/kbuckets.ex b/lib/kbuckets.ex index b9a3ad8..bea244b 100644 --- a/lib/kbuckets.ex +++ b/lib/kbuckets.ex @@ -12,13 +12,15 @@ defmodule KBuckets do defstruct node_id: nil, last_connected: nil, last_error: nil, retries: 0 @type t :: %Item{ - node_id: Wallet.t(), + node_id: Wallet.t() | <<_::256>>, last_connected: integer() | nil, last_error: integer() | nil, retries: integer() } end + alias KBuckets.Item + defimpl String.Chars, for: Item do def to_string(item) do String.Chars.to_string(Map.from_struct(item)) @@ -35,7 +37,6 @@ defmodule KBuckets do @zero <<0::size(1)>> @one <<1::size(1)>> - @spec new() :: kbuckets() def new() do self_id = Diode.miner() @@ -46,7 +47,6 @@ defmodule KBuckets do }}} end - @spec self(kbuckets()) :: Item.t() def self({:kbucket, self_id, _tree}) do %Item{node_id: self_id, last_connected: -1} end @@ -56,7 +56,6 @@ defmodule KBuckets do |> Object.decode!() end - @spec to_uri(Item.t()) :: binary() def to_uri(item) do server = object(item) host = Server.host(server) @@ -64,7 +63,6 @@ defmodule KBuckets do "diode://#{item.node_id}@#{host}:#{port}" end - @spec size(kbuckets()) :: non_neg_integer() def size({:kbucket, _self_id, tree}) do do_size(tree) end @@ -77,7 +75,6 @@ defmodule KBuckets do do_size(zero) + do_size(one) end - @spec bucket_count(kbuckets()) :: pos_integer() def bucket_count({:kbucket, _self_id, tree}) do do_bucket_count(tree) end @@ -90,7 +87,6 @@ defmodule KBuckets do do_bucket_count(zero) + do_bucket_count(one) end - @spec to_list(kbuckets() | [Item.t()]) :: [Item.t()] def to_list({:kbucket, _self_id, tree}) do do_to_list(tree) end @@ -110,7 +106,6 @@ defmodule KBuckets do @doc """ nearer_n finds the n nodes nearer or equal to the current node to the provided item. """ - @spec nearer_n(kbuckets() | [Item.t()], Item.t() | item_id(), pos_integer()) :: [Item.t()] def nearer_n({:kbucket, self, _tree} = kbuckets, item, n) do min_dist = distance(self, item) @@ -217,7 +212,6 @@ defmodule KBuckets do Diode.hash(data) end - @spec key(item_id() | node_id() | KBuckets.Item.t()) :: item_id() def key(wallet() = w) do hash(Wallet.address!(w)) end @@ -255,7 +249,6 @@ defmodule KBuckets do end end - @spec delete_item(kbuckets(), Item.t() | item_id()) :: kbuckets() def delete_item({:kbucket, self_id, tree}, item) do key = key(item) @@ -267,7 +260,6 @@ defmodule KBuckets do {:kbucket, self_id, tree} end - @spec update_item(kbuckets(), Item.t() | item_id()) :: kbuckets() def update_item({:kbucket, self_id, tree}, item) do key = key(item) @@ -283,13 +275,11 @@ defmodule KBuckets do {:kbucket, self_id, tree} end - @spec member?(kbuckets(), Item.t() | item_id()) :: boolean() def member?(kb, item_id) do item(kb, item_id) != nil end # Return an item for the given wallet address - @spec item(kbuckets(), Item.t() | item_id()) :: Item.t() | nil def item(kb, item_id) do key = key(item_id) @@ -306,12 +296,10 @@ defmodule KBuckets do end end - @spec insert_item(kbuckets(), Item.t() | item_id()) :: kbuckets() def insert_item({:kbucket, self_id, tree}, item) do {:kbucket, self_id, do_insert_item(tree, item)} end - @spec insert_items(kbuckets(), [Item.t()]) :: kbuckets() def insert_items({:kbucket, self_id, tree}, items) do tree = Enum.reduce(items, tree, fn item, acc -> @@ -363,7 +351,6 @@ defmodule KBuckets do fun.({:leaf, prefix, bucket}) end - @spec k() :: integer() def k() do @k end diff --git a/lib/moonbeam/call_permit.ex b/lib/moonbeam/call_permit.ex new file mode 100644 index 0000000..fade637 --- /dev/null +++ b/lib/moonbeam/call_permit.ex @@ -0,0 +1,71 @@ +defmodule CallPermit do + def address() do + Base16.decode("0x000000000000000000000000000000000000080A") + end + + # 0xe7f13b866a7fc159cb6ee32bcb4103cf0477652e + def wallet() do + # Diode.miner() + # Wallet.from_privkey() + {:wallet, + <<205, 186, 242, 6, 129, 177, 86, 35, 141, 148, 105, 188, 131, 116, 84, 18, 226, 131, 244, + 208, 162, 155, 231, 186, 90, 212, 147, 79, 134, 68, 17, 170>>, + <<2, 155, 102, 229, 244, 105, 136, 238, 53, 54, 160, 44, 171, 93, 3, 183, 210, 90, 143, 207, + 59, 161, 223, 135, 222, 113, 0, 8, 88, 55, 222, 249, 70>>, + <<231, 241, 59, 134, 106, 127, 193, 89, 203, 110, 227, 43, 203, 65, 3, 207, 4, 119, 101, 46>>} + end + + # /// @dev Dispatch a call on the behalf of an other user with a EIP712 permit. + # /// Will revert if the permit is not valid or if the dispatched call reverts or errors (such as + # /// out of gas). + # /// If successful the EIP712 nonce is increased to prevent this permit to be replayed. + # /// @param from Who made the permit and want its call to be dispatched on their behalf. + # /// @param to Which address the call is made to. + # /// @param value Value being transfered from the "from" account. + # /// @param data Call data + # /// @param gaslimit Gaslimit the dispatched call requires. + # /// Providing it prevents the dispatcher to manipulate the gaslimit. + # /// @param deadline Deadline in UNIX seconds after which the permit will no longer be valid. + # /// @param v V part of the signature. + # /// @param r R part of the signature. + # /// @param s S part of the signature. + # /// @return output Output of the call. + # /// @custom:selector b5ea0966 + def dispatch(from, to, value, data, gaslimit, deadline, v, r, s) do + ABI.encode_call( + "dispatch", + [ + "address", + "address", + "uint256", + "bytes", + "uint64", + "uint256", + "uint8", + "bytes32", + "bytes32" + ], + [from, to, value, data, gaslimit, deadline, v, r, s] + ) + end + + def nonces(owner) do + ABI.encode_call("nonces", ["address"], [owner]) + end + + def domain_separator() do + ABI.encode_call("DOMAIN_SEPARATOR", [], []) + end + + def rpc_call!(call, from \\ nil, blockref \\ "latest") do + {:ok, ret} = rpc_call(call, from, blockref) + ret + end + + def rpc_call(call, from \\ nil, blockref \\ "latest") do + from = if from != nil, do: Base16.encode(from) + Moonbeam.call(Base16.encode(address()), from, Base16.encode(call), blockref) + end + + # CallPermit.call!(CallPermit.domain_separator()) +end diff --git a/lib/moonbeam/edge_m1.ex b/lib/moonbeam/edge_m1.ex new file mode 100644 index 0000000..b310fd7 --- /dev/null +++ b/lib/moonbeam/edge_m1.ex @@ -0,0 +1,180 @@ +# Diode Server +# Copyright 2023 Diode +# Licensed under the Diode License, Version 1.1 +defmodule Network.EdgeM1 do + import Network.EdgeV2, only: [response: 1, response: 2, error: 1] + + def handle_async_msg(msg, state) do + case msg do + ["getblockpeak"] -> + Moonbeam.block_number() + |> Base16.decode() + |> response() + + ["getblock", index] when is_binary(index) -> + error("not implemented") + + ["getblockheader", index] when is_binary(index) -> + %{ + "hash" => hash, + "nonce" => nonce, + "miner" => miner, + "number" => number, + "parentHash" => previous_block, + "stateRoot" => state_hash, + "timestamp" => timestamp, + "transactionsRoot" => transaction_hash + } = Moonbeam.get_block_by_number(hex_blockref(index)) + + response(%{ + "block_hash" => Base16.decode(hash), + "miner" => Base16.decode(miner), + "miner_signature" => nil, + "nonce" => Base16.decode(nonce), + "number" => Base16.decode(number), + "previous_block" => Base16.decode(previous_block), + "state_hash" => Base16.decode(state_hash), + "timestamp" => Base16.decode(timestamp), + "transaction_hash" => Base16.decode(transaction_hash) + }) + + ["getblockheader2", index] when is_binary(index) -> + error("not implemented") + + ["getblockquick", last_block, window_size] + when is_binary(last_block) and + is_binary(window_size) -> + error("not implemented") + + ["getblockquick2", last_block, window_size] + when is_binary(last_block) and + is_binary(window_size) -> + error("not implemented") + + ["getstateroots", _index] -> + error("not implemented") + + ["getaccount", block, address] -> + # requires https://eips.ethereum.org/EIPS/eip-1186 + # response(Moonbeam.proof(address, [0], blockref(block))) + + code = Base16.decode(Moonbeam.get_code(hex_address(address), hex_blockref(block))) + + storage_root = + if code == "" do + "" + else + Hash.keccak_256("#{div(System.os_time(:second), 10)}") + end + + response(%{ + nonce: + Base16.decode( + Moonbeam.get_transaction_count(hex_address(address), hex_blockref(block)) + ), + balance: Base16.decode(Moonbeam.get_balance(hex_address(address), hex_blockref(block))), + storage_root: storage_root, + code: Hash.keccak_256(code) + }) + + ["getaccountroots", _index, _id] -> + error("not implemented") + + ["getaccountvalue", block, address, key] -> + # requires https://eips.ethereum.org/EIPS/eip-1186 + # response(Moonbeam.proof(address, [key], blockref(block))) + Moonbeam.get_storage_at(hex_address(address), hex_key(key), hex_blockref(block)) + |> Base16.decode() + |> response() + + ["getaccountvalues", block, address | keys] -> + # requires https://eips.ethereum.org/EIPS/eip-1186 + # response(Moonbeam.proof(address, keys, blockref(block))) + Enum.map(keys, fn key -> + Moonbeam.get_storage_at(hex_address(address), hex_key(key), hex_blockref(block)) + |> Base16.decode() + end) + |> response() + + ["sendtransaction", _tx] -> + error("not implemented") + + ["getmetanonce", block, address] -> + CallPermit.nonces(address) + |> CallPermit.rpc_call!(nil, hex_blockref(block)) + |> Base16.decode_int() + |> response() + + ["sendmetatransaction", tx] -> + # These are CallPermit metatransactions + # Testing transaction + [from, to, value, call, gaslimit, deadline, v, r, s] = Rlp.decode!(tx) + value = Rlpx.bin2num(value) + gaslimit = Rlpx.bin2num(gaslimit) + deadline = Rlpx.bin2num(deadline) + v = Rlpx.bin2num(v) + r = Rlpx.bin2num(r) + s = Rlpx.bin2num(s) + + call = CallPermit.dispatch(from, to, value, call, gaslimit, deadline, v, r, s) + + nonce = Moonbeam.NonceProvider.nonce() + # Can't do this pre-check because we will be receiving batches of future nonces + # those are not yet valid but will be valid in the future, after the other txs have + # been processed... + if nonce == Moonbeam.NonceProvider.fetch_nonce() do + {:ok, _} = CallPermit.rpc_call(call, Wallet.address!(CallPermit.wallet())) + end + + # Moonbeam.estimate_gas(Base16.encode(CallPermit.address()), Base16.encode(call)) + # |> IO.inspect(label: "estimate_gas") + # {:error, %{"message" => error}} -> + # error(error) + + tx = + Shell.raw(CallPermit.wallet(), call, + to: CallPermit.address(), + chainId: Moonbeam.chain_id(), + gas: 12_000_000, + gasPrice: Base16.decode(Moonbeam.gas_price()), + value: 0, + nonce: nonce + ) + + payload = + tx + |> Chain.Transaction.to_rlp() + |> Rlp.encode!() + |> Base16.encode() + + tx_hash = + Chain.Transaction.to_rlp(tx) + |> Rlp.encode!() + |> Hash.keccak_256() + |> Base16.encode() + + case Moonbeam.send_raw_transaction(payload) do + ^tx_hash -> response("ok", tx_hash) + :already_known -> response("ok", tx_hash) + end + + _ -> + default(msg, state) + end + end + + defp default(msg, state) do + Network.EdgeV2.handle_async_msg(msg, state) + end + + defp hex_blockref(ref) when ref in ["latest", "earliest"], do: ref + defp hex_blockref(ref), do: Base16.encode(ref) + + defp hex_address(<<_::binary-size(20)>> = address) do + Base16.encode(address) + end + + defp hex_key(<<_::binary-size(32)>> = key) do + Base16.encode(key) + end +end diff --git a/lib/moonbeam/moonbeam.ex b/lib/moonbeam/moonbeam.ex new file mode 100644 index 0000000..578066d --- /dev/null +++ b/lib/moonbeam/moonbeam.ex @@ -0,0 +1,104 @@ +defmodule Moonbeam do + # https://github.com/moonbeam-foundation/moonbeam/blob/master/precompiles/call-permit/CallPermit.sol + @endpoint "https://moonbeam-alpha.api.onfinality.io/public" + # @endpoint "https://rpc.api.moonbase.moonbeam.network" + + def epoch() do + 0 + end + + def peak() do + 1000 + end + + def genesis_hash() do + # https://moonbase.moonscan.io/block/0 + 0x33638DDE636F9264B6472B9D976D58E757FE88BADAC53F204F3F530ECC5AACFA + end + + def get_proof(address, keys, block \\ "latest") do + # requires https://eips.ethereum.org/EIPS/eip-1186 + rpc!("eth_getProof", [address, keys, block]) + end + + def block_number() do + rpc!("eth_blockNumber") + end + + def get_block_by_number(block \\ "lastest", with_transactions \\ false) do + rpc!("eth_getBlockByNumber", [block, with_transactions]) + end + + def get_storage_at(address, slot, block \\ "latest") do + rpc!("eth_getStorageAt", [address, slot, block]) + end + + def get_code(address, block \\ "latest") do + rpc!("eth_getCode", [address, block]) + end + + def get_transaction_count(address, block \\ "latest") do + rpc!("eth_getTransactionCount", [address, block]) + end + + def get_balance(address, block \\ "latest") do + rpc!("eth_getBalance", [address, block]) + end + + def send_raw_transaction(tx) do + case rpc("eth_sendRawTransaction", [tx]) do + {:ok, tx_hash} -> tx_hash + {:error, %{"code" => -32603, "message" => "already known"}} -> :already_known + {:error, error} -> raise "RPC error: #{inspect(error)}" + end + end + + def gas_price() do + rpc!("eth_gasPrice") + end + + def rpc(method, params \\ []) do + request = %{ + jsonrpc: "2.0", + method: method, + params: params, + id: 1 + } + + {:ok, %{body: body}} = + HTTPoison.post(@endpoint, Poison.encode!(request), [{"Content-Type", "application/json"}]) + + case Poison.decode!(body) do + %{"result" => result} -> + {:ok, result} + + %{"error" => error} -> + {:error, error} + end + end + + def rpc!(method, params \\ []) do + case rpc(method, params) do + {:ok, result} -> result + {:error, error} -> raise "RPC error: #{inspect(error)}" + end + end + + def call(to, from, data, block \\ "latest") do + rpc("eth_call", [%{to: to, data: data, from: from}, block]) + end + + def call!(to, from, data, block \\ "latest") do + {:ok, ret} = call(to, from, data, block) + ret + end + + def estimate_gas(to, data, block \\ "latest") do + rpc!("eth_estimateGas", [%{to: to, data: data}, block]) + end + + def chain_id() do + # Moonbase Alpha (0x507) + 1287 + end +end diff --git a/lib/moonbeam/nonce_provider.ex b/lib/moonbeam/nonce_provider.ex new file mode 100644 index 0000000..14821d8 --- /dev/null +++ b/lib/moonbeam/nonce_provider.ex @@ -0,0 +1,76 @@ +defmodule Moonbeam.NonceProvider do + alias Moonbeam.NonceProvider + use GenServer + require Logger + + defstruct [:nonce, :fetched_nonce] + + def start_link() do + GenServer.start_link(__MODULE__, nil, name: __MODULE__, hibernate_after: 5_000) + end + + @impl true + def init(nil) do + {:ok, %__MODULE__{}} + end + + def nonce() do + GenServer.call(__MODULE__, :nonce) + end + + def peek_nonce() do + GenServer.call(__MODULE__, :peek_nonce) + end + + def check_stale_nonce() do + send(__MODULE__, :check_stale_nonce) + end + + @impl true + def handle_call(:peek_nonce, _from, state = %NonceProvider{nonce: nonce}) do + {:reply, nonce, state} + end + + def handle_call(:nonce, _from, state = %NonceProvider{nonce: nil}) do + nonce = fetch_nonce() + {:reply, nonce, %NonceProvider{state | nonce: nonce + 1, fetched_nonce: nonce}} + end + + def handle_call(:nonce, _from, state = %NonceProvider{nonce: nonce}) do + # check if nonce is stale + pid = self() + Debouncer.apply(__MODULE__, fn -> send(pid, :check_stale_nonce) end, 30_000) + + {:reply, nonce, %NonceProvider{state | nonce: nonce + 1}} + end + + @impl true + def handle_info( + :check_stale_nonce, + state = %NonceProvider{nonce: old_nonce, fetched_nonce: fetched_once} + ) do + new_nonce = fetch_nonce() + # if nonce is stuck then something is wrong with processing of transactions + + cond do + new_nonce == fetched_once -> + Logger.warn("Nonce is stuck (#{old_nonce}), resetting to: #{new_nonce}") + {:noreply, %NonceProvider{state | fetched_nonce: new_nonce, nonce: new_nonce}} + + new_nonce > old_nonce -> + Logger.warn("Nonce is too low (#{old_nonce}), resetting to: #{new_nonce}") + {:noreply, %NonceProvider{state | fetched_nonce: new_nonce, nonce: new_nonce}} + + true -> + {:noreply, %NonceProvider{state | fetched_nonce: new_nonce}} + end + end + + def fetch_nonce() do + Moonbeam.get_transaction_count( + Wallet.base16(CallPermit.wallet()), + "latest" + ) + |> Base16.decode_int() + end +end diff --git a/lib/network/edge_v2.ex b/lib/network/edge_v2.ex index dd2caa4..eaf6264 100644 --- a/lib/network/edge_v2.ex +++ b/lib/network/edge_v2.ex @@ -314,6 +314,9 @@ defmodule Network.EdgeV2 do def handle_async_msg(msg, state) do case msg do + ["m1:" <> cmd | rest] -> + Network.EdgeM1.handle_async_msg([cmd | rest], state) + ["ping"] -> response("pong") @@ -358,6 +361,10 @@ defmodule Network.EdgeV2 do |> response() end + ["isonline", key] -> + online = Map.get(Network.Server.get_connections(Network.EdgeV2), key) != nil + response(online) + ["getobject", key] -> case Kademlia.find_value(key) do nil -> nil @@ -551,11 +558,11 @@ defmodule Network.EdgeV2 do end end - defp response(arg) do + def response(arg) do response_array([arg]) end - defp response(arg, arg2) do + def response(arg, arg2) do response_array([arg, arg2]) end @@ -563,11 +570,11 @@ defmodule Network.EdgeV2 do ["response" | args] end - defp error(code, message) do + def error(code, message) do ["error", code, message] end - defp error(message) do + def error(message) do ["error", message] end @@ -1123,6 +1130,7 @@ defmodule Network.EdgeV2 do end) || check_block(n) end + @spec check_block(integer()) :: no_return() defp check_block(n) do if is_integer(n) and n < Chain.peak() do # we had some cases of missing blocks diff --git a/lib/network/peer_handler.ex b/lib/network/peer_handler.ex index 49dac36..7150166 100644 --- a/lib/network/peer_handler.ex +++ b/lib/network/peer_handler.ex @@ -204,11 +204,7 @@ defmodule Network.PeerHandler do genesis = Chain.genesis_hash() if genesis != genesis_hash do - log(state, "wrong genesis: ~p ~p", [ - Base16.encode(genesis), - Base16.encode(genesis_hash) - ]) - + log(state, "wrong genesis: ~p ~p", [Base16.encode(genesis), Base16.encode(genesis_hash)]) {:stop, :normal, state} else state = publish_peak(state) diff --git a/lib/network/rpc.ex b/lib/network/rpc.ex index ec2ef2f..4c6db74 100644 --- a/lib/network/rpc.ex +++ b/lib/network/rpc.ex @@ -741,7 +741,11 @@ defmodule Network.Rpc do gas_price = Map.get(opts, "gasPrice", 0x3B9ACA00) value = Map.get(opts, "value", 0x0) blockRef = Map.get(opts, "blockRef", "latest") - chain_id = with_block(blockRef, fn block -> Diode.chain_id(Block.number(block)) end) + + chain_id = + Map.get_lazy(opts, "chainId", fn -> + with_block(blockRef, fn block -> Diode.chain_id(Block.number(block)) end) + end) nonce = Map.get_lazy(opts, "nonce", fn -> diff --git a/lib/network/server.ex b/lib/network/server.ex index 16816a1..1e91bb4 100644 --- a/lib/network/server.ex +++ b/lib/network/server.ex @@ -48,7 +48,7 @@ defmodule Network.Server do end @impl true - @spec init({integer(), atom(), %{}}) :: {:ok, Network.Server.t(), {:continue, :accept}} + # @spec init({[integer()], atom(), %{}}) :: {:ok, Network.Server.t(), {:continue, :accept}} def init({ports, protocolHandler, opts}) when is_list(ports) do :erlang.process_flag(:trap_exit, true) diff --git a/lib/oncrash.ex b/lib/oncrash.ex deleted file mode 100644 index d015d8b..0000000 --- a/lib/oncrash.ex +++ /dev/null @@ -1,19 +0,0 @@ -# Diode Server -# Copyright 2021 Diode -# Licensed under the Diode License, Version 1.1 -defmodule OnCrash do - @spec call(pid() | nil, (reason -> any())) :: true when reason: any() - def call(pid \\ nil, fun) do - pid = if pid == nil, do: self(), else: pid - - spawn(fn -> - ref = Process.monitor(pid) - - receive do - {:DOWN, ^ref, :process, ^pid, reason} -> fun.(reason) - end - end) - - true - end -end diff --git a/lib/shell.ex b/lib/shell.ex index 1ddc5a6..5386071 100644 --- a/lib/shell.ex +++ b/lib/shell.ex @@ -131,7 +131,7 @@ defmodule Shell do def profile_import() do Stats.toggle_print() - :observer.start() + # :observer.start() spawn(fn -> Chain.import_blocks("blocks.dat") end) end diff --git a/lib/wallet.ex b/lib/wallet.ex index 6e805de..f2fc67d 100644 --- a/lib/wallet.ex +++ b/lib/wallet.ex @@ -141,6 +141,11 @@ defmodule Wallet do def sign!(wallet() = w, msg, algo \\ :kec) do Secp256k1.sign(privkey!(w), msg, algo) end + + def eth_sign!(wallet() = w, msg, chain_id \\ nil, algo \\ :kec) do + [v, r, s] = sign!(w, msg, algo) |> Secp256k1.bitcoin_to_rlp(chain_id) + <> + end end defimpl Inspect, for: Wallet do diff --git a/mix.exs b/mix.exs index d07ab4b..65bbc9b 100644 --- a/mix.exs +++ b/mix.exs @@ -10,20 +10,20 @@ defmodule Diode.Mixfile do def project do [ + aliases: aliases(), app: Diode, + compilers: [:elixir_make] ++ Mix.compilers(), + deps: deps(), + description: "Diode Network Full Blockchain Node implementation", + docs: docs(), elixir: "~> 1.11", - version: :persistent_term.get(:vsn, @vsn), + elixirc_options: [warnings_as_errors: Mix.target() == :host], + elixirc_paths: elixirc_paths(Mix.env()), full_version: :persistent_term.get(:full_vsn, @full_vsn), + package: package(), source_url: @url, - description: "Diode Network Full Blockchain Node implementation", - elixirc_options: [warnings_as_errors: Mix.target() == :host], start_permanent: Mix.env() == :prod, - deps: deps(), - aliases: aliases(), - compilers: [:elixir_make] ++ Mix.compilers(), - elixirc_paths: elixirc_paths(Mix.env()), - docs: docs(), - package: package() + version: :persistent_term.get(:vsn, @vsn) ] end @@ -51,7 +51,8 @@ defmodule Diode.Mixfile do lint: [ "compile", "format --check-formatted", - "credo --only warning" + "credo --only warning", + "dialyzer" ] ] end @@ -94,6 +95,8 @@ defmodule Diode.Mixfile do {:sqlitex, github: "diodechain/sqlitex"}, {:niffler, "~> 0.1"}, {:while, "~> 0.2"}, + {:httpoison, "~> 2.0"}, + {:oncrash, "~> 0.0"}, # linting {:dialyxir, "~> 1.1", only: [:dev], runtime: false}, diff --git a/mix.lock b/mix.lock index 56d4d18..5b03caf 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,7 @@ %{ "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, @@ -8,7 +9,7 @@ "debouncer": {:hex, :debouncer, "0.1.5", "1d993bd01400585a6bf463d36ea50386deea2c09bc6e55d613b7b6c0b4ccb4f9", [:mix], [], "hexpm", "cecc2a4c4e9b51c7fbc5474c317a50cd5870334eb3e2b95d85d578ed58c4fa24"}, "decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, + "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, "earmark": {:hex, :earmark, "1.4.20", "d5097b1c7417a03c73a2985fcf01c3f72192c427b8a498719737dca5273938cb", [:mix], [{:earmark_parser, "== 1.4.18", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "7be744242dbde74c858279f4a65d9d31f37d163190d739340015c30038c1edb3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.19", "de0d033d5ff9fc396a24eadc2fcf2afa3d120841eb3f1004d138cbf9273210e8", [:mix], [], "hexpm", "527ab6630b5c75c3a3960b75844c314ec305c76d9899bb30f71cb85952a9dc45"}, "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, @@ -16,18 +17,25 @@ "esqlite": {:git, "https://github.com/mmzeeman/esqlite.git", "c764204c23d5a5ea9cb17eee5529b2dad253bedb", []}, "ex_doc": {:hex, :ex_doc, "0.28.0", "7eaf526dd8c80ae8c04d52ac8801594426ae322b52a6156cd038f30bafa8226f", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e55cdadf69a5d1f4cfd8477122ebac5e1fadd433a8c1022dafc5025e48db0131"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "hackney": {:hex, :hackney, "1.19.1", "59de4716e985dd2b5cbd4954fa1ae187e2b610a9c4520ffcb0b1653c3d6e5559", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "8aa08234bdefc269995c63c2282cf3cd0e36febe3a6bfab11b610572fdd1cad0"}, "hex2bin": {:hex, :hex2bin, "1.0.0", "aac26eab998ae80eacee1c7607c629ab503ebf77a62b9242bae2b94d47dcb71e", [:rebar3], [], "hexpm"}, + "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, "keccakf1600": {:git, "https://github.com/diodechain/erlang-keccakf1600.git", "d121a7828a2d217112e55a8815ba575a6261f8c5", []}, - "libsecp256k1": {:git, "https://github.com/diodechain/libsecp256k1.git", "812c8091899b9ea7f73d59a1d2738067d9e075c4", []}, + "libsecp256k1": {:git, "https://github.com/diodechain/libsecp256k1.git", "3a52d0ebb2d8f1e5a18c9b02e8f3160523755ee6", []}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.1", "0de4c81303fe07806ebc2494d5321ce8fb4df106e34dd5f9d787b637ebadc256", [:mix], [], "hexpm", "7a86b920d2aedce5fb6280ac8261ac1a739ae6c1a1ad38f5eadf910063008942"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mix_erlang_tasks": {:hex, :mix_erlang_tasks, "0.1.0", "36819fec60b80689eb1380938675af215565a89320a9e29c72c70d97512e4649", [:mix], [], "hexpm", "95d2839c422c482a70c08a8702da8242f86b773f8ab6e8602a4eb72da8da04ed"}, "mix_rebar3": {:hex, :mix_rebar3, "0.2.0", "b33656ef3047f21a19fac3254cb30a1d2c75ea419a3ad28c4b88f42c62a4202d", [:mix], [], "hexpm", "11eabb70c0a7ead9aa3631f048c3d7d5e868172b87b6493d0dc6f6d591c1afae"}, "niffler": {:hex, :niffler, "0.3.0", "e82b5d348ed22060b5200ca381f4102b749d9fd238002e703d005d8e22ccf33d", [:mix, :rebar3], [{:mix_rebar3, "~> 0.2", [hex: :mix_rebar3, repo: "hexpm", optional: false]}], "hexpm", "abf45448f88d3fbb74797883397931e2940cbbb1b400d938658affb4fb2a5ff1"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"}, + "oncrash": {:hex, :oncrash, "0.1.0", "9cf4ae8eba4ea250b579470172c5e9b8c75418b2264de7dbcf42e408d62e30fb", [:mix], [], "hexpm", "6968e775491cd857f9b6ff940bf2574fd1c2fab84fa7e14d5f56c39174c00018"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"}, "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, @@ -35,7 +43,9 @@ "profiler": {:git, "https://github.com/dominicletz/profiler.git", "fa164ca623b755f11e4065ceb74e8f67a578e577", []}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "sha3": {:hex, :sha3, "2.0.0", "fbe5db75a8389fdc810b682446c053b91e591d38d5422808f3a702ed2b270515", [:rebar3], [{:hex2bin, "1.0.0", [hex: :hex2bin, repo: "hexpm", optional: false]}], "hexpm"}, - "sqlitex": {:git, "https://github.com/diodechain/sqlitex.git", "165f909690599074ca26cfabad9989440d4b6219", []}, + "sqlitex": {:git, "https://github.com/diodechain/sqlitex.git", "17bc3cb6ca1fe7ff7ebb65dd24ff97b1630f3df1", []}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "while": {:hex, :while, "0.2.3", "71a85cca7c979baad2d2968d390dda41527056b81c22a5b422e8cc4d02697f30", [:mix], [], "hexpm", "49e40bd15c219325e2586d1d7166b561e74b90bc02f2ad35890b0346d37ebadb"}, } diff --git a/Dockerfile b/scripts/Dockerfile similarity index 82% rename from Dockerfile rename to scripts/Dockerfile index 0b7a7d3..e911835 100644 --- a/Dockerfile +++ b/scripts/Dockerfile @@ -6,6 +6,8 @@ FROM elixir:1.14.4 RUN apt-get update && apt-get install -y libboost-dev libboost-system-dev ENV MIX_ENV=prod +ENV PORT=8080 +ENV ERL_EPMD_ADDRESS=127.0.0.1 COPY mix.* /app/ @@ -17,4 +19,4 @@ COPY . /app/ RUN mix do compile, git_version -CMD ["elixir", "-S", "mix", "run", "--no-halt"] +ENTRYPOINT ["scripts/entrypoint"] diff --git a/clean.sh b/scripts/clean.sh similarity index 100% rename from clean.sh rename to scripts/clean.sh diff --git a/scripts/docker b/scripts/docker new file mode 100755 index 0000000..90562d8 --- /dev/null +++ b/scripts/docker @@ -0,0 +1,50 @@ +#!/bin/bash +# Diode Server +# Copyright 2021 Diode +# Licensed under the Diode License, Version 1.1 +set -e +mkdir -p data_prod +MD5=md5sum +if [ -f "$HOME/.erlang.cookie" ]; then + COOKIE=`cat $HOME/.erlang.cookie` +else + if [[ "$OSTYPE" == "darwin"* ]] + then + MD5=md5 + fi + COOKIE=`echo $RANDOM | $MD5 | head -c 20` + echo $COOKIE > "$HOME/.erlang.cookie" +fi + +docker build . -t diode -f scripts/Dockerfile + +case "$1" in + "remsh") + exec docker run \ + --network=host -ti \ + -e COOKIE=$COOKIE \ + diode remsh + ;; + "-f") + exec docker run \ + --network=host -ti \ + -e COOKIE=$COOKIE \ + --mount type=bind,source="$(pwd)/data_prod",target=/app/data_prod \ + --name diode \ + diode + ;; + "") + exec docker run \ + -d \ + --restart unless-stopped \ + --network=host -ti \ + -e COOKIE=$COOKIE \ + --mount type=bind,source="$(pwd)/data_prod",target=/app/data_prod \ + --name diode \ + diode + ;; + "") + echo "Usage: $0 <''|-f|remsh>" + exit 1 + ;; +esac diff --git a/scripts/entrypoint b/scripts/entrypoint new file mode 100755 index 0000000..e6c63f6 --- /dev/null +++ b/scripts/entrypoint @@ -0,0 +1,17 @@ +#!/bin/bash +set -e + +case "$1" in + "remsh") + export ELIXIR_ERL_OPTIONS="-setcookie $COOKIE" + exec iex --sname remsh_$$ --remsh diode + ;; + "") + export ELIXIR_ERL_OPTIONS="+sbwt none -noinput -noshell -sname diode +A 8" + exec iex -S mix run --no-halt + ;; + *) + echo "Usage: $0 <''|remsh>" + exit 1 + ;; +esac \ No newline at end of file diff --git a/evmbench.exs b/scripts/evmbench.exs similarity index 100% rename from evmbench.exs rename to scripts/evmbench.exs diff --git a/evmbench.sh b/scripts/evmbench.sh similarity index 100% rename from evmbench.sh rename to scripts/evmbench.sh diff --git a/run_beta b/scripts/run_beta similarity index 100% rename from run_beta rename to scripts/run_beta diff --git a/run_offline b/scripts/run_offline similarity index 100% rename from run_offline rename to scripts/run_offline diff --git a/run_perf b/scripts/run_perf similarity index 100% rename from run_perf rename to scripts/run_perf diff --git a/run_test b/scripts/run_test similarity index 100% rename from run_test rename to scripts/run_test diff --git a/test/abi_test.exs b/test/abi_test.exs new file mode 100644 index 0000000..0e686bc --- /dev/null +++ b/test/abi_test.exs @@ -0,0 +1,116 @@ +# Diode Server +# Copyright 2021 Diode +# Licensed under the Diode License, Version 1.1 +defmodule ABITest do + use ExUnit.Case + + test "reference" do + assert EIP712.encode_single_type("Person", [ + {"name", "string"}, + {"wallet", "address"} + ]) == "Person(string name,address wallet)" + + assert hex( + EIP712.type_hash("Person", %{ + "Person" => [ + {"name", "string"}, + {"wallet", "address"} + ] + }) + ) == "0xb9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500" + + assert EIP712.encode_single_type("EIP712Domain", [ + {"name", "string"}, + {"version", "string"}, + {"chainId", "uint256"}, + {"verifyingContract", "address"} + ]) == + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + + assert EIP712.encode_type("Transaction", %{ + "Transaction" => [ + {"from", "Person"}, + {"to", "Person"}, + {"tx", "Asset"} + ], + "Person" => [ + {"wallet", "address"}, + {"name", "string"} + ], + "Asset" => [ + {"token", "address"}, + {"amount", "uint256"} + ] + }) == + "Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)" + + domain_separator = + EIP712.hash_struct("EIP712Domain", [ + {"name", "string", "Ether Mail"}, + {"version", "string", "1"}, + {"chainId", "uint256", 1}, + {"verifyingContract", "address", unhex("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC")} + ]) + + assert hex(domain_separator) == + "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" + + assert EIP712.hash_domain_separator( + "Ether Mail", + "1", + 1, + unhex("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC") + ) == domain_separator + + assert hex( + EIP712.encode(domain_separator, "Person", [ + {"name", "string", "Bob"}, + {"wallet", "address", unhex("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB")} + ]) + ) == "0x0a94cf6625e5860fc4f330d75bcd0c3a4737957d2321d1a024540ab5320fe903" + end + + test "eip-712" do + cow = Wallet.from_privkey(Hash.keccak_256("cow")) + assert hex(Wallet.address!(cow)) == "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826" + + type_data = %{ + "Person" => [ + {"name", "string"}, + {"wallet", "address"} + ], + "Mail" => [ + {"from", "Person"}, + {"to", "Person"}, + {"contents", "string"} + ] + } + + domain_separator = + EIP712.hash_domain_separator( + "Ether Mail", + "1", + 1, + unhex("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC") + ) + + digest = + EIP712.encode(domain_separator, "Mail", type_data, %{ + "from" => %{ + "name" => "Cow", + "wallet" => unhex("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826") + }, + "to" => %{ + "name" => "Bob", + "wallet" => unhex("0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB") + }, + "contents" => "Hello, Bob!" + }) + + assert hex(Wallet.eth_sign!(cow, digest, nil, :none)) == + "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" + end + + defp hex(x), do: Base16.encode(x) + defp unhex(x), do: Base16.decode(x) +end