From 82a5d3576a161a9c70bca95edda4f95a50cfd0ba Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 14 Aug 2023 18:22:44 +0200 Subject: [PATCH 01/14] Added 'isonline' edge2 call --- lib/network/edge_v2.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/network/edge_v2.ex b/lib/network/edge_v2.ex index dd2caa4..9d2559e 100644 --- a/lib/network/edge_v2.ex +++ b/lib/network/edge_v2.ex @@ -358,6 +358,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 From bbd46992a429b44eae3381dcaf200213174f1bfd Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Wed, 13 Sep 2023 00:10:59 +0200 Subject: [PATCH 02/14] Updated fabfile --- deployment/fabfile.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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") From f4157415632638b6e1e889f95d64e27e8bc78ac0 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Wed, 13 Sep 2023 00:12:50 +0200 Subject: [PATCH 03/14] Fixed warning --- lib/shell.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 335bb693b61155da65e5c85e1fece1e69ac981bb Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Thu, 28 Sep 2023 12:18:19 +0200 Subject: [PATCH 04/14] wip --- lib/chain_wrap.ex | 18 + lib/diode.ex | 6 +- lib/moonbeam/call_permit.ex | 143 +++++ lib/moonbeam/eip712.ex | 98 +++ lib/network/edge_v3.ex | 1154 +++++++++++++++++++++++++++++++++++ lib/network/peer_handler.ex | 17 +- lib/wallet.ex | 5 + mix.exs | 1 + mix.lock | 9 + test/abi_test.exs | 116 ++++ 10 files changed, 1555 insertions(+), 12 deletions(-) create mode 100644 lib/chain_wrap.ex create mode 100644 lib/moonbeam/call_permit.ex create mode 100644 lib/moonbeam/eip712.ex create mode 100644 lib/network/edge_v3.ex create mode 100644 test/abi_test.exs diff --git a/lib/chain_wrap.ex b/lib/chain_wrap.ex new file mode 100644 index 0000000..942b002 --- /dev/null +++ b/lib/chain_wrap.ex @@ -0,0 +1,18 @@ +defmodule ChainWrap do + def epoch() do + 0 + end + + def peak() do + 1000 + end + + def genesis_hash() do + # https://moonbase.moonscan.io/block/0 + 0x33638DDE636F9264B6472B9D976D58E757FE88BADAC53F204F3F530ECC5AACFA + end + + def light_node?() do + true + end +end diff --git a/lib/diode.ex b/lib/diode.ex index 1b5fba4..e37e4c5 100644 --- a/lib/diode.ex +++ b/lib/diode.ex @@ -78,8 +78,6 @@ defmodule Diode do supervisor(Model.Sql), supervisor(Channels), worker(PubSub, [args]), - worker(Chain.BlockCache, [ets_extra]), - worker(Chain, [ets_extra]), worker(Chain.Pool, [args]), worker(TicketStore, [ets_extra]) ] @@ -395,9 +393,9 @@ defmodule Diode do def self(hostname) do Object.Server.new(hostname, hd(edge2_ports()), peer_port(), version(), [ - ["tickets", TicketStore.value(Chain.epoch())], + ["tickets", TicketStore.value(ChainWrap.epoch())], ["uptime", Diode.uptime()], - ["block", Chain.peak()] + ["block", ChainWrap.peak()] ]) |> Object.Server.sign(Wallet.privkey!(Diode.miner())) end diff --git a/lib/moonbeam/call_permit.ex b/lib/moonbeam/call_permit.ex new file mode 100644 index 0000000..02cef1f --- /dev/null +++ b/lib/moonbeam/call_permit.ex @@ -0,0 +1,143 @@ +defmodule CallPermit do + # https://github.com/moonbeam-foundation/moonbeam/blob/master/precompiles/call-permit/CallPermit.sol + @address Base16.decode("0x000000000000000000000000000000000000080A") + # Moonbase Alpha (0x507) + # @chain_id 1287 + @domain_separator Base16.decode( + "0x2d44830364594de15bf34f87ca86da8d1967e5bc7d64b301864028acb9120412" + ) + + def wallet() do + {: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 + + @endpoint "https://moonbeam-alpha.api.onfinality.io/public" + # @endpoint "https://rpc.api.moonbase.moonbeam.network" + def call(data) do + rpc("eth_call", [ + %{ + to: Base16.encode(@address), + data: Base16.encode(data) + }, + "latest" + ]) + end + + def blockNumber() do + rpc("eth_blockNumber") + end + + def rpc(method, params \\ []) do + request = %{ + jsonrpc: "2.0", + method: method, + params: params, + id: 1 + } + + {:ok, %{body: body}} = + HTTPoison.post(@endpoint, Jason.encode!(request), [{"Content-Type", "application/json"}]) + + case Jason.decode!(body) do + %{"result" => result} -> + result + + %{"error" => error} -> + raise "RPC error: #{inspect(error)}" + end + end + + def call_permit(from, to, value, data, gaslimit, deadline) do + nonce = call(nonces(from)) |> Base16.decode_int() + + signature = + EIP712.encode(@domain_separator, "CallPermit", [ + {"from", :address, from}, + {"to", :address, to}, + {"value", :uint256, value}, + {"data", :bytes, data}, + {"gaslimit", :uint64, gaslimit}, + {"nonce", :uint256, nonce} + ]) + + [v, r, s] = + Wallet.sign!(wallet(), signature) + |> Secp256k1.bitcoin_to_rlp() + + dispatch(from, to, value, data, gaslimit, deadline, v, r, s) + end + + # Making a BNS register call + def test() do + bns = 0x75140F88B0F4B2FBC6DADC16CC51203ADB07FE36 + + other_wallet = + {:wallet, + <<168, 72, 60, 32, 26, 244, 241, 69, 33, 155, 57, 121, 27, 21, 88, 28, 204, 144, 133, 121, + 249, 232, 26, 246, 75, 105, 137, 103, 59, 96, 250, 145>>, + <<2, 234, 139, 191, 188, 193, 51, 129, 233, 219, 195, 29, 184, 143, 180, 241, 241, 125, + 151, 17, 194, 147, 145, 66, 214, 210, 149, 111, 30, 152, 115, 246, 28>>, + <<111, 57, 182, 104, 128, 82, 13, 111, 164, 185, 235, 166, 127, 6, 48, 118, 214, 19, 2, 6>>} + + gas_limit = 500_000 + deadline = 1_800_000_000 + + call = + ABI.encode_call("register", ["string", "address"], [ + "test_account12", + Wallet.address!(other_wallet) + ]) + + call_permit(Wallet.address!(other_wallet), bns, 0, call, gas_limit, deadline) + |> call() + end + + # CallPermit.call(CallPermit.domain_separator()) +end diff --git a/lib/moonbeam/eip712.ex b/lib/moonbeam/eip712.ex new file mode 100644 index 0000000..872484b --- /dev/null +++ b/lib/moonbeam/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/network/edge_v3.ex b/lib/network/edge_v3.ex new file mode 100644 index 0000000..8e3368a --- /dev/null +++ b/lib/network/edge_v3.ex @@ -0,0 +1,1154 @@ +# Diode Server +# Copyright 2021 Diode +# Licensed under the Diode License, Version 1.1 +defmodule Network.EdgeV3 do + use Network.Handler + require Logger + alias Object.Ticket + alias Object.Channel + import Ticket, only: :macros + import Channel, only: :macros + + defmodule PortClient do + defstruct pid: nil, mon: nil, ref: nil, write: true + + @type t :: %PortClient{ + pid: pid(), + mon: reference(), + write: true | false + } + end + + defmodule Port do + defstruct state: nil, + ref: nil, + from: nil, + clients: [], + portname: nil, + shared: false, + mailbox: :queue.new() + + @type ref :: binary() + @type t :: %Port{ + state: :open | :pre_open, + ref: ref(), + from: nil | {pid(), reference()}, + clients: [PortClient.t()], + portname: any(), + shared: true | false, + mailbox: :queue.queue(binary()) + } + end + + @moduledoc """ + There are currently three access rights for "Ports" which are + loosely following Posix conventions: + 1) r = Read + 2) w = Write + 3) s = Shared + """ + defmodule PortCollection do + defstruct refs: %{} + @type t :: %PortCollection{refs: %{Port.ref() => Port.t()}} + + @spec put(PortCollection.t(), Port.t()) :: PortCollection.t() + def put(pc, port) do + %{pc | refs: Map.put(pc.refs, port.ref, port)} + end + + @spec delete(PortCollection.t(), Port.ref()) :: PortCollection.t() + def delete(pc, ref) do + %{pc | refs: Map.delete(pc.refs, ref)} + end + + @spec get(PortCollection.t(), Port.ref(), any()) :: Port.t() | nil + def get(pc, ref, default \\ nil) do + Map.get(pc.refs, ref, default) + end + + @spec get_clientref(PortCollection.t(), Port.ref()) :: {PortClient.t(), Port.t()} | nil + def get_clientref(pc, cref) do + Enum.find_value(pc.refs, fn {_ref, port} -> + Enum.find_value(port.clients, fn + client = %PortClient{ref: ^cref} -> {client, port} + _ -> nil + end) + end) + end + + @spec get_clientmon(PortCollection.t(), reference()) :: {PortClient.t(), Port.t()} | nil + def get_clientmon(pc, cmon) do + Enum.find_value(pc.refs, fn {_ref, port} -> + Enum.find_value(port.clients, fn + client = %PortClient{mon: ^cmon} -> {client, port} + _ -> nil + end) + end) + end + + @spec find_sharedport(PortCollection.t(), Port.t()) :: Port.t() | nil + def find_sharedport(_pc, %Port{shared: false}) do + nil + end + + def find_sharedport(pc, %Port{portname: portname}) do + Enum.find_value(pc.refs, fn + {_ref, port = %Port{state: :open, portname: ^portname, shared: true}} -> port + {_ref, _other} -> nil + end) + end + end + + @type state :: %{ + socket: any(), + inbuffer: nil | {integer(), binary()}, + blocked: :queue.queue(tuple()), + compression: nil | :zlib, + extra_flags: [], + node_id: Wallet.t(), + node_address: :inet.ip_address(), + ports: PortCollection.t(), + unpaid_bytes: integer(), + unpaid_rx_bytes: integer(), + last_ticket: Time.t(), + last_message: Time.t(), + pid: pid(), + sender: pid() + } + + def do_init(state) do + PubSub.subscribe({:edge, device_address(state)}) + + state = + Map.merge(state, %{ + ports: %PortCollection{}, + inbuffer: nil, + blocked: :queue.new(), + compression: nil, + extra_flags: [], + unpaid_bytes: 0, + unpaid_rx_bytes: 0, + last_ticket: nil, + last_message: Time.utc_now(), + pid: self(), + sender: Network.Sender.new(state.socket) + }) + + log(state, "accepted connection") + {:noreply, must_have_ticket(state)} + end + + defp must_have_ticket(state = %{last_ticket: last}) do + Process.send_after(self(), {:must_have_ticket, last}, 20_000) + state + end + + def ssl_options(opts) do + Network.Server.default_ssl_options(opts) + |> Keyword.put(:packet, :raw) + end + + def monitor(this, pid) do + if self() == this do + Process.monitor(pid) + else + call(this, {:monitor, pid}) + end + end + + defp call(pid, msg, timeout \\ :infinity) do + GenServer.call(pid, msg, timeout) + end + + @impl true + def handle_call(fun, from, state) when is_function(fun) do + fun.(from, state) + end + + def handle_call(:socket, _from, state) do + {:reply, state.socket, state} + end + + def handle_call({:socket_do, fun}, _from, state) do + {:reply, fun.(state.socket), state} + end + + def handle_call({:monitor, pid}, _from, state) do + {:reply, Process.monitor(pid), state} + end + + @max_preopen_ports 5 + def handle_call({:portopen, pid, ref, flags, portname, device_address}, from, state) do + cond do + Enum.count(state.ports.refs, fn {_key, %Port{state: pstate}} -> pstate == :pre_open end) > + @max_preopen_ports -> + # Send message to ensure this is still an active port + Process.send_after(self(), {:check_activity, state.last_message}, 15_000) + {:reply, {:error, "too many hanging ports"}, state} + + PortCollection.get(state.ports, ref) != nil -> + {:reply, {:error, "already opening"}, state} + + true -> + mon = monitor(state.pid, pid) + + client = %PortClient{ + mon: mon, + pid: pid, + ref: ref, + write: String.contains?(flags, "r") + } + + port = %Port{ + state: :pre_open, + from: from, + clients: [client], + portname: portname, + shared: String.contains?(flags, "s"), + ref: ref + } + + case PortCollection.find_sharedport(state.ports, port) do + nil -> + state = + send_socket(state, {:port, ref}, random_ref(), [ + "portopen", + portname, + ref, + device_address + ]) + + ports = PortCollection.put(state.ports, port) + {:noreply, %{state | ports: ports}} + + existing_port -> + port = %Port{existing_port | clients: [client | existing_port.clients]} + ports = PortCollection.put(state.ports, port) + {:reply, {:ok, existing_port.ref}, %{state | ports: ports}} + end + end + end + + @impl true + def handle_cast(fun, state) when is_function(fun) do + fun.(state) + end + + def handle_cast({:portsend, ref, data, _pid}, state) do + if PortCollection.get(state.ports, ref) != nil do + {:noreply, send_socket(state, {:port, ref}, random_ref(), ["portsend", ref, data])} + else + {:noreply, state} + end + end + + def handle_cast({:portclose, ref}, state) do + {:noreply, portclose(state, ref)} + end + + def handle_cast(:stop, state) do + log(state, "connection closed because of handshake anomaly.") + {:stop, :normal, state} + end + + @impl true + def terminate(reason, %{sender: sender}) do + # log(state, "Received terminate ~p ~p", [reason, state]) + if reason == :normal do + Network.Sender.stop(sender) + end + + reason + end + + def terminate(reason, state) do + log(state, "Received terminate before init ~p ~p", [reason, state]) + reason + end + + def handle_msg(msg, state) do + case msg do + ["hello", vsn | flags] when is_binary(vsn) -> + if to_num(vsn) != 1_000 do + {error("version not supported"), state} + else + state1 = + Enum.reduce(flags, state, fn flag, state -> + case flag do + "zlib" -> %{state | compression: :zlib} + other -> %{state | extra_flags: [other | state.extra_flags]} + end + end) + + # If compression has been enabled then on the next frame + state = %{state | compression: state1.compression, extra_flags: state1.extra_flags} + {response("ok"), state} + end + + ["ticket" | rest = [_block, _fc, _tc, _tb, _la, _ds]] -> + handle_ticket(rest, state) + + ["bytes"] -> + # This is an exception as unpaid_bytes can be negative + {response(Rlpx.int2bin(state.unpaid_bytes)), state} + + ["portsend", ref, data] -> + case PortCollection.get(state.ports, ref) do + nil -> + error("port does not exist") + + %Port{state: :open, clients: clients} -> + for client <- clients do + if client.write do + GenServer.cast(client.pid, {:portsend, client.ref, data, state.pid}) + end + end + + response("ok") + end + + _other -> + :async + end + end + + def handle_async_msg(msg, state) do + case msg do + ["ping"] -> + response("pong") + + ["channel", block_number, fleet, type, name, params, signature] -> + obj = + channel( + server_id: Diode.miner() |> Wallet.address!(), + block_number: to_num(block_number), + fleet_contract: fleet, + type: type, + name: name, + params: params, + signature: signature + ) + + device = Object.Channel.device_address(obj) + + cond do + not Wallet.equal?(device, device_id(state)) -> + error("invalid channel signature") + + not Contract.Fleet.device_allowlisted?(fleet, device) -> + error("device not whitelisted for this fleet") + + not Object.Channel.valid_type?(obj) -> + error("invalid channel type") + + not Object.Channel.valid_params?(obj) -> + error("invalid channel parameters") + + true -> + key = Object.Channel.key(obj) + + case Kademlia.find_value(key) do + nil -> + Kademlia.store(obj) + Object.encode_list!(obj) + + binary -> + Object.encode_list!(Object.decode!(binary)) + end + |> 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 + binary -> Object.encode_list!(Object.decode!(binary)) + end + |> response() + + ["getnode", node] -> + case Kademlia.find_node(node) do + nil -> nil + item -> Object.encode_list!(KBuckets.object(item)) + end + |> response() + + ["getblockpeak"] -> + response(Chain.peak()) + + ["getblock", index] when is_binary(index) -> + response(BlockProcess.with_block(to_num(index), &Chain.Block.export(&1))) + + ["getblockheader", index] when is_binary(index) -> + response(block_header(to_num(index))) + + ["getblockheader2", index] when is_binary(index) -> + header = block_header(to_num(index)) + pubkey = Chain.Header.recover_miner(header) |> Wallet.pubkey!() + response(header, pubkey) + + ["getblockquick", last_block, window_size] + when is_binary(last_block) and + is_binary(window_size) -> + window_size = to_num(window_size) + last_block = to_num(last_block) + # this will throw if the block does not exist + block_header(last_block) + + get_blockquick_seq(last_block, window_size) + |> Enum.map(fn num -> + header = block_header(num) + miner = Chain.Header.recover_miner(header) |> Wallet.pubkey!() + {header, miner} + end) + |> response() + + ["getblockquick2", last_block, window_size] + when is_binary(last_block) and + is_binary(window_size) -> + get_blockquick_seq(to_num(last_block), to_num(window_size)) + |> response() + + ["getstateroots", index] -> + BlockProcess.with_block(to_num(index), fn block -> + Chain.Block.state_tree(block) + |> MerkleTree.root_hashes() + end) + |> response() + + ["getaccount", index, id] -> + BlockProcess.with_block(to_num(index), fn block -> + Chain.Block.state(block) + |> Chain.State.account(id) + |> case do + nil -> + error("account does not exist") + + account = %Chain.Account{} -> + proof = + Chain.Block.state_tree(block) + |> MerkleTree.get_proofs(id) + + response( + %{ + nonce: account.nonce, + balance: account.balance, + storage_root: Chain.Account.root_hash(account), + code: Chain.Account.codehash(account) + }, + proof + ) + end + end) + + ["getaccountroots", index, id] -> + BlockProcess.with_account(to_num(index), id, fn + nil -> error("account does not exist") + acc -> response(MerkleTree.root_hashes(Chain.Account.tree(acc))) + end) + + ["getaccountvalue", index, id, key] -> + BlockProcess.with_account(to_num(index), id, fn + nil -> error("account does not exist") + acc -> response(MerkleTree.get_proofs(Chain.Account.tree(acc), key)) + end) + + ["getaccountvalues", index, id | keys] -> + BlockProcess.with_account(to_num(index), id, fn + nil -> + error("account does not exist") + + acc -> + tree = MerkleTree.merkle(Chain.Account.tree(acc)) + + response( + Enum.map(keys, fn key -> + MerkleTree.get_proofs(tree, key) + end) + ) + end) + + ["portopen", device_id, port, flags] -> + portopen(state, device_id, to_num(port), flags) + + ["portopen", device_id, port] -> + portopen(state, device_id, to_num(port), "rw") + + # "portopen" response + ["response", ref, "ok"] -> + call(state.pid, fn _from, state -> + case PortCollection.get(state.ports, ref) do + port = %Port{state: :pre_open} -> + GenServer.reply(port.from, {:ok, ref}) + ports = PortCollection.put(state.ports, %Port{port | state: :open, from: nil}) + {:reply, :ok, %{state | ports: ports}} + + nil -> + log(state, "ignoring response for undefined ref ~p", [ref]) + {:reply, :ok, state} + end + end) + + nil + + # "portopen" error + ["error", ref, reason] -> + port = %Port{state: :pre_open} = PortCollection.get(state.ports, ref) + GenServer.reply(port.from, {:error, reason}) + + call(state.pid, fn _from, state -> + {:reply, :ok, portclose(state, port, false)} + end) + + nil + + ["portclose", ref] -> + case PortCollection.get(state.ports, ref) do + nil -> + error("port does not exit") + + port = %Port{state: :open} -> + call(state.pid, fn _from, state -> + {:reply, :ok, portclose(state, port, false)} + end) + + response("ok") + end + + ["sendtransaction", tx] -> + # Testing transaction + tx = Chain.Transaction.from_rlp(tx) + + err = + Chain.with_peak(fn peak -> + state = Chain.Block.state(peak) + + case Chain.Transaction.apply(tx, peak, state) do + {:ok, _state, %{msg: :ok}} -> nil + {:ok, _state, rcpt} -> "Transaction exception: #{rcpt.msg}" + {:error, :nonce_too_high} -> nil + {:error, reason} -> "Transaction failed: #{inspect(reason)}" + end + end) + + # Adding transacton, even when :nonce_too_high + if err == nil do + Chain.Pool.add_transaction(tx, true) + + if Diode.dev_mode?() do + Chain.Worker.work() + end + + response("ok") + else + error(400, err) + end + + nil -> + log(state, "Unhandled message: ~40s~n", [truncate(msg)]) + error(400, "that is not rlp") + + _ -> + log(state, "Unhandled message: ~40s~n", [truncate(msg)]) + error(401, "bad input") + end + end + + defp response(arg) do + response_array([arg]) + end + + defp response(arg, arg2) do + response_array([arg, arg2]) + end + + defp response_array(args) do + ["response" | args] + end + + defp error(code, message) do + ["error", code, message] + end + + defp error(message) do + ["error", message] + end + + defp handle_packet(raw_msg, state) do + state = account_incoming(state, raw_msg) + msg = decode(state, raw_msg) + + # should be [request_id, method_params, opts] + case msg do + [request_id, method_params, opts] -> + handle_request(state, to_num(request_id), method_params, opts) + + [request_id, method_params] -> + handle_request(state, to_num(request_id), method_params, []) + + _other -> + log(state, "connection closed because wrong message received.") + {:stop, :normal, state} + end + end + + defp handle_data("", state) do + {:noreply, state} + end + + defp handle_data(<<0::unsigned-size(16), rest::binary>>, state = %{inbuffer: nil}) do + handle_data(rest, state) + end + + defp handle_data(<>, state = %{inbuffer: nil}) do + handle_data(length, raw_msg, state) + end + + defp handle_data(<>, state = %{inbuffer: {length, buffer}}) do + handle_data(length, buffer <> more, %{state | inbuffer: nil}) + end + + defp handle_data(length, raw_msg, state) do + if byte_size(raw_msg) >= length do + {:noreply, state} = handle_packet(binary_part(raw_msg, 0, length), state) + rest = binary_part(raw_msg, length, byte_size(raw_msg) - length) + handle_data(rest, %{state | inbuffer: nil}) + else + {:noreply, %{state | inbuffer: {length, raw_msg}}} + end + end + + @impl true + def handle_info({:check_activity, then_last_message}, state = %{last_message: now_last_message}) do + if then_last_message == now_last_message do + {:stop, :no_activity_timeout, state} + else + {:noreply, state} + end + end + + def handle_info({:ssl, _socket, data}, state) do + handle_data(data, %{state | last_message: Time.utc_now()}) + end + + def handle_info({:topic, _topic, _message}, state) do + throw(:notimpl) + # state = send_socket(state, random_ref(), [topic, message]) + {:noreply, state} + end + + def handle_info({:stop_unpaid, b0}, state = %{unpaid_bytes: b}) do + log(state, "connection closed because unpaid #{b0}(#{b}) bytes.") + {:stop, :normal, state} + end + + def handle_info({:must_have_ticket, last}, state = %{last_ticket: timestamp}) do + if timestamp == nil or timestamp == last do + log(state, "connection closed because no valid ticket sent within time limit.") + {:stop, :normal, state} + else + {:noreply, state} + end + end + + def handle_info({:ssl_closed, _}, state) do + log(state, "connection closed by remote.") + {:stop, :normal, state} + end + + def handle_info({:DOWN, mon, _type, _object, _info}, state) do + {:noreply, portclose(state, mon)} + end + + def handle_info(msg, state) do + log(state, "Unhandled info: ~p", [msg]) + {:noreply, state} + end + + defp handle_request(state, request_id, method_params, _opts) do + case handle_msg(method_params, state) do + :async -> + pid = self() + + spawn_link(fn -> + result = handle_async_msg(method_params, state) + + GenServer.cast(pid, fn state2 -> + {:noreply, send_socket(state2, request_id, request_id, result)} + end) + end) + + {:noreply, state} + + {result, state} -> + {:noreply, send_socket(state, request_id, request_id, result)} + + result -> + {:noreply, send_socket(state, request_id, request_id, result)} + end + end + + defp handle_ticket( + [block, fleet, total_connections, total_bytes, local_address, device_signature], + state + ) do + dl = + ticket( + server_id: Wallet.address!(Diode.miner()), + fleet_contract: fleet, + total_connections: to_num(total_connections), + total_bytes: to_num(total_bytes), + local_address: local_address, + block_number: to_num(block), + device_signature: device_signature + ) + + cond do + Ticket.block_number(dl) > Chain.peak() -> + log( + state, + "Ticket with future block number #{Ticket.block_number(dl)} vs. #{Chain.peak()}!" + ) + + error("block number too high") + + not Ticket.device_address?(dl, device_id(state)) -> + log(state, "Received invalid ticket signature!") + error("signature mismatch") + + # TODO: Needs to be re-enabled after dev-contract is all-yes + # not Contract.Fleet.device_allowlisted?(fleet, device) -> + # log(state, "Received invalid ticket fleet!") + # error("device not whitelisted") + + true -> + dl = Ticket.server_sign(dl, Wallet.privkey!(Diode.miner())) + ret = TicketStore.add(dl, device_id(state)) + + # address = Ticket.device_address(dl) + # short = String.slice(Base16.encode(address), 0..7) + # total = Ticket.total_bytes(dl) + # unpaid = state.unpaid_bytes + # IO.puts("[#{short}] TICKET total: #{total} unpaid: #{unpaid} ret => #{inspect(ret)}") + + case ret do + {:ok, bytes} -> + key = Object.key(dl) + + # Storing the updated ticket of this device, debounce is 15 sec + Debouncer.immediate( + key, + fn -> + Model.KademliaSql.put_object(Kademlia.hash(key), Object.encode!(dl)) + Kademlia.store(dl) + end, + 15_000 + ) + + # Storing the updated ticket of this device, debounce is 10 sec + Debouncer.immediate( + :publish_me, + fn -> + me = Diode.self() + Kademlia.store(me) + end, + 10_000 + ) + + {response("thanks!", bytes), + %{state | unpaid_bytes: state.unpaid_bytes - bytes, last_ticket: Time.utc_now()}} + + {:too_old, min} -> + response("too_old", min) + + {:too_low, last} -> + response_array([ + "too_low", + Ticket.block_hash(last), + Ticket.total_connections(last), + Ticket.total_bytes(last), + Ticket.local_address(last), + Ticket.device_signature(last) + ]) + end + end + end + + defp truncate(msg) when is_binary(msg) and byte_size(msg) > 40 do + binary_part(msg, 0, 37) <> "..." + end + + defp truncate(msg) when is_binary(msg) do + msg + end + + defp truncate(other) do + :io_lib.format("~0p", [other]) + |> :erlang.iolist_to_binary() + |> truncate() + end + + defp portopen(state, <>, portname, flags) do + case Kademlia.find_value(channel_id) do + nil -> + error("not found") + + bin -> + channel = Object.decode!(bin) + + Object.Channel.server_id(channel) + |> Wallet.from_address() + |> Wallet.equal?(Diode.miner()) + |> if do + pid = Channels.ensure(channel) + do_portopen(state, device_address(state), state.pid, portname, flags, pid) + else + error("wrong host") + end + end + end + + defp portopen(state, device_id, portname, flags) do + address = device_address(state) + + cond do + device_id == address -> + error("can't connect to yourself") + + validate_flags(flags) == false -> + error("invalid flags") + + true -> + with <> <- device_id, + w <- Wallet.from_address(bin), + [pid | _] <- PubSub.subscribers({:edge, Wallet.address!(w)}) do + do_portopen(state, address, state.pid, portname, flags, pid) + else + [] -> error("not found") + other -> error("invalid address #{inspect(other)}") + end + end + end + + defp validate_flags("rw"), do: true + defp validate_flags("r"), do: true + defp validate_flags("w"), do: true + defp validate_flags("rws"), do: true + defp validate_flags("rs"), do: true + defp validate_flags("ws"), do: true + defp validate_flags(_), do: false + + defp random_ref() do + Random.uint31h() + |> to_bin() + + # :io.format("REF ~p~n", [ref]) + end + + defp do_portopen(state, device_address, this, portname, flags, pid) do + mon = monitor(this, pid) + ref = random_ref() + + # Receives an open request from another local connected edge worker. + # Now needs to forward the request to the device and remember to + # keep in 'pre-open' state until the device acks. + # Todo: Check for network access based on contract + resp = + try do + call(pid, {:portopen, this, ref, flags, portname, device_address}, 35_000) + catch + kind, what -> + log( + state, + "Portopen failed for #{Base16.encode(device_address)} #{inspect({kind, what})}" + ) + + :error + end + + case resp do + {:ok, cref} -> + client = %PortClient{ + pid: pid, + mon: mon, + ref: cref, + write: String.contains?(flags, "w") + } + + call(this, fn _from, state -> + ports = + PortCollection.put(state.ports, %Port{state: :open, clients: [client], ref: ref}) + + {:reply, :ok, %{state | ports: ports}} + end) + + response("ok", ref) + + {:error, reason} -> + Process.demonitor(mon, [:flush]) + error(reason) + + :error -> + Process.demonitor(mon, [:flush]) + error(ref) + end + end + + defp portclose(state, ref, action \\ true) + + # Closing whole port no matter how many clients + defp portclose(state, port = %Port{}, action) do + for client <- port.clients do + GenServer.cast(client.pid, {:portclose, port.ref}) + Process.demonitor(client.mon, [:flush]) + end + + state = + if action do + # {:current_stacktrace, what} = :erlang.process_info(self(), :current_stacktrace) + # :io.format("portclose from: ~p~n", [what]) + send_socket(state, {:port, port.ref}, random_ref(), ["portclose", port.ref]) + else + state + end + + %{state | ports: PortCollection.delete(state.ports, port.ref)} + end + + # Removing client but keeping port open if still >0 clients + defp portclose(state, clientmon, action) when is_reference(clientmon) do + do_portclose(state, PortCollection.get_clientmon(state.ports, clientmon), action) + end + + defp portclose(state, clientref, action) do + do_portclose(state, PortCollection.get_clientref(state.ports, clientref), action) + end + + defp do_portclose(state, nil, _action) do + state + end + + defp do_portclose(state, {client, %Port{clients: [client], ref: ref}}, action) do + Process.demonitor(client.mon, [:flush]) + + state = + if action do + # {:current_stacktrace, what} = :erlang.process_info(self(), :current_stacktrace) + # :io.format("portclose from: ~p~n", [what]) + send_socket(state, {:port, ref}, random_ref(), ["portclose", ref]) + else + state + end + + %{state | ports: PortCollection.delete(state.ports, ref)} + end + + defp do_portclose(state, {client, port}, _action) do + Process.demonitor(client.mon, [:flush]) + + %{ + state + | ports: + PortCollection.put(state.ports, %Port{port | clients: List.delete(port.clients, client)}) + } + end + + defp decode(state, msg) do + case state.compression do + nil -> msg + :zlib -> :zlib.unzip(msg) + end + |> Rlp.decode!() + end + + defp encode(msg) do + Rlp.encode!(msg) + end + + defp is_portsend({:port, _}), do: true + defp is_portsend(_), do: false + + defp send_threshold() do + Diode.ticket_grace() - Diode.ticket_grace() / 4 + end + + defp send_socket( + state = %{unpaid_bytes: unpaid}, + partition, + request_id, + data + ) do + cond do + # early exit + unpaid > Diode.ticket_grace() -> + send(self(), {:stop_unpaid, unpaid}) + msg = encode([random_ref(), ["goodbye", "ticket expected", "you might get blocked"]]) + :ok = do_send_socket(state, partition, msg) + account_outgoing(state, msg) + + # stopping port data, and ensure there is a ticket within 20s + unpaid > send_threshold() and is_portsend(partition) -> + %{state | blocked: :queue.in({partition, request_id, data}, state.blocked)} + |> account_outgoing() + |> must_have_ticket() + + true -> + state = + if data == nil do + account_outgoing(state) + else + msg = encode([request_id, data]) + :ok = do_send_socket(state, partition, msg) + account_outgoing(state, msg) + end + + # continue port data sending? + if state.unpaid_bytes < send_threshold() and not :queue.is_empty(state.blocked) do + {{:value, {partition, request_id, data}}, blocked} = :queue.out(state.blocked) + send_socket(%{state | blocked: blocked}, partition, request_id, data) + else + state + end + end + end + + defp do_send_socket(state, partition, msg) do + msg = + case state.compression do + nil -> msg + :zlib -> :zlib.zip(msg) + end + + length = byte_size(msg) + Network.Sender.push_async(state.sender, partition, <>) + end + + @spec device_id(state()) :: Wallet.t() + def device_id(%{node_id: id}), do: id + def device_address(%{node_id: id}), do: Wallet.address!(id) + + defp account_incoming(state = %{unpaid_rx_bytes: unpaid_rx}, msg) do + %{state | unpaid_rx_bytes: unpaid_rx + byte_size(msg)} + end + + defp account_outgoing(state = %{unpaid_bytes: unpaid, unpaid_rx_bytes: unpaid_rx}, msg \\ "") do + %{state | unpaid_bytes: unpaid + unpaid_rx + byte_size(msg), unpaid_rx_bytes: 0} + end + + def on_nodeid(_edge) do + :ok + end + + def get_blockquick_window(last_block, window_size) do + hash = Chain.blockhash(last_block) + + window = + Chain.Block.blockquick_window(hash) + |> Enum.reverse() + |> Enum.take(window_size) + + len = length(window) + + if len < window_size do + next_block = last_block - len + window ++ get_blockquick_window(next_block, window_size - len) + else + window + end + end + + def find_sequence(last_block, counts, score, threshold) do + window = + Chain.Block.blockquick_window(Chain.blockhash(last_block)) + |> Enum.reverse() + + Enum.reduce_while(window, {counts, score, last_block}, fn miner, + {counts, score, last_block} -> + {score_value, counts} = Map.pop(counts, miner, 0) + score = score + score_value + + if score > threshold do + {:halt, {:ok, last_block}} + else + {:cont, {counts, score, last_block - 1}} + end + end) + |> case do + {:ok, last_block} -> + {:ok, last_block} + + {counts, score, last_block} -> + find_sequence(last_block, counts, score, threshold) + end + end + + def get_blockquick_seq(last_block, window_size) do + # Step 1: Identifying current view the device has + # based on it's current last valid block number + window = get_blockquick_window(last_block, window_size) + counts = Enum.reduce(window, %{}, fn miner, acc -> Map.update(acc, miner, 1, &(&1 + 1)) end) + threshold = div(window_size, 2) + + # Step 2: Findind a provable sequence + # Iterating from peak backwards until the block score is over 50% of the window_size + peak = Chain.peak() + {:ok, provable_block} = find_sequence(peak, counts, 0, threshold) + + # Step 3: Filling gap between 'last_block' and provable sequence, but not + # by more than 'window_size' block heads before the provable sequence + begin = provable_block + size = max(min(window_size, begin - last_block) - 1, 1) + + gap_fill = Enum.to_list((begin - size)..(begin - 1)) + gap_fill ++ Enum.to_list(begin..peak) + + # # Step 4: Checking whether the the provable sequence can be shortened + # # TODO + # {:ok, heads} = + # Enum.reduce_while(heads, {counts, 0, []}, fn {head, miner}, + # {counts, score, heads} -> + # {value, counts} = Map.pop(counts, miner, 0) + # score = score + value + # heads = [{head, miner} | heads] + + # if score > threshold do + # {:halt, {:ok, heads}} + # else + # {:cont, {counts, score, heads}} + # end + # end) + end + + defp block_header(n) do + BlockProcess.with_block(n, fn + nil -> nil + block -> Chain.Header.strip_state(block.header) + end) || check_block(n) + end + + defp check_block(n) do + if is_integer(n) and n < Chain.peak() do + # we had some cases of missing blocks + # this means the async blockwriter did somehow? skip + # putting this block on disk. If this happens (which is a bug) + # the only course for recovery is to restart the node, which + # triggers an integrity check + Logger.error("missing block #{n}") + System.halt(1) + else + Logger.info("block #{inspect(n)} not found") + throw(:notfound) + end + end + + defp to_num(bin) do + Rlpx.bin2num(bin) + end + + defp to_bin(num) do + Rlpx.num2bin(num) + end +end diff --git a/lib/network/peer_handler.ex b/lib/network/peer_handler.ex index 49dac36..2be317e 100644 --- a/lib/network/peer_handler.ex +++ b/lib/network/peer_handler.ex @@ -138,7 +138,7 @@ defmodule Network.PeerHandler do hello = Diode.self(hostname) - case ssl_send(state, [@hello, Object.encode!(hello), Chain.genesis_hash()]) do + case ssl_send(state, [@hello, Object.encode!(hello), ChainWrap.genesis_hash()]) do {:noreply, state} -> receive do {:ssl, _socket, msg} -> @@ -201,17 +201,18 @@ defmodule Network.PeerHandler do end defp handle_msg([@hello, server, genesis_hash], state) do - genesis = Chain.genesis_hash() + genesis = ChainWrap.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) + state = + if ChainWrap.light_node?() do + state + else + publish_peak(state) + end if Map.has_key?(state, :peer_port) do {:noreply, state} 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..ff89ee9 100644 --- a/mix.exs +++ b/mix.exs @@ -94,6 +94,7 @@ defmodule Diode.Mixfile do {:sqlitex, github: "diodechain/sqlitex"}, {:niffler, "~> 0.1"}, {:while, "~> 0.2"}, + {:httpoison, "~> 2.0"}, # linting {:dialyxir, "~> 1.1", only: [:dev], runtime: false}, diff --git a/mix.lock b/mix.lock index 56d4d18..8533aa5 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"}, @@ -16,18 +17,24 @@ "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", []}, "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"}, + "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"}, @@ -36,6 +43,8 @@ "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", []}, + "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/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 From c14d6309cefa122feaeb2d6df028d7066cf6eb41 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Fri, 29 Sep 2023 16:12:26 +0200 Subject: [PATCH 05/14] wip --- lib/chain_wrap.ex | 18 - lib/diode.ex | 4 +- lib/{moonbeam => }/eip712.ex | 0 lib/moonbeam/call_permit.ex | 92 ++- lib/moonbeam/edge_m1.ex | 82 +++ lib/moonbeam/moonbeam.ex | 83 +++ lib/network/edge_v2.ex | 12 +- lib/network/edge_v3.ex | 1154 ---------------------------------- lib/network/peer_handler.ex | 6 +- 9 files changed, 215 insertions(+), 1236 deletions(-) delete mode 100644 lib/chain_wrap.ex rename lib/{moonbeam => }/eip712.ex (100%) create mode 100644 lib/moonbeam/edge_m1.ex create mode 100644 lib/moonbeam/moonbeam.ex delete mode 100644 lib/network/edge_v3.ex diff --git a/lib/chain_wrap.ex b/lib/chain_wrap.ex deleted file mode 100644 index 942b002..0000000 --- a/lib/chain_wrap.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule ChainWrap do - def epoch() do - 0 - end - - def peak() do - 1000 - end - - def genesis_hash() do - # https://moonbase.moonscan.io/block/0 - 0x33638DDE636F9264B6472B9D976D58E757FE88BADAC53F204F3F530ECC5AACFA - end - - def light_node?() do - true - end -end diff --git a/lib/diode.ex b/lib/diode.ex index e37e4c5..e2b0fb5 100644 --- a/lib/diode.ex +++ b/lib/diode.ex @@ -393,9 +393,9 @@ defmodule Diode do def self(hostname) do Object.Server.new(hostname, hd(edge2_ports()), peer_port(), version(), [ - ["tickets", TicketStore.value(ChainWrap.epoch())], + ["tickets", TicketStore.value(Moonbeam.epoch())], ["uptime", Diode.uptime()], - ["block", ChainWrap.peak()] + ["block", Moonbeam.peak()] ]) |> Object.Server.sign(Wallet.privkey!(Diode.miner())) end diff --git a/lib/moonbeam/eip712.ex b/lib/eip712.ex similarity index 100% rename from lib/moonbeam/eip712.ex rename to lib/eip712.ex diff --git a/lib/moonbeam/call_permit.ex b/lib/moonbeam/call_permit.ex index 02cef1f..7030cdd 100644 --- a/lib/moonbeam/call_permit.ex +++ b/lib/moonbeam/call_permit.ex @@ -1,8 +1,4 @@ defmodule CallPermit do - # https://github.com/moonbeam-foundation/moonbeam/blob/master/precompiles/call-permit/CallPermit.sol - @address Base16.decode("0x000000000000000000000000000000000000080A") - # Moonbase Alpha (0x507) - # @chain_id 1287 @domain_separator Base16.decode( "0x2d44830364594de15bf34f87ca86da8d1967e5bc7d64b301864028acb9120412" ) @@ -58,57 +54,23 @@ defmodule CallPermit do ABI.encode_call("DOMAIN_SEPARATOR", [], []) end - @endpoint "https://moonbeam-alpha.api.onfinality.io/public" - # @endpoint "https://rpc.api.moonbase.moonbeam.network" - def call(data) do - rpc("eth_call", [ - %{ - to: Base16.encode(@address), - data: Base16.encode(data) - }, - "latest" - ]) - end - - def blockNumber() do - rpc("eth_blockNumber") - end - - def rpc(method, params \\ []) do - request = %{ - jsonrpc: "2.0", - method: method, - params: params, - id: 1 - } - - {:ok, %{body: body}} = - HTTPoison.post(@endpoint, Jason.encode!(request), [{"Content-Type", "application/json"}]) - - case Jason.decode!(body) do - %{"result" => result} -> - result - - %{"error" => error} -> - raise "RPC error: #{inspect(error)}" - end - end - - def call_permit(from, to, value, data, gaslimit, deadline) do - nonce = call(nonces(from)) |> Base16.decode_int() + def call_permit(from_wallet, to, value, data, gaslimit, deadline) do + from = Wallet.address!(from_wallet) + nonce = Moonbeam.call!(nonces(from)) |> Base16.decode_int() signature = EIP712.encode(@domain_separator, "CallPermit", [ - {"from", :address, from}, - {"to", :address, to}, - {"value", :uint256, value}, - {"data", :bytes, data}, - {"gaslimit", :uint64, gaslimit}, - {"nonce", :uint256, nonce} + {"from", "address", from}, + {"to", "address", to}, + {"value", "uint256", value}, + {"data", "bytes", data}, + {"gaslimit", "uint64", gaslimit}, + {"nonce", "uint256", nonce}, + {"deadline", "uint256", deadline} ]) [v, r, s] = - Wallet.sign!(wallet(), signature) + Wallet.sign!(from_wallet, signature, :none) |> Secp256k1.bitcoin_to_rlp() dispatch(from, to, value, data, gaslimit, deadline, v, r, s) @@ -116,7 +78,7 @@ defmodule CallPermit do # Making a BNS register call def test() do - bns = 0x75140F88B0F4B2FBC6DADC16CC51203ADB07FE36 + bns = Base16.decode("0x75140F88B0F4B2FBC6DADC16CC51203ADB07FE36") other_wallet = {:wallet, @@ -130,14 +92,34 @@ defmodule CallPermit do deadline = 1_800_000_000 call = - ABI.encode_call("register", ["string", "address"], [ - "test_account12", + ABI.encode_call("Register", ["string", "address"], [ + "anotheraccount", Wallet.address!(other_wallet) ]) - call_permit(Wallet.address!(other_wallet), bns, 0, call, gas_limit, deadline) - |> call() + call_permit(other_wallet, bns, 0, call, gas_limit, deadline) + |> Moonbeam.call!() + end + + def test2() do + bns = Base16.decode("0x75140F88B0F4B2FBC6DADC16CC51203ADB07FE36") + + other_wallet = + {:wallet, + <<168, 72, 60, 32, 26, 244, 241, 69, 33, 155, 57, 121, 27, 21, 88, 28, 204, 144, 133, 121, + 249, 232, 26, 246, 75, 105, 137, 103, 59, 96, 250, 145>>, + <<2, 234, 139, 191, 188, 193, 51, 129, 233, 219, 195, 29, 184, 143, 180, 241, 241, 125, + 151, 17, 194, 147, 145, 66, 214, 210, 149, 111, 30, 152, 115, 246, 28>>, + <<111, 57, 182, 104, 128, 82, 13, 111, 164, 185, 235, 166, 127, 6, 48, 118, 214, 19, 2, 6>>} + + gas_limit = 500_000 + deadline = 1_800_000_000 + + call = ABI.encode_call("Version") + + call_permit(other_wallet, bns, 0, call, gas_limit, deadline) + |> Moonbeam.call!() end - # CallPermit.call(CallPermit.domain_separator()) + # 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..d0f4ef5 --- /dev/null +++ b/lib/moonbeam/edge_m1.ex @@ -0,0 +1,82 @@ +# Diode Server +# Copyright 2023 Diode +# Licensed under the Diode License, Version 1.1 +defmodule Network.EdgeM1 do + import Network.EdgeV2, only: [response: 1, error: 1] + + def handle_async_msg(msg, state) do + case msg do + ["getblockpeak"] -> + response(Moonbeam.block_number()) + + ["getblock", index] when is_binary(index) -> + error("not implemented") + + ["getblockheader", index] when is_binary(index) -> + response(Moonbeam.get_block_by_number(index)) + + ["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))) + + response(%{ + nonce: Moonbeam.get_transaction_count(hex_address(address), hex_blockref(block)), + balance: Moonbeam.get_balance(hex_address(address), hex_blockref(block)), + storage_root: nil, + code: nil + }) + + ["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))) + error("not implemented") + + ["getaccountvalues", _block, _address | _keys] -> + # requires https://eips.ethereum.org/EIPS/eip-1186 + # response(Moonbeam.proof(address, keys, blockref(block))) + error("not implemented") + + ["sendtransaction", tx] -> + # These are CallPermit metatransactions + # Testing transaction + [from, to, value, call, gaslimit, deadline, v, r, s] = Rlp.decode!(tx) + call = CallPermit.dispatch(from, to, value, call, gaslimit, deadline, v, r, s) + response(Moonbeam.call!(call)) + + _ -> + 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("0x" <> _rest = hex), do: hex + defp hex_blockref(n) when is_integer(n), do: Base16.encode(n, false) + defp hex_blockref(_other), do: throw(:notfound) + + defp hex_address(<<_::binary-size(20)>> = address) do + Base16.encode(address) + end +end diff --git a/lib/moonbeam/moonbeam.ex b/lib/moonbeam/moonbeam.ex new file mode 100644 index 0000000..5f53dfd --- /dev/null +++ b/lib/moonbeam/moonbeam.ex @@ -0,0 +1,83 @@ +defmodule Moonbeam do + # https://github.com/moonbeam-foundation/moonbeam/blob/master/precompiles/call-permit/CallPermit.sol + @address Base16.decode("0x000000000000000000000000000000000000080A") + @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 light_node?() do + true + 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_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 rpc!(method, params \\ []) do + request = %{ + jsonrpc: "2.0", + method: method, + params: params, + id: 1 + } + + {:ok, %{body: body}} = + HTTPoison.post(@endpoint, Jason.encode!(request), [{"Content-Type", "application/json"}]) + + case Jason.decode!(body) do + %{"result" => result} -> + result + + %{"error" => error} -> + raise "RPC error: #{inspect(error)}" + end + end + + def call!(data) do + rpc!("eth_call", [ + %{ + to: Base16.encode(@address), + data: Base16.encode(data) + }, + "latest" + ]) + end + + def chain_id() do + # Moonbase Alpha (0x507) + 1287 + end +end diff --git a/lib/network/edge_v2.ex b/lib/network/edge_v2.ex index 9d2559e..3e792d4 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.EdgeV2.handle_async_msg([cmd | rest], state) + ["ping"] -> response("pong") @@ -555,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 @@ -567,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 @@ -672,6 +675,7 @@ defmodule Network.EdgeV2 do pid = self() spawn_link(fn -> + OnCrash.call(fn err -> log(state, "Error: ~p", [err]) end) result = handle_async_msg(method_params, state) GenServer.cast(pid, fn state2 -> diff --git a/lib/network/edge_v3.ex b/lib/network/edge_v3.ex deleted file mode 100644 index 8e3368a..0000000 --- a/lib/network/edge_v3.ex +++ /dev/null @@ -1,1154 +0,0 @@ -# Diode Server -# Copyright 2021 Diode -# Licensed under the Diode License, Version 1.1 -defmodule Network.EdgeV3 do - use Network.Handler - require Logger - alias Object.Ticket - alias Object.Channel - import Ticket, only: :macros - import Channel, only: :macros - - defmodule PortClient do - defstruct pid: nil, mon: nil, ref: nil, write: true - - @type t :: %PortClient{ - pid: pid(), - mon: reference(), - write: true | false - } - end - - defmodule Port do - defstruct state: nil, - ref: nil, - from: nil, - clients: [], - portname: nil, - shared: false, - mailbox: :queue.new() - - @type ref :: binary() - @type t :: %Port{ - state: :open | :pre_open, - ref: ref(), - from: nil | {pid(), reference()}, - clients: [PortClient.t()], - portname: any(), - shared: true | false, - mailbox: :queue.queue(binary()) - } - end - - @moduledoc """ - There are currently three access rights for "Ports" which are - loosely following Posix conventions: - 1) r = Read - 2) w = Write - 3) s = Shared - """ - defmodule PortCollection do - defstruct refs: %{} - @type t :: %PortCollection{refs: %{Port.ref() => Port.t()}} - - @spec put(PortCollection.t(), Port.t()) :: PortCollection.t() - def put(pc, port) do - %{pc | refs: Map.put(pc.refs, port.ref, port)} - end - - @spec delete(PortCollection.t(), Port.ref()) :: PortCollection.t() - def delete(pc, ref) do - %{pc | refs: Map.delete(pc.refs, ref)} - end - - @spec get(PortCollection.t(), Port.ref(), any()) :: Port.t() | nil - def get(pc, ref, default \\ nil) do - Map.get(pc.refs, ref, default) - end - - @spec get_clientref(PortCollection.t(), Port.ref()) :: {PortClient.t(), Port.t()} | nil - def get_clientref(pc, cref) do - Enum.find_value(pc.refs, fn {_ref, port} -> - Enum.find_value(port.clients, fn - client = %PortClient{ref: ^cref} -> {client, port} - _ -> nil - end) - end) - end - - @spec get_clientmon(PortCollection.t(), reference()) :: {PortClient.t(), Port.t()} | nil - def get_clientmon(pc, cmon) do - Enum.find_value(pc.refs, fn {_ref, port} -> - Enum.find_value(port.clients, fn - client = %PortClient{mon: ^cmon} -> {client, port} - _ -> nil - end) - end) - end - - @spec find_sharedport(PortCollection.t(), Port.t()) :: Port.t() | nil - def find_sharedport(_pc, %Port{shared: false}) do - nil - end - - def find_sharedport(pc, %Port{portname: portname}) do - Enum.find_value(pc.refs, fn - {_ref, port = %Port{state: :open, portname: ^portname, shared: true}} -> port - {_ref, _other} -> nil - end) - end - end - - @type state :: %{ - socket: any(), - inbuffer: nil | {integer(), binary()}, - blocked: :queue.queue(tuple()), - compression: nil | :zlib, - extra_flags: [], - node_id: Wallet.t(), - node_address: :inet.ip_address(), - ports: PortCollection.t(), - unpaid_bytes: integer(), - unpaid_rx_bytes: integer(), - last_ticket: Time.t(), - last_message: Time.t(), - pid: pid(), - sender: pid() - } - - def do_init(state) do - PubSub.subscribe({:edge, device_address(state)}) - - state = - Map.merge(state, %{ - ports: %PortCollection{}, - inbuffer: nil, - blocked: :queue.new(), - compression: nil, - extra_flags: [], - unpaid_bytes: 0, - unpaid_rx_bytes: 0, - last_ticket: nil, - last_message: Time.utc_now(), - pid: self(), - sender: Network.Sender.new(state.socket) - }) - - log(state, "accepted connection") - {:noreply, must_have_ticket(state)} - end - - defp must_have_ticket(state = %{last_ticket: last}) do - Process.send_after(self(), {:must_have_ticket, last}, 20_000) - state - end - - def ssl_options(opts) do - Network.Server.default_ssl_options(opts) - |> Keyword.put(:packet, :raw) - end - - def monitor(this, pid) do - if self() == this do - Process.monitor(pid) - else - call(this, {:monitor, pid}) - end - end - - defp call(pid, msg, timeout \\ :infinity) do - GenServer.call(pid, msg, timeout) - end - - @impl true - def handle_call(fun, from, state) when is_function(fun) do - fun.(from, state) - end - - def handle_call(:socket, _from, state) do - {:reply, state.socket, state} - end - - def handle_call({:socket_do, fun}, _from, state) do - {:reply, fun.(state.socket), state} - end - - def handle_call({:monitor, pid}, _from, state) do - {:reply, Process.monitor(pid), state} - end - - @max_preopen_ports 5 - def handle_call({:portopen, pid, ref, flags, portname, device_address}, from, state) do - cond do - Enum.count(state.ports.refs, fn {_key, %Port{state: pstate}} -> pstate == :pre_open end) > - @max_preopen_ports -> - # Send message to ensure this is still an active port - Process.send_after(self(), {:check_activity, state.last_message}, 15_000) - {:reply, {:error, "too many hanging ports"}, state} - - PortCollection.get(state.ports, ref) != nil -> - {:reply, {:error, "already opening"}, state} - - true -> - mon = monitor(state.pid, pid) - - client = %PortClient{ - mon: mon, - pid: pid, - ref: ref, - write: String.contains?(flags, "r") - } - - port = %Port{ - state: :pre_open, - from: from, - clients: [client], - portname: portname, - shared: String.contains?(flags, "s"), - ref: ref - } - - case PortCollection.find_sharedport(state.ports, port) do - nil -> - state = - send_socket(state, {:port, ref}, random_ref(), [ - "portopen", - portname, - ref, - device_address - ]) - - ports = PortCollection.put(state.ports, port) - {:noreply, %{state | ports: ports}} - - existing_port -> - port = %Port{existing_port | clients: [client | existing_port.clients]} - ports = PortCollection.put(state.ports, port) - {:reply, {:ok, existing_port.ref}, %{state | ports: ports}} - end - end - end - - @impl true - def handle_cast(fun, state) when is_function(fun) do - fun.(state) - end - - def handle_cast({:portsend, ref, data, _pid}, state) do - if PortCollection.get(state.ports, ref) != nil do - {:noreply, send_socket(state, {:port, ref}, random_ref(), ["portsend", ref, data])} - else - {:noreply, state} - end - end - - def handle_cast({:portclose, ref}, state) do - {:noreply, portclose(state, ref)} - end - - def handle_cast(:stop, state) do - log(state, "connection closed because of handshake anomaly.") - {:stop, :normal, state} - end - - @impl true - def terminate(reason, %{sender: sender}) do - # log(state, "Received terminate ~p ~p", [reason, state]) - if reason == :normal do - Network.Sender.stop(sender) - end - - reason - end - - def terminate(reason, state) do - log(state, "Received terminate before init ~p ~p", [reason, state]) - reason - end - - def handle_msg(msg, state) do - case msg do - ["hello", vsn | flags] when is_binary(vsn) -> - if to_num(vsn) != 1_000 do - {error("version not supported"), state} - else - state1 = - Enum.reduce(flags, state, fn flag, state -> - case flag do - "zlib" -> %{state | compression: :zlib} - other -> %{state | extra_flags: [other | state.extra_flags]} - end - end) - - # If compression has been enabled then on the next frame - state = %{state | compression: state1.compression, extra_flags: state1.extra_flags} - {response("ok"), state} - end - - ["ticket" | rest = [_block, _fc, _tc, _tb, _la, _ds]] -> - handle_ticket(rest, state) - - ["bytes"] -> - # This is an exception as unpaid_bytes can be negative - {response(Rlpx.int2bin(state.unpaid_bytes)), state} - - ["portsend", ref, data] -> - case PortCollection.get(state.ports, ref) do - nil -> - error("port does not exist") - - %Port{state: :open, clients: clients} -> - for client <- clients do - if client.write do - GenServer.cast(client.pid, {:portsend, client.ref, data, state.pid}) - end - end - - response("ok") - end - - _other -> - :async - end - end - - def handle_async_msg(msg, state) do - case msg do - ["ping"] -> - response("pong") - - ["channel", block_number, fleet, type, name, params, signature] -> - obj = - channel( - server_id: Diode.miner() |> Wallet.address!(), - block_number: to_num(block_number), - fleet_contract: fleet, - type: type, - name: name, - params: params, - signature: signature - ) - - device = Object.Channel.device_address(obj) - - cond do - not Wallet.equal?(device, device_id(state)) -> - error("invalid channel signature") - - not Contract.Fleet.device_allowlisted?(fleet, device) -> - error("device not whitelisted for this fleet") - - not Object.Channel.valid_type?(obj) -> - error("invalid channel type") - - not Object.Channel.valid_params?(obj) -> - error("invalid channel parameters") - - true -> - key = Object.Channel.key(obj) - - case Kademlia.find_value(key) do - nil -> - Kademlia.store(obj) - Object.encode_list!(obj) - - binary -> - Object.encode_list!(Object.decode!(binary)) - end - |> 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 - binary -> Object.encode_list!(Object.decode!(binary)) - end - |> response() - - ["getnode", node] -> - case Kademlia.find_node(node) do - nil -> nil - item -> Object.encode_list!(KBuckets.object(item)) - end - |> response() - - ["getblockpeak"] -> - response(Chain.peak()) - - ["getblock", index] when is_binary(index) -> - response(BlockProcess.with_block(to_num(index), &Chain.Block.export(&1))) - - ["getblockheader", index] when is_binary(index) -> - response(block_header(to_num(index))) - - ["getblockheader2", index] when is_binary(index) -> - header = block_header(to_num(index)) - pubkey = Chain.Header.recover_miner(header) |> Wallet.pubkey!() - response(header, pubkey) - - ["getblockquick", last_block, window_size] - when is_binary(last_block) and - is_binary(window_size) -> - window_size = to_num(window_size) - last_block = to_num(last_block) - # this will throw if the block does not exist - block_header(last_block) - - get_blockquick_seq(last_block, window_size) - |> Enum.map(fn num -> - header = block_header(num) - miner = Chain.Header.recover_miner(header) |> Wallet.pubkey!() - {header, miner} - end) - |> response() - - ["getblockquick2", last_block, window_size] - when is_binary(last_block) and - is_binary(window_size) -> - get_blockquick_seq(to_num(last_block), to_num(window_size)) - |> response() - - ["getstateroots", index] -> - BlockProcess.with_block(to_num(index), fn block -> - Chain.Block.state_tree(block) - |> MerkleTree.root_hashes() - end) - |> response() - - ["getaccount", index, id] -> - BlockProcess.with_block(to_num(index), fn block -> - Chain.Block.state(block) - |> Chain.State.account(id) - |> case do - nil -> - error("account does not exist") - - account = %Chain.Account{} -> - proof = - Chain.Block.state_tree(block) - |> MerkleTree.get_proofs(id) - - response( - %{ - nonce: account.nonce, - balance: account.balance, - storage_root: Chain.Account.root_hash(account), - code: Chain.Account.codehash(account) - }, - proof - ) - end - end) - - ["getaccountroots", index, id] -> - BlockProcess.with_account(to_num(index), id, fn - nil -> error("account does not exist") - acc -> response(MerkleTree.root_hashes(Chain.Account.tree(acc))) - end) - - ["getaccountvalue", index, id, key] -> - BlockProcess.with_account(to_num(index), id, fn - nil -> error("account does not exist") - acc -> response(MerkleTree.get_proofs(Chain.Account.tree(acc), key)) - end) - - ["getaccountvalues", index, id | keys] -> - BlockProcess.with_account(to_num(index), id, fn - nil -> - error("account does not exist") - - acc -> - tree = MerkleTree.merkle(Chain.Account.tree(acc)) - - response( - Enum.map(keys, fn key -> - MerkleTree.get_proofs(tree, key) - end) - ) - end) - - ["portopen", device_id, port, flags] -> - portopen(state, device_id, to_num(port), flags) - - ["portopen", device_id, port] -> - portopen(state, device_id, to_num(port), "rw") - - # "portopen" response - ["response", ref, "ok"] -> - call(state.pid, fn _from, state -> - case PortCollection.get(state.ports, ref) do - port = %Port{state: :pre_open} -> - GenServer.reply(port.from, {:ok, ref}) - ports = PortCollection.put(state.ports, %Port{port | state: :open, from: nil}) - {:reply, :ok, %{state | ports: ports}} - - nil -> - log(state, "ignoring response for undefined ref ~p", [ref]) - {:reply, :ok, state} - end - end) - - nil - - # "portopen" error - ["error", ref, reason] -> - port = %Port{state: :pre_open} = PortCollection.get(state.ports, ref) - GenServer.reply(port.from, {:error, reason}) - - call(state.pid, fn _from, state -> - {:reply, :ok, portclose(state, port, false)} - end) - - nil - - ["portclose", ref] -> - case PortCollection.get(state.ports, ref) do - nil -> - error("port does not exit") - - port = %Port{state: :open} -> - call(state.pid, fn _from, state -> - {:reply, :ok, portclose(state, port, false)} - end) - - response("ok") - end - - ["sendtransaction", tx] -> - # Testing transaction - tx = Chain.Transaction.from_rlp(tx) - - err = - Chain.with_peak(fn peak -> - state = Chain.Block.state(peak) - - case Chain.Transaction.apply(tx, peak, state) do - {:ok, _state, %{msg: :ok}} -> nil - {:ok, _state, rcpt} -> "Transaction exception: #{rcpt.msg}" - {:error, :nonce_too_high} -> nil - {:error, reason} -> "Transaction failed: #{inspect(reason)}" - end - end) - - # Adding transacton, even when :nonce_too_high - if err == nil do - Chain.Pool.add_transaction(tx, true) - - if Diode.dev_mode?() do - Chain.Worker.work() - end - - response("ok") - else - error(400, err) - end - - nil -> - log(state, "Unhandled message: ~40s~n", [truncate(msg)]) - error(400, "that is not rlp") - - _ -> - log(state, "Unhandled message: ~40s~n", [truncate(msg)]) - error(401, "bad input") - end - end - - defp response(arg) do - response_array([arg]) - end - - defp response(arg, arg2) do - response_array([arg, arg2]) - end - - defp response_array(args) do - ["response" | args] - end - - defp error(code, message) do - ["error", code, message] - end - - defp error(message) do - ["error", message] - end - - defp handle_packet(raw_msg, state) do - state = account_incoming(state, raw_msg) - msg = decode(state, raw_msg) - - # should be [request_id, method_params, opts] - case msg do - [request_id, method_params, opts] -> - handle_request(state, to_num(request_id), method_params, opts) - - [request_id, method_params] -> - handle_request(state, to_num(request_id), method_params, []) - - _other -> - log(state, "connection closed because wrong message received.") - {:stop, :normal, state} - end - end - - defp handle_data("", state) do - {:noreply, state} - end - - defp handle_data(<<0::unsigned-size(16), rest::binary>>, state = %{inbuffer: nil}) do - handle_data(rest, state) - end - - defp handle_data(<>, state = %{inbuffer: nil}) do - handle_data(length, raw_msg, state) - end - - defp handle_data(<>, state = %{inbuffer: {length, buffer}}) do - handle_data(length, buffer <> more, %{state | inbuffer: nil}) - end - - defp handle_data(length, raw_msg, state) do - if byte_size(raw_msg) >= length do - {:noreply, state} = handle_packet(binary_part(raw_msg, 0, length), state) - rest = binary_part(raw_msg, length, byte_size(raw_msg) - length) - handle_data(rest, %{state | inbuffer: nil}) - else - {:noreply, %{state | inbuffer: {length, raw_msg}}} - end - end - - @impl true - def handle_info({:check_activity, then_last_message}, state = %{last_message: now_last_message}) do - if then_last_message == now_last_message do - {:stop, :no_activity_timeout, state} - else - {:noreply, state} - end - end - - def handle_info({:ssl, _socket, data}, state) do - handle_data(data, %{state | last_message: Time.utc_now()}) - end - - def handle_info({:topic, _topic, _message}, state) do - throw(:notimpl) - # state = send_socket(state, random_ref(), [topic, message]) - {:noreply, state} - end - - def handle_info({:stop_unpaid, b0}, state = %{unpaid_bytes: b}) do - log(state, "connection closed because unpaid #{b0}(#{b}) bytes.") - {:stop, :normal, state} - end - - def handle_info({:must_have_ticket, last}, state = %{last_ticket: timestamp}) do - if timestamp == nil or timestamp == last do - log(state, "connection closed because no valid ticket sent within time limit.") - {:stop, :normal, state} - else - {:noreply, state} - end - end - - def handle_info({:ssl_closed, _}, state) do - log(state, "connection closed by remote.") - {:stop, :normal, state} - end - - def handle_info({:DOWN, mon, _type, _object, _info}, state) do - {:noreply, portclose(state, mon)} - end - - def handle_info(msg, state) do - log(state, "Unhandled info: ~p", [msg]) - {:noreply, state} - end - - defp handle_request(state, request_id, method_params, _opts) do - case handle_msg(method_params, state) do - :async -> - pid = self() - - spawn_link(fn -> - result = handle_async_msg(method_params, state) - - GenServer.cast(pid, fn state2 -> - {:noreply, send_socket(state2, request_id, request_id, result)} - end) - end) - - {:noreply, state} - - {result, state} -> - {:noreply, send_socket(state, request_id, request_id, result)} - - result -> - {:noreply, send_socket(state, request_id, request_id, result)} - end - end - - defp handle_ticket( - [block, fleet, total_connections, total_bytes, local_address, device_signature], - state - ) do - dl = - ticket( - server_id: Wallet.address!(Diode.miner()), - fleet_contract: fleet, - total_connections: to_num(total_connections), - total_bytes: to_num(total_bytes), - local_address: local_address, - block_number: to_num(block), - device_signature: device_signature - ) - - cond do - Ticket.block_number(dl) > Chain.peak() -> - log( - state, - "Ticket with future block number #{Ticket.block_number(dl)} vs. #{Chain.peak()}!" - ) - - error("block number too high") - - not Ticket.device_address?(dl, device_id(state)) -> - log(state, "Received invalid ticket signature!") - error("signature mismatch") - - # TODO: Needs to be re-enabled after dev-contract is all-yes - # not Contract.Fleet.device_allowlisted?(fleet, device) -> - # log(state, "Received invalid ticket fleet!") - # error("device not whitelisted") - - true -> - dl = Ticket.server_sign(dl, Wallet.privkey!(Diode.miner())) - ret = TicketStore.add(dl, device_id(state)) - - # address = Ticket.device_address(dl) - # short = String.slice(Base16.encode(address), 0..7) - # total = Ticket.total_bytes(dl) - # unpaid = state.unpaid_bytes - # IO.puts("[#{short}] TICKET total: #{total} unpaid: #{unpaid} ret => #{inspect(ret)}") - - case ret do - {:ok, bytes} -> - key = Object.key(dl) - - # Storing the updated ticket of this device, debounce is 15 sec - Debouncer.immediate( - key, - fn -> - Model.KademliaSql.put_object(Kademlia.hash(key), Object.encode!(dl)) - Kademlia.store(dl) - end, - 15_000 - ) - - # Storing the updated ticket of this device, debounce is 10 sec - Debouncer.immediate( - :publish_me, - fn -> - me = Diode.self() - Kademlia.store(me) - end, - 10_000 - ) - - {response("thanks!", bytes), - %{state | unpaid_bytes: state.unpaid_bytes - bytes, last_ticket: Time.utc_now()}} - - {:too_old, min} -> - response("too_old", min) - - {:too_low, last} -> - response_array([ - "too_low", - Ticket.block_hash(last), - Ticket.total_connections(last), - Ticket.total_bytes(last), - Ticket.local_address(last), - Ticket.device_signature(last) - ]) - end - end - end - - defp truncate(msg) when is_binary(msg) and byte_size(msg) > 40 do - binary_part(msg, 0, 37) <> "..." - end - - defp truncate(msg) when is_binary(msg) do - msg - end - - defp truncate(other) do - :io_lib.format("~0p", [other]) - |> :erlang.iolist_to_binary() - |> truncate() - end - - defp portopen(state, <>, portname, flags) do - case Kademlia.find_value(channel_id) do - nil -> - error("not found") - - bin -> - channel = Object.decode!(bin) - - Object.Channel.server_id(channel) - |> Wallet.from_address() - |> Wallet.equal?(Diode.miner()) - |> if do - pid = Channels.ensure(channel) - do_portopen(state, device_address(state), state.pid, portname, flags, pid) - else - error("wrong host") - end - end - end - - defp portopen(state, device_id, portname, flags) do - address = device_address(state) - - cond do - device_id == address -> - error("can't connect to yourself") - - validate_flags(flags) == false -> - error("invalid flags") - - true -> - with <> <- device_id, - w <- Wallet.from_address(bin), - [pid | _] <- PubSub.subscribers({:edge, Wallet.address!(w)}) do - do_portopen(state, address, state.pid, portname, flags, pid) - else - [] -> error("not found") - other -> error("invalid address #{inspect(other)}") - end - end - end - - defp validate_flags("rw"), do: true - defp validate_flags("r"), do: true - defp validate_flags("w"), do: true - defp validate_flags("rws"), do: true - defp validate_flags("rs"), do: true - defp validate_flags("ws"), do: true - defp validate_flags(_), do: false - - defp random_ref() do - Random.uint31h() - |> to_bin() - - # :io.format("REF ~p~n", [ref]) - end - - defp do_portopen(state, device_address, this, portname, flags, pid) do - mon = monitor(this, pid) - ref = random_ref() - - # Receives an open request from another local connected edge worker. - # Now needs to forward the request to the device and remember to - # keep in 'pre-open' state until the device acks. - # Todo: Check for network access based on contract - resp = - try do - call(pid, {:portopen, this, ref, flags, portname, device_address}, 35_000) - catch - kind, what -> - log( - state, - "Portopen failed for #{Base16.encode(device_address)} #{inspect({kind, what})}" - ) - - :error - end - - case resp do - {:ok, cref} -> - client = %PortClient{ - pid: pid, - mon: mon, - ref: cref, - write: String.contains?(flags, "w") - } - - call(this, fn _from, state -> - ports = - PortCollection.put(state.ports, %Port{state: :open, clients: [client], ref: ref}) - - {:reply, :ok, %{state | ports: ports}} - end) - - response("ok", ref) - - {:error, reason} -> - Process.demonitor(mon, [:flush]) - error(reason) - - :error -> - Process.demonitor(mon, [:flush]) - error(ref) - end - end - - defp portclose(state, ref, action \\ true) - - # Closing whole port no matter how many clients - defp portclose(state, port = %Port{}, action) do - for client <- port.clients do - GenServer.cast(client.pid, {:portclose, port.ref}) - Process.demonitor(client.mon, [:flush]) - end - - state = - if action do - # {:current_stacktrace, what} = :erlang.process_info(self(), :current_stacktrace) - # :io.format("portclose from: ~p~n", [what]) - send_socket(state, {:port, port.ref}, random_ref(), ["portclose", port.ref]) - else - state - end - - %{state | ports: PortCollection.delete(state.ports, port.ref)} - end - - # Removing client but keeping port open if still >0 clients - defp portclose(state, clientmon, action) when is_reference(clientmon) do - do_portclose(state, PortCollection.get_clientmon(state.ports, clientmon), action) - end - - defp portclose(state, clientref, action) do - do_portclose(state, PortCollection.get_clientref(state.ports, clientref), action) - end - - defp do_portclose(state, nil, _action) do - state - end - - defp do_portclose(state, {client, %Port{clients: [client], ref: ref}}, action) do - Process.demonitor(client.mon, [:flush]) - - state = - if action do - # {:current_stacktrace, what} = :erlang.process_info(self(), :current_stacktrace) - # :io.format("portclose from: ~p~n", [what]) - send_socket(state, {:port, ref}, random_ref(), ["portclose", ref]) - else - state - end - - %{state | ports: PortCollection.delete(state.ports, ref)} - end - - defp do_portclose(state, {client, port}, _action) do - Process.demonitor(client.mon, [:flush]) - - %{ - state - | ports: - PortCollection.put(state.ports, %Port{port | clients: List.delete(port.clients, client)}) - } - end - - defp decode(state, msg) do - case state.compression do - nil -> msg - :zlib -> :zlib.unzip(msg) - end - |> Rlp.decode!() - end - - defp encode(msg) do - Rlp.encode!(msg) - end - - defp is_portsend({:port, _}), do: true - defp is_portsend(_), do: false - - defp send_threshold() do - Diode.ticket_grace() - Diode.ticket_grace() / 4 - end - - defp send_socket( - state = %{unpaid_bytes: unpaid}, - partition, - request_id, - data - ) do - cond do - # early exit - unpaid > Diode.ticket_grace() -> - send(self(), {:stop_unpaid, unpaid}) - msg = encode([random_ref(), ["goodbye", "ticket expected", "you might get blocked"]]) - :ok = do_send_socket(state, partition, msg) - account_outgoing(state, msg) - - # stopping port data, and ensure there is a ticket within 20s - unpaid > send_threshold() and is_portsend(partition) -> - %{state | blocked: :queue.in({partition, request_id, data}, state.blocked)} - |> account_outgoing() - |> must_have_ticket() - - true -> - state = - if data == nil do - account_outgoing(state) - else - msg = encode([request_id, data]) - :ok = do_send_socket(state, partition, msg) - account_outgoing(state, msg) - end - - # continue port data sending? - if state.unpaid_bytes < send_threshold() and not :queue.is_empty(state.blocked) do - {{:value, {partition, request_id, data}}, blocked} = :queue.out(state.blocked) - send_socket(%{state | blocked: blocked}, partition, request_id, data) - else - state - end - end - end - - defp do_send_socket(state, partition, msg) do - msg = - case state.compression do - nil -> msg - :zlib -> :zlib.zip(msg) - end - - length = byte_size(msg) - Network.Sender.push_async(state.sender, partition, <>) - end - - @spec device_id(state()) :: Wallet.t() - def device_id(%{node_id: id}), do: id - def device_address(%{node_id: id}), do: Wallet.address!(id) - - defp account_incoming(state = %{unpaid_rx_bytes: unpaid_rx}, msg) do - %{state | unpaid_rx_bytes: unpaid_rx + byte_size(msg)} - end - - defp account_outgoing(state = %{unpaid_bytes: unpaid, unpaid_rx_bytes: unpaid_rx}, msg \\ "") do - %{state | unpaid_bytes: unpaid + unpaid_rx + byte_size(msg), unpaid_rx_bytes: 0} - end - - def on_nodeid(_edge) do - :ok - end - - def get_blockquick_window(last_block, window_size) do - hash = Chain.blockhash(last_block) - - window = - Chain.Block.blockquick_window(hash) - |> Enum.reverse() - |> Enum.take(window_size) - - len = length(window) - - if len < window_size do - next_block = last_block - len - window ++ get_blockquick_window(next_block, window_size - len) - else - window - end - end - - def find_sequence(last_block, counts, score, threshold) do - window = - Chain.Block.blockquick_window(Chain.blockhash(last_block)) - |> Enum.reverse() - - Enum.reduce_while(window, {counts, score, last_block}, fn miner, - {counts, score, last_block} -> - {score_value, counts} = Map.pop(counts, miner, 0) - score = score + score_value - - if score > threshold do - {:halt, {:ok, last_block}} - else - {:cont, {counts, score, last_block - 1}} - end - end) - |> case do - {:ok, last_block} -> - {:ok, last_block} - - {counts, score, last_block} -> - find_sequence(last_block, counts, score, threshold) - end - end - - def get_blockquick_seq(last_block, window_size) do - # Step 1: Identifying current view the device has - # based on it's current last valid block number - window = get_blockquick_window(last_block, window_size) - counts = Enum.reduce(window, %{}, fn miner, acc -> Map.update(acc, miner, 1, &(&1 + 1)) end) - threshold = div(window_size, 2) - - # Step 2: Findind a provable sequence - # Iterating from peak backwards until the block score is over 50% of the window_size - peak = Chain.peak() - {:ok, provable_block} = find_sequence(peak, counts, 0, threshold) - - # Step 3: Filling gap between 'last_block' and provable sequence, but not - # by more than 'window_size' block heads before the provable sequence - begin = provable_block - size = max(min(window_size, begin - last_block) - 1, 1) - - gap_fill = Enum.to_list((begin - size)..(begin - 1)) - gap_fill ++ Enum.to_list(begin..peak) - - # # Step 4: Checking whether the the provable sequence can be shortened - # # TODO - # {:ok, heads} = - # Enum.reduce_while(heads, {counts, 0, []}, fn {head, miner}, - # {counts, score, heads} -> - # {value, counts} = Map.pop(counts, miner, 0) - # score = score + value - # heads = [{head, miner} | heads] - - # if score > threshold do - # {:halt, {:ok, heads}} - # else - # {:cont, {counts, score, heads}} - # end - # end) - end - - defp block_header(n) do - BlockProcess.with_block(n, fn - nil -> nil - block -> Chain.Header.strip_state(block.header) - end) || check_block(n) - end - - defp check_block(n) do - if is_integer(n) and n < Chain.peak() do - # we had some cases of missing blocks - # this means the async blockwriter did somehow? skip - # putting this block on disk. If this happens (which is a bug) - # the only course for recovery is to restart the node, which - # triggers an integrity check - Logger.error("missing block #{n}") - System.halt(1) - else - Logger.info("block #{inspect(n)} not found") - throw(:notfound) - end - end - - defp to_num(bin) do - Rlpx.bin2num(bin) - end - - defp to_bin(num) do - Rlpx.num2bin(num) - end -end diff --git a/lib/network/peer_handler.ex b/lib/network/peer_handler.ex index 2be317e..05cabd8 100644 --- a/lib/network/peer_handler.ex +++ b/lib/network/peer_handler.ex @@ -138,7 +138,7 @@ defmodule Network.PeerHandler do hello = Diode.self(hostname) - case ssl_send(state, [@hello, Object.encode!(hello), ChainWrap.genesis_hash()]) do + case ssl_send(state, [@hello, Object.encode!(hello), Moonbeam.genesis_hash()]) do {:noreply, state} -> receive do {:ssl, _socket, msg} -> @@ -201,14 +201,14 @@ defmodule Network.PeerHandler do end defp handle_msg([@hello, server, genesis_hash], state) do - genesis = ChainWrap.genesis_hash() + genesis = Moonbeam.genesis_hash() if genesis != genesis_hash do log(state, "wrong genesis: ~p ~p", [Base16.encode(genesis), Base16.encode(genesis_hash)]) {:stop, :normal, state} else state = - if ChainWrap.light_node?() do + if Moonbeam.light_node?() do state else publish_peak(state) From c0407f4f190a151a295e7a058ec6de984f29a8da Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Sun, 1 Oct 2023 00:23:47 +0200 Subject: [PATCH 06/14] wip --- lib/diode.ex | 9 ++- lib/moonbeam/call_permit.ex | 21 +++++- lib/moonbeam/edge_m1.ex | 129 +++++++++++++++++++++++++++++---- lib/moonbeam/moonbeam.ex | 49 ++++++++++--- lib/moonbeam/nonce_provider.ex | 76 +++++++++++++++++++ lib/network/edge_v2.ex | 11 ++- lib/network/rpc.ex | 6 +- 7 files changed, 265 insertions(+), 36 deletions(-) create mode 100644 lib/moonbeam/nonce_provider.ex diff --git a/lib/diode.ex b/lib/diode.ex index e2b0fb5..9affebd 100644 --- a/lib/diode.ex +++ b/lib/diode.ex @@ -77,9 +77,12 @@ defmodule Diode do worker(Stats, []), supervisor(Model.Sql), supervisor(Channels), + 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 = @@ -393,9 +396,9 @@ defmodule Diode do def self(hostname) do Object.Server.new(hostname, hd(edge2_ports()), peer_port(), version(), [ - ["tickets", TicketStore.value(Moonbeam.epoch())], + ["tickets", TicketStore.value(Chain.epoch())], ["uptime", Diode.uptime()], - ["block", Moonbeam.peak()] + ["block", Chain.peak()] ]) |> Object.Server.sign(Wallet.privkey!(Diode.miner())) end diff --git a/lib/moonbeam/call_permit.ex b/lib/moonbeam/call_permit.ex index 7030cdd..e9a8384 100644 --- a/lib/moonbeam/call_permit.ex +++ b/lib/moonbeam/call_permit.ex @@ -3,6 +3,11 @@ defmodule CallPermit do "0x2d44830364594de15bf34f87ca86da8d1967e5bc7d64b301864028acb9120412" ) + def address() do + Base16.decode("0x000000000000000000000000000000000000080A") + end + + # 0xe7f13b866a7fc159cb6ee32bcb4103cf0477652e def wallet() do {:wallet, <<205, 186, 242, 6, 129, 177, 86, 35, 141, 148, 105, 188, 131, 116, 84, 18, 226, 131, 244, @@ -56,7 +61,7 @@ defmodule CallPermit do def call_permit(from_wallet, to, value, data, gaslimit, deadline) do from = Wallet.address!(from_wallet) - nonce = Moonbeam.call!(nonces(from)) |> Base16.decode_int() + nonce = rpc_call!(nonces(from)) |> Base16.decode_int() signature = EIP712.encode(@domain_separator, "CallPermit", [ @@ -98,7 +103,7 @@ defmodule CallPermit do ]) call_permit(other_wallet, bns, 0, call, gas_limit, deadline) - |> Moonbeam.call!() + |> rpc_call!() end def test2() do @@ -118,7 +123,17 @@ defmodule CallPermit do call = ABI.encode_call("Version") call_permit(other_wallet, bns, 0, call, gas_limit, deadline) - |> Moonbeam.call!() + |> rpc_call!() + 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()) diff --git a/lib/moonbeam/edge_m1.ex b/lib/moonbeam/edge_m1.ex index d0f4ef5..ba1079f 100644 --- a/lib/moonbeam/edge_m1.ex +++ b/lib/moonbeam/edge_m1.ex @@ -2,18 +2,41 @@ # Copyright 2023 Diode # Licensed under the Diode License, Version 1.1 defmodule Network.EdgeM1 do - import Network.EdgeV2, only: [response: 1, error: 1] + import Network.EdgeV2, only: [response: 1, response: 2, error: 1] def handle_async_msg(msg, state) do case msg do ["getblockpeak"] -> - response(Moonbeam.block_number()) + Moonbeam.block_number() + |> Base16.decode() + |> response() ["getblock", index] when is_binary(index) -> error("not implemented") ["getblockheader", index] when is_binary(index) -> - response(Moonbeam.get_block_by_number(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") @@ -35,32 +58,106 @@ defmodule Network.EdgeM1 do # 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: Moonbeam.get_transaction_count(hex_address(address), hex_blockref(block)), - balance: Moonbeam.get_balance(hex_address(address), hex_blockref(block)), - storage_root: nil, - code: nil + 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] -> + ["getaccountvalue", block, address, key] -> # requires https://eips.ethereum.org/EIPS/eip-1186 # response(Moonbeam.proof(address, [key], blockref(block))) - error("not implemented") + Moonbeam.get_storage_at(hex_address(address), hex_key(key), hex_blockref(block)) + |> Base16.decode() + |> response() - ["getaccountvalues", _block, _address | _keys] -> + ["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") - ["sendtransaction", tx] -> + ["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) - response(Moonbeam.call!(call)) + + 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 + CallPermit.rpc_call(call, Wallet.address!(CallPermit.wallet())) + |> IO.inspect(label: "rpc_call") + 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) @@ -72,11 +169,13 @@ defmodule Network.EdgeM1 do end defp hex_blockref(ref) when ref in ["latest", "earliest"], do: ref - defp hex_blockref("0x" <> _rest = hex), do: hex - defp hex_blockref(n) when is_integer(n), do: Base16.encode(n, false) - defp hex_blockref(_other), do: throw(:notfound) + 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 index 5f53dfd..8cf8fbb 100644 --- a/lib/moonbeam/moonbeam.ex +++ b/lib/moonbeam/moonbeam.ex @@ -1,6 +1,5 @@ defmodule Moonbeam do # https://github.com/moonbeam-foundation/moonbeam/blob/master/precompiles/call-permit/CallPermit.sol - @address Base16.decode("0x000000000000000000000000000000000000080A") @endpoint "https://moonbeam-alpha.api.onfinality.io/public" # @endpoint "https://rpc.api.moonbase.moonbeam.network" @@ -38,6 +37,10 @@ defmodule Moonbeam 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 @@ -46,7 +49,19 @@ defmodule Moonbeam do rpc!("eth_getBalance", [address, block]) end - def rpc!(method, params \\ []) do + 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, @@ -59,21 +74,31 @@ defmodule Moonbeam do case Jason.decode!(body) do %{"result" => result} -> - result + {:ok, result} %{"error" => error} -> - raise "RPC error: #{inspect(error)}" + {:error, error} end end - def call!(data) do - rpc!("eth_call", [ - %{ - to: Base16.encode(@address), - data: Base16.encode(data) - }, - "latest" - ]) + 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 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 3e792d4..714f632 100644 --- a/lib/network/edge_v2.ex +++ b/lib/network/edge_v2.ex @@ -315,7 +315,7 @@ defmodule Network.EdgeV2 do def handle_async_msg(msg, state) do case msg do ["m1:" <> cmd | rest] -> - Network.EdgeV2.handle_async_msg([cmd | rest], state) + Network.EdgeM1.handle_async_msg([cmd | rest], state) ["ping"] -> response("pong") @@ -675,9 +675,16 @@ defmodule Network.EdgeV2 do pid = self() spawn_link(fn -> - OnCrash.call(fn err -> log(state, "Error: ~p", [err]) end) + OnCrash.call(fn err -> + if err != :normal do + IO.inspect("#{inspect(method_params)} failed: #{inspect(err)}") + end + end) + result = handle_async_msg(method_params, state) + IO.inspect("#{inspect(method_params)} result: #{inspect(result)}") + GenServer.cast(pid, fn state2 -> {:noreply, send_socket(state2, request_id, request_id, result)} end) 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 -> From 1b82b3d1cafcb5dcf820d55e3c155637ed459bc0 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Wed, 4 Oct 2023 11:42:48 +0200 Subject: [PATCH 07/14] pre-merge cleanup --- lib/moonbeam/call_permit.ex | 7 +------ lib/moonbeam/edge_m1.ex | 3 +-- lib/network/edge_v2.ex | 8 -------- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/lib/moonbeam/call_permit.ex b/lib/moonbeam/call_permit.ex index e9a8384..27b6c9c 100644 --- a/lib/moonbeam/call_permit.ex +++ b/lib/moonbeam/call_permit.ex @@ -9,12 +9,7 @@ defmodule CallPermit do # 0xe7f13b866a7fc159cb6ee32bcb4103cf0477652e def wallet() do - {: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>>} + Diode.miner() end # /// @dev Dispatch a call on the behalf of an other user with a EIP712 permit. diff --git a/lib/moonbeam/edge_m1.ex b/lib/moonbeam/edge_m1.ex index ba1079f..b310fd7 100644 --- a/lib/moonbeam/edge_m1.ex +++ b/lib/moonbeam/edge_m1.ex @@ -123,8 +123,7 @@ defmodule Network.EdgeM1 do # 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 - CallPermit.rpc_call(call, Wallet.address!(CallPermit.wallet())) - |> IO.inspect(label: "rpc_call") + {:ok, _} = CallPermit.rpc_call(call, Wallet.address!(CallPermit.wallet())) end # Moonbeam.estimate_gas(Base16.encode(CallPermit.address()), Base16.encode(call)) diff --git a/lib/network/edge_v2.ex b/lib/network/edge_v2.ex index 714f632..359d6fb 100644 --- a/lib/network/edge_v2.ex +++ b/lib/network/edge_v2.ex @@ -675,16 +675,8 @@ defmodule Network.EdgeV2 do pid = self() spawn_link(fn -> - OnCrash.call(fn err -> - if err != :normal do - IO.inspect("#{inspect(method_params)} failed: #{inspect(err)}") - end - end) - result = handle_async_msg(method_params, state) - IO.inspect("#{inspect(method_params)} result: #{inspect(result)}") - GenServer.cast(pid, fn state2 -> {:noreply, send_socket(state2, request_id, request_id, result)} end) From 4b86cbcc24fb0d7e2297bfaebd9319bbaa186af3 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Wed, 4 Oct 2023 12:38:25 +0200 Subject: [PATCH 08/14] more cleanup --- .travis.yml | 21 ----------- lib/moonbeam/call_permit.ex | 71 ------------------------------------- lib/moonbeam/moonbeam.ex | 8 ++--- lib/network/edge_v2.ex | 1 + lib/network/peer_handler.ex | 7 +--- lib/network/server.ex | 2 +- mix.lock | 4 +-- 7 files changed, 7 insertions(+), 107 deletions(-) delete mode 100644 .travis.yml 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/lib/moonbeam/call_permit.ex b/lib/moonbeam/call_permit.ex index 27b6c9c..8c94404 100644 --- a/lib/moonbeam/call_permit.ex +++ b/lib/moonbeam/call_permit.ex @@ -1,8 +1,4 @@ defmodule CallPermit do - @domain_separator Base16.decode( - "0x2d44830364594de15bf34f87ca86da8d1967e5bc7d64b301864028acb9120412" - ) - def address() do Base16.decode("0x000000000000000000000000000000000000080A") end @@ -54,73 +50,6 @@ defmodule CallPermit do ABI.encode_call("DOMAIN_SEPARATOR", [], []) end - def call_permit(from_wallet, to, value, data, gaslimit, deadline) do - from = Wallet.address!(from_wallet) - nonce = rpc_call!(nonces(from)) |> Base16.decode_int() - - signature = - EIP712.encode(@domain_separator, "CallPermit", [ - {"from", "address", from}, - {"to", "address", to}, - {"value", "uint256", value}, - {"data", "bytes", data}, - {"gaslimit", "uint64", gaslimit}, - {"nonce", "uint256", nonce}, - {"deadline", "uint256", deadline} - ]) - - [v, r, s] = - Wallet.sign!(from_wallet, signature, :none) - |> Secp256k1.bitcoin_to_rlp() - - dispatch(from, to, value, data, gaslimit, deadline, v, r, s) - end - - # Making a BNS register call - def test() do - bns = Base16.decode("0x75140F88B0F4B2FBC6DADC16CC51203ADB07FE36") - - other_wallet = - {:wallet, - <<168, 72, 60, 32, 26, 244, 241, 69, 33, 155, 57, 121, 27, 21, 88, 28, 204, 144, 133, 121, - 249, 232, 26, 246, 75, 105, 137, 103, 59, 96, 250, 145>>, - <<2, 234, 139, 191, 188, 193, 51, 129, 233, 219, 195, 29, 184, 143, 180, 241, 241, 125, - 151, 17, 194, 147, 145, 66, 214, 210, 149, 111, 30, 152, 115, 246, 28>>, - <<111, 57, 182, 104, 128, 82, 13, 111, 164, 185, 235, 166, 127, 6, 48, 118, 214, 19, 2, 6>>} - - gas_limit = 500_000 - deadline = 1_800_000_000 - - call = - ABI.encode_call("Register", ["string", "address"], [ - "anotheraccount", - Wallet.address!(other_wallet) - ]) - - call_permit(other_wallet, bns, 0, call, gas_limit, deadline) - |> rpc_call!() - end - - def test2() do - bns = Base16.decode("0x75140F88B0F4B2FBC6DADC16CC51203ADB07FE36") - - other_wallet = - {:wallet, - <<168, 72, 60, 32, 26, 244, 241, 69, 33, 155, 57, 121, 27, 21, 88, 28, 204, 144, 133, 121, - 249, 232, 26, 246, 75, 105, 137, 103, 59, 96, 250, 145>>, - <<2, 234, 139, 191, 188, 193, 51, 129, 233, 219, 195, 29, 184, 143, 180, 241, 241, 125, - 151, 17, 194, 147, 145, 66, 214, 210, 149, 111, 30, 152, 115, 246, 28>>, - <<111, 57, 182, 104, 128, 82, 13, 111, 164, 185, 235, 166, 127, 6, 48, 118, 214, 19, 2, 6>>} - - gas_limit = 500_000 - deadline = 1_800_000_000 - - call = ABI.encode_call("Version") - - call_permit(other_wallet, bns, 0, call, gas_limit, deadline) - |> rpc_call!() - end - def rpc_call!(call, from \\ nil, blockref \\ "latest") do {:ok, ret} = rpc_call(call, from, blockref) ret diff --git a/lib/moonbeam/moonbeam.ex b/lib/moonbeam/moonbeam.ex index 8cf8fbb..578066d 100644 --- a/lib/moonbeam/moonbeam.ex +++ b/lib/moonbeam/moonbeam.ex @@ -16,10 +16,6 @@ defmodule Moonbeam do 0x33638DDE636F9264B6472B9D976D58E757FE88BADAC53F204F3F530ECC5AACFA end - def light_node?() do - true - end - def get_proof(address, keys, block \\ "latest") do # requires https://eips.ethereum.org/EIPS/eip-1186 rpc!("eth_getProof", [address, keys, block]) @@ -70,9 +66,9 @@ defmodule Moonbeam do } {:ok, %{body: body}} = - HTTPoison.post(@endpoint, Jason.encode!(request), [{"Content-Type", "application/json"}]) + HTTPoison.post(@endpoint, Poison.encode!(request), [{"Content-Type", "application/json"}]) - case Jason.decode!(body) do + case Poison.decode!(body) do %{"result" => result} -> {:ok, result} diff --git a/lib/network/edge_v2.ex b/lib/network/edge_v2.ex index 359d6fb..eaf6264 100644 --- a/lib/network/edge_v2.ex +++ b/lib/network/edge_v2.ex @@ -1130,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 05cabd8..7ac4aae 100644 --- a/lib/network/peer_handler.ex +++ b/lib/network/peer_handler.ex @@ -207,12 +207,7 @@ defmodule Network.PeerHandler do log(state, "wrong genesis: ~p ~p", [Base16.encode(genesis), Base16.encode(genesis_hash)]) {:stop, :normal, state} else - state = - if Moonbeam.light_node?() do - state - else - publish_peak(state) - end + state = publish_peak(state) if Map.has_key?(state, :peer_port) do {:noreply, state} diff --git a/lib/network/server.ex b/lib/network/server.ex index 16816a1..dccf25c 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/mix.lock b/mix.lock index 8533aa5..ed9d910 100644 --- a/mix.lock +++ b/mix.lock @@ -9,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"}, @@ -42,7 +42,7 @@ "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", "445195dbfdeceebc30c67c3889a87160a6d066b3", []}, "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"}, From 696a3df493492ee2ca196d22e6efae85c0722fbf Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Wed, 4 Oct 2023 14:15:34 +0200 Subject: [PATCH 09/14] wip --- lib/chain/block.ex | 1 - lib/kademlia.ex | 7 ------- lib/kbuckets.ex | 19 +++---------------- lib/network/server.ex | 2 +- mix.exs | 22 +++++++++++++--------- mix.lock | 1 + 6 files changed, 18 insertions(+), 34 deletions(-) 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/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/network/server.ex b/lib/network/server.ex index dccf25c..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/mix.exs b/mix.exs index ff89ee9..13c6781 100644 --- a/mix.exs +++ b/mix.exs @@ -10,20 +10,23 @@ 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", + dialyzer: [ + plt_add_apps: [:sqlitex] + ], + 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 @@ -95,6 +98,7 @@ defmodule Diode.Mixfile do {: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 ed9d910..a3ced67 100644 --- a/mix.lock +++ b/mix.lock @@ -34,6 +34,7 @@ "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"}, From 41dce054c163f9c14c751db6b9e65318acca70ac Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Wed, 4 Oct 2023 15:03:01 +0200 Subject: [PATCH 10/14] cleanup --- lib/chain.ex | 60 ++++++---------------------------------------------- mix.exs | 6 ++---- mix.lock | 2 +- 3 files changed, 10 insertions(+), 58 deletions(-) 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/mix.exs b/mix.exs index 13c6781..65bbc9b 100644 --- a/mix.exs +++ b/mix.exs @@ -15,9 +15,6 @@ defmodule Diode.Mixfile do compilers: [:elixir_make] ++ Mix.compilers(), deps: deps(), description: "Diode Network Full Blockchain Node implementation", - dialyzer: [ - plt_add_apps: [:sqlitex] - ], docs: docs(), elixir: "~> 1.11", elixirc_options: [warnings_as_errors: Mix.target() == :host], @@ -54,7 +51,8 @@ defmodule Diode.Mixfile do lint: [ "compile", "format --check-formatted", - "credo --only warning" + "credo --only warning", + "dialyzer" ] ] end diff --git a/mix.lock b/mix.lock index a3ced67..12b8967 100644 --- a/mix.lock +++ b/mix.lock @@ -43,7 +43,7 @@ "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", "445195dbfdeceebc30c67c3889a87160a6d066b3", []}, + "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"}, From 0cee4cc32ffcf1e68303d51e8ba2d04a68031de2 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Wed, 4 Oct 2023 16:11:13 +0200 Subject: [PATCH 11/14] cleanup --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ .tool-versions | 2 ++ lib/oncrash.ex | 19 ------------------- 3 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .tool-versions delete mode 100644 lib/oncrash.ex diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..637245c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: "CI" +on: ["push", "pull_request"] + +jobs: + test_and_build: + name: "Build mix release" + runs-on: "ubuntu-latest" + steps: + - 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/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 From b9e42c2ccaac6fa8cacd6dbf9c8473382c741705 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Wed, 4 Oct 2023 17:59:21 +0200 Subject: [PATCH 12/14] cleanup --- docker | 28 ---------------- lib/diode.ex | 6 ++++ lib/moonbeam/call_permit.ex | 9 ++++- lib/network/peer_handler.ex | 4 +-- mix.lock | 2 +- Dockerfile => scripts/Dockerfile | 4 ++- clean.sh => scripts/clean.sh | 0 scripts/docker | 50 ++++++++++++++++++++++++++++ scripts/entrypoint | 17 ++++++++++ evmbench.exs => scripts/evmbench.exs | 0 evmbench.sh => scripts/evmbench.sh | 0 run_beta => scripts/run_beta | 0 run_offline => scripts/run_offline | 0 run_perf => scripts/run_perf | 0 run_test => scripts/run_test | 0 15 files changed, 87 insertions(+), 33 deletions(-) delete mode 100755 docker rename Dockerfile => scripts/Dockerfile (82%) rename clean.sh => scripts/clean.sh (100%) create mode 100755 scripts/docker create mode 100755 scripts/entrypoint rename evmbench.exs => scripts/evmbench.exs (100%) rename evmbench.sh => scripts/evmbench.sh (100%) rename run_beta => scripts/run_beta (100%) rename run_offline => scripts/run_offline (100%) rename run_perf => scripts/run_perf (100%) rename run_test => scripts/run_test (100%) 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/diode.ex b/lib/diode.ex index 9affebd..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 diff --git a/lib/moonbeam/call_permit.ex b/lib/moonbeam/call_permit.ex index 8c94404..fade637 100644 --- a/lib/moonbeam/call_permit.ex +++ b/lib/moonbeam/call_permit.ex @@ -5,7 +5,14 @@ defmodule CallPermit do # 0xe7f13b866a7fc159cb6ee32bcb4103cf0477652e def wallet() do - Diode.miner() + # 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. diff --git a/lib/network/peer_handler.ex b/lib/network/peer_handler.ex index 7ac4aae..7150166 100644 --- a/lib/network/peer_handler.ex +++ b/lib/network/peer_handler.ex @@ -138,7 +138,7 @@ defmodule Network.PeerHandler do hello = Diode.self(hostname) - case ssl_send(state, [@hello, Object.encode!(hello), Moonbeam.genesis_hash()]) do + case ssl_send(state, [@hello, Object.encode!(hello), Chain.genesis_hash()]) do {:noreply, state} -> receive do {:ssl, _socket, msg} -> @@ -201,7 +201,7 @@ defmodule Network.PeerHandler do end defp handle_msg([@hello, server, genesis_hash], state) do - genesis = Moonbeam.genesis_hash() + genesis = Chain.genesis_hash() if genesis != genesis_hash do log(state, "wrong genesis: ~p ~p", [Base16.encode(genesis), Base16.encode(genesis_hash)]) diff --git a/mix.lock b/mix.lock index 12b8967..5b03caf 100644 --- a/mix.lock +++ b/mix.lock @@ -23,7 +23,7 @@ "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"}, 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 From 3f8683ba05ec2f5f1ecc0c4dd46b7eeee1188c0f Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Wed, 4 Oct 2023 18:06:13 +0200 Subject: [PATCH 13/14] fixup --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 637245c..44764d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,12 @@ on: ["push", "pull_request"] jobs: test_and_build: - name: "Build mix release" + name: "Build and lint" runs-on: "ubuntu-latest" steps: + - name: Install deps + run: apt-get install -y libboost-dev libboost-system-dev + - name: Setup elixir uses: erlef/setup-elixir@v1 with: From 728c9a24a67fb6d022b1a2514538b3ed54852809 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Wed, 4 Oct 2023 18:06:52 +0200 Subject: [PATCH 14/14] fixup --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44764d7..b71eb1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: runs-on: "ubuntu-latest" steps: - name: Install deps - run: apt-get install -y libboost-dev libboost-system-dev + run: sudo apt-get install -y libboost-dev libboost-system-dev - name: Setup elixir uses: erlef/setup-elixir@v1