diff --git a/lib/blacksmith/application.ex b/lib/blacksmith/application.ex index 70ac066..3435eb3 100644 --- a/lib/blacksmith/application.ex +++ b/lib/blacksmith/application.ex @@ -12,7 +12,8 @@ defmodule Blacksmith.Application do children = [ supervisor(Blacksmith.Repo, []), - {Db.Redis, name: Db.Redis}, + {Redis, name: Redis}, + {TransactionProccessor, name: TransactionProccessor}, {Forger, name: Forger}, {VM, name: VM}, {TransactionPool, name: TransactionPool}, diff --git a/lib/block.ex b/lib/block.ex deleted file mode 100644 index f72d5da..0000000 --- a/lib/block.ex +++ /dev/null @@ -1,58 +0,0 @@ -defmodule Block do - @hash_size Crypto.hash_size() - - defstruct parent_block: <<0::size(256)>>, - number: 0, - winner: <<0::size(256)>>, - state_changes_hash: Crypto.hash(<<>>) - - def to_binary(%{ - parent_block: parent_block, - number: number, - winner: winner, - state_changes_hash: state_changes_hash - }) do - parent_block <> <> <> winner <> state_changes_hash - end - - def to_json(block) do - block - |> Map.from_struct() - |> Enum.map(fn {key, value} -> - if is_binary(value) do - {key, Base.encode16(value, case: :lower)} - else - {key, value} - end - end) - |> Enum.into(%{}) - |> Poison.encode!() - end - - def to_binary(%{ - parent_block: parent_block, - number: number, - winner: winner, - state_changes_hash: state_changes_hash - }) do - parent_block <> <> <> winner <> state_changes_hash - end - - def from_map(%{ - parent_block: parent_block, - number: number, - winner: winner, - state_changes_hash: state_changes_hash - }) do - {number, _} = Integer.parse(number) - - %__MODULE__{ - parent_block: parent_block, - number: number, - winner: winner, - state_changes_hash: state_changes_hash - } - end - - def hash(block), do: Crypto.hash(to_binary(block)) -end diff --git a/lib/blockchain.ex b/lib/blockchain.ex deleted file mode 100644 index db7ba78..0000000 --- a/lib/blockchain.ex +++ /dev/null @@ -1,94 +0,0 @@ -defmodule Blockchain do - use Utils - - @db Db.Redis - - def initialize() do - if !initialized?() do - forge_genesis_block() - deploy_base_contracts() - end - end - - def initialized?() do - !is_nil(best_block_hash()) - end - - def deploy_base_contracts() do - {:ok, redis} = Redix.start_link() - - VM.set_contract_code( - redis, - Constants.system_address(), - Constants.base_api_name(), - Constants.base_api_code() - ) - - VM.set_contract_code( - redis, - Constants.system_address(), - Constants.base_token_name(), - Constants.base_token_code() - ) - - VM.set_contract_code( - redis, - Constants.system_address(), - Constants.user_contracts_name(), - Constants.user_contracts_code() - ) - - VM.set_contract_code( - redis, - Constants.system_address(), - Constants.human_readable_name_registry_name(), - Constants.human_readable_name_registry_code() - ) - end - - @doc """ - Forges the genesis block. Note the genesis block is just a `%Block{}` - with default values set. - """ - def forge_genesis_block() do - forge(%Block{}) - end - - def forge(block) do - block_hash = Block.hash(block) - - @db.set_map(block_hash, block) - @db.set_binary("best_block_hash", block_hash) - WebsocketHandler.broadcast(:blocks, block) - end - - def get_latest_blocks(number) do - end - - def finalize_block() do - best_block = best_block() - - block = %Block{ - number: best_block.number + 1, - parent_block: Block.hash(best_block), - state_changes_hash: state_changes_hash() - } - - forge(block) - reset_state_changes() - end - - def reset_state_changes(), do: @db.delete("state_changes") - - def best_block_hash() do - @db.get_binary("best_block_hash") |> ok - end - - def best_block() do - @db.get_map(best_block_hash(), Block) - end - - def state_changes_hash(), do: Crypto.hash(Enum.join(state_changes())) - - def state_changes(), do: @db.get_list("state_changes") |> ok -end diff --git a/lib/db/redis.ex b/lib/db/redis.ex deleted file mode 100644 index 19cbd95..0000000 --- a/lib/db/redis.ex +++ /dev/null @@ -1,166 +0,0 @@ -defmodule Db.Redis do - @behaviour DbBehaviour - import Utils - use GenServer - - def start_link(opts) do - GenServer.start_link(__MODULE__, %{}, opts) - end - - def init(_args) do - {:ok, redis} = Redix.start_link() - {:ok, redis} - end - - def get_binary(key) do - GenServer.call(Db.Redis, {:get_binary, key}) - end - - def reset() do - GenServer.cast(Db.Redis, :reset) - end - - def delete(key) do - GenServer.cast(Db.Redis, {:delete, key}) - end - - def set_binary(key, value) do - GenServer.cast(Db.Redis, {:set_binary, key, value}) - end - - def set_map(key, value) do - GenServer.cast(Db.Redis, {:set_map, key, value}) - end - - def get_map(key, struct \\ nil) do - GenServer.call(Db.Redis, {:get_map, key, struct}) - end - - def push(key, value) do - GenServer.cast(Db.Redis, {:push, key, value}) - end - - def pop(key) do - GenServer.call(Db.Redis, {:pop, key}) - end - - def get_list(key) do - GenServer.call(Db.Redis, {:get_list, key}) - end - - def handle_cast(:reset, redis) do - Redix.command(redis, [ - "FLUSHALL" - ]) - - {:noreply, redis} - end - - def handle_cast({:set_map, key, value}, redis) do - value = - if Map.has_key?(value, :__struct__) do - Map.from_struct(value) - else - value - end - - keys_and_values = - Enum.flat_map(value, fn {k, v} -> - if is_number(v) do - [k, :binary.encode_unsigned(v)] - else - [k, v] - end - end) - - Redix.command( - redis, - [ - "HSET", - key - ] ++ keys_and_values - ) - - {:noreply, redis} - end - - def handle_cast({:delete, key}, redis) do - Redix.command(redis, [ - "DEL", - key - ]) - - {:noreply, redis} - end - - def handle_cast({:set_binary, key, value}, redis) do - Redix.command(redis, [ - "SET", - key, - value - ]) - - {:noreply, redis} - end - - def handle_cast({:push, key, value}, redis) do - Redix.command(redis, [ - "RPUSH", - key, - value - ]) - - {:noreply, redis} - end - - def handle_call({:get_map, key, struct}, _from, redis) do - value = - Redix.command(redis, [ - "HGETALL", - key - ]) - |> ok - |> Enum.chunk_every(2) - |> Map.new(fn [k, v] -> - if is_number(Map.get(struct(struct), String.to_atom(k))) do - {String.to_atom(k), :binary.decode_unsigned(v)} - else - {String.to_atom(k), v} - end - end) - - {:reply, value, redis} - end - - def handle_call({:get_list, key}, _from, redis) do - value = - Redix.command(redis, [ - "LRANGE", - key, - 0, - -1 - ]) - - {:reply, value, redis} - end - - def handle_call({:pop, key}, _from, redis) do - value = - Redix.command(redis, [ - "LPOP", - key - ]) - - {:reply, value, redis} - end - - def handle_call({:get_binary, key}, _from, redis) do - value = - Redix.command(redis, [ - "GET", - key - ]) - - {:reply, value, redis} - end -end diff --git a/lib/forger.ex b/lib/forger.ex index 44c29ea..9caccf4 100644 --- a/lib/forger.ex +++ b/lib/forger.ex @@ -1,4 +1,5 @@ defmodule Forger do + @one_second 1_000 use GenServer def start_link(opts) do @@ -39,11 +40,14 @@ defmodule Forger do end def handle_cast(:auto_forge, state = %{redis: redis, auto_forge: auto_forge}) do - {:ok, ["transactions::done", receipt]} = - Redix.command(redis, ["BRPOP", "transactions::done", 0]) + case Redix.command(redis, ["BRPOP", "transactions::done", 1]) do + {:ok, ["transactions::done", receipt]} -> + GenServer.cast(self(), {:forge, [receipt]}) + {:ok, nil} -> + end + if auto_forge do - GenServer.cast(self(), {:forge, [receipt]}) GenServer.cast(self(), :auto_forge) end @@ -55,6 +59,10 @@ defmodule Forger do send(subscriber, receipts) end) + state = Map.put(state, :subscribers, []) + {:noreply, state} + end + def handle_cast({:forge, receipts}, state) do {:noreply, state} end diff --git a/lib/models/block.ex b/lib/models/block.ex index e85d24e..451acbb 100644 --- a/lib/models/block.ex +++ b/lib/models/block.ex @@ -12,7 +12,7 @@ defmodule Models.Block do timestamps end - def max_burned(query \\ __MODULE__), do: from(q in query, order_by: q.max_burned) |> Repo.one() + def max_burned(query \\ __MODULE__), do: from(q in query, order_by: q.total_burned) def latest(query \\ __MODULE__, count), do: from(q in query, order_by: q.number, limit: ^count) diff --git a/lib/models/contract.ex b/lib/models/contract.ex new file mode 100644 index 0000000..b352fc2 --- /dev/null +++ b/lib/models/contract.ex @@ -0,0 +1,64 @@ +defmodule Models.Contract do + @system_address Constants.system_address() + + use Ecto.Schema + import Ecto.Changeset + import Ecto.Query, only: [from: 1, from: 2] + + schema "contracts" do + field(:code, :binary) + timestamps + end + + def post(%{ + address: <<0::256>>, + contract_name: contract_name, + method: method, + params: params, + sender: sender + }) do + TransactionPool.add(%{ + code: system_code(contract_name), + env: %{ + sender: sender, + address: @system_address, + contract_name: contract_name + }, + method: method, + params: params + }) + end + + def get(%{ + address: <<0::256>>, + contract_name: contract_name, + method: method, + params: params, + }) do + VM.get(%{ + code: system_code(contract_name), + env: %{ + address: Constants.system_address(), + contract_name: contract_name + }, + method: method, + params: params + }) + end + + def system_code(contract_name) do + case contract_name do + "BaseToken" -> Constants.base_token_code() + "BaseApi" -> Constants.base_api_code() + "UserContracts" -> Constants.user_contracts_code() + "HumanReadableNameRegistery" -> Constants.human_readable_name_registry_code() + end + end + + def changeset(user, params \\ %{}) do + user + |> validate_required([ + :code + ]) + end +end diff --git a/lib/redis.ex b/lib/redis.ex index 5ba47d3..d3d6316 100644 --- a/lib/redis.ex +++ b/lib/redis.ex @@ -1,84 +1,166 @@ defmodule Redis do + @behaviour DbBehaviour import Utils + use GenServer - def flushall(redis) do - Redix.command(redis, [ - "FLUSHALL" - ]) + def start_link(opts) do + GenServer.start_link(__MODULE__, %{}, opts) end - def get(redis, key) do - Redix.command(redis, [ - "GET", - key - ]) + def init(_args) do + {:ok, redis} = Redix.start_link() + {:ok, redis} end - def del(redis, key) do - Redix.command(redis, [ - "DEL", - key - ]) + def get_binary(key) do + GenServer.call(Redis, {:get_binary, key}) + end + + def reset() do + GenServer.cast(Redis, :reset) + end + + def delete(key) do + GenServer.cast(Redis, {:delete, key}) + end + + def set_binary(key, value) do + GenServer.cast(Redis, {:set_binary, key, value}) end - def set(redis, key, value) do + def set_map(key, value) do + GenServer.cast(Redis, {:set_map, key, value}) + end + + def get_map(key, struct \\ nil) do + GenServer.call(Redis, {:get_map, key, struct}) + end + + def push(key, value) do + GenServer.cast(Redis, {:push, key, value}) + end + + def pop(key) do + GenServer.call(Redis, {:pop, key}) + end + + def get_list(key) do + GenServer.call(Redis, {:get_list, key}) + end + + def handle_cast(:reset, redis) do Redix.command(redis, [ - "SET", - key, - value + "FLUSHALL" ]) + + {:noreply, redis} end - def hset(redis, key, hash) do + def handle_cast({:set_map, key, value}, redis) do + value = + if Map.has_key?(value, :__struct__) do + Map.from_struct(value) + else + value + end + + keys_and_values = + Enum.flat_map(value, fn {k, v} -> + if is_number(v) do + [k, :binary.encode_unsigned(v)] + else + [k, v] + end + end) + Redix.command( redis, [ "HSET", key - ] ++ Enum.flat_map(hash, fn {k, v} -> [k, v] end) + ] ++ keys_and_values ) + + {:noreply, redis} end - def hgetall(redis, key) do + def handle_cast({:delete, key}, redis) do Redix.command(redis, [ - "HGETALL", + "DEL", key ]) - |> ok - |> Enum.chunk_every(2) - |> Map.new(fn [k, v] -> - {String.to_atom(k), v} - end) + + {:noreply, redis} end - def rpush(redis, key, value) do + def handle_cast({:set_binary, key, value}, redis) do Redix.command(redis, [ - "RPUSH", + "SET", key, value ]) + + {:noreply, redis} end - def lpop(redis, key) do + def handle_cast({:push, key, value}, redis) do Redix.command(redis, [ - "LPOP", - key + "RPUSH", + key, + value ]) + + {:noreply, redis} end - def llen(redis, key) do - Redix.command(redis, [ - "LLEN", - key - ]) + def handle_call({:get_map, key, struct}, _from, redis) do + value = + Redix.command(redis, [ + "HGETALL", + key + ]) + |> ok + |> Enum.chunk_every(2) + |> Map.new(fn [k, v] -> + if is_number(Map.get(struct(struct), String.to_atom(k))) do + {String.to_atom(k), :binary.decode_unsigned(v)} + else + {String.to_atom(k), v} + end + end) + + {:reply, value, redis} end - def lrange(redis, key, start, stop) do - Redix.command(redis, [ - "LRANGE", - key, - start, - stop - ]) + def handle_call({:get_list, key}, _from, redis) do + value = + Redix.command(redis, [ + "LRANGE", + key, + 0, + -1 + ]) + + {:reply, value, redis} + end + + def handle_call({:pop, key}, _from, redis) do + value = + Redix.command(redis, [ + "LPOP", + key + ]) + + {:reply, value, redis} + end + + def handle_call({:get_binary, key}, _from, redis) do + value = + Redix.command(redis, [ + "GET", + key + ]) + + {:reply, value, redis} end end diff --git a/lib/router.ex b/lib/router.ex index f1dd531..0ab3e7f 100644 --- a/lib/router.ex +++ b/lib/router.ex @@ -7,6 +7,8 @@ defmodule Router do alias Blacksmith.Plug.CBOR alias Blacksmith.Plug.SignatureAuth + alias Models.Contract + plug(CORSPlug) plug(Plug.Parsers, @@ -29,12 +31,9 @@ defmodule Router do result = conn |> parse_get_request() - |> VM.run_get() + {:ok, result} = Contract.get(result) - case result do - {:ok, result} -> send_resp(conn, 200, result) - {:error, error_code, response} -> send_resp(conn, 500, response) - end + send_resp(conn, 200, result) end get "/blocks" do @@ -60,14 +59,10 @@ defmodule Router do end post "/transactions" do - TransactionPool.add(conn.assigns.body) + Contract.post(conn.params) + Forger.wait_for_block(self()) - result = - receive do - {:transaction_forged, transaction} -> transaction - end - - send_resp(conn, 200, result) + send_resp(conn, 200, "") end def parse_get_request(conn) do diff --git a/lib/transaction_pool.ex b/lib/transaction_pool.ex index 3754d04..39e2193 100644 --- a/lib/transaction_pool.ex +++ b/lib/transaction_pool.ex @@ -1,6 +1,5 @@ defmodule TransactionPool do @channel "transactions" - @db Db.Redis use GenServer def start_link(opts) do @@ -8,13 +7,10 @@ defmodule TransactionPool do end def init(state) do - {:ok, pubsub} = Redix.PubSub.start_link() {:ok, redis} = Redix.start_link() - Redix.PubSub.subscribe(pubsub, @channel, self()) {:ok, Map.merge(state, %{ - pubsub: pubsub, subscribers: %{}, processes: %{}, redis: redis, @@ -35,7 +31,6 @@ defmodule TransactionPool do receive do {:transaction, :done, <>} -> - IO.inspect("got it") {return_code, result} other -> @@ -47,30 +42,6 @@ defmodule TransactionPool do GenServer.cast(__MODULE__, {:subscribe, sender, nonce, pid}) end - def handle_info({:redix_pubsub, pid, :subscribed, %{channel: @channel}}, state) do - {:noreply, state} - end - - def handle_cast({:subscribe, sender, nonce, pid}, state = %{pubsub: pubsub}) do - sender_and_nonce = sender <> <> - state = put_in(state, [:subscribers, sender_and_nonce], pid) - {:noreply, state} - end - - def handle_info( - {:redix_pubsub, pubsub, :message, %{channel: @channel, payload: payload}}, - state - ) do - << - sender_and_nonce::binary-size(40), - output::binary - >> = payload - - pid = get_in(state, [:subscribers, sender_and_nonce]) - send(pid, {:transaction, :done, output}) - {:noreply, state} - end - def handle_call( {:add, transaction}, {pid, _reference}, @@ -78,14 +49,8 @@ defmodule TransactionPool do redis: redis } ) do - @db.push("transactions::queued", transaction) + Redis.push("transactions::queued", transaction) {:reply, {:ok, nil}, state} end - - def set_contract_code(redis, address, contract_name, contract_code) do - key = address <> Helpers.pad_bytes_right(contract_name) - - Redis.set(redis, key, contract_code) - end end diff --git a/lib/transaction_processor.ex b/lib/transaction_processor.ex index 415847f..c12f87c 100644 --- a/lib/transaction_processor.ex +++ b/lib/transaction_processor.ex @@ -1,8 +1,17 @@ defmodule TransactionProccessor do + use GenServer + @crate "transaction_processor" - def start_link() do - # Port.open({:spawn_executable, path_to_executable}, - # [args: ["redis://127.0.0.1/"]]) + def start_link(opts) do + Port.open({:spawn_executable, path_to_executable}, + args: ["redis://127.0.0.1/"] + ) + + GenServer.start_link(__MODULE__, %{}, opts) + end + + def init(_args) do + {:ok, nil} end def path_to_executable() do diff --git a/mix.exs b/mix.exs index 36b8ff3..4244c9e 100644 --- a/mix.exs +++ b/mix.exs @@ -50,7 +50,8 @@ defmodule Blacksmith.Mixfile do {:ex_machina, "~> 2.2", only: :test}, {:httpoison, "~> 1.1", only: [:dev, :test]}, {:libsodium, "~> 0.0.10"}, - {:ok, "~> 1.11"}, + {:binary, "~> 0.0.5"}, + {:ok, "~> 2.0"}, {:plug, "~> 1.5"}, {:postgrex, "~> 0.13.0"}, {:redix, "0.6.0"}, diff --git a/mix.lock b/mix.lock index fdf6301..b8f81d4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,6 @@ %{ "benchee": {:hex, :benchee, "0.12.1", "1286a79bab2f1899220134daf9a695586af00ea71968e6cd89d3d3afdf7cecba", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, + "binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], [], "hexpm"}, "cbor": {:hex, :cbor, "0.1.4", "2a2f7d082269b8281fcd15e73c481c90763a546eb2c17c360680a7dc481ca850", [:mix], [], "hexpm"}, "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "chatterbox": {:git, "https://github.com/tony612/chatterbox.git", "c7c2ab4c3e74618cfa78fdbf6444fac3952d194b", [branch: "my-fix"]}, @@ -30,7 +31,7 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, - "ok": {:hex, :ok, "1.11.0", "0acbb73d98b5198db963ae710dad2e6efeed05149871a30e1eb08a91ae4f7042", [:mix], [], "hexpm"}, + "ok": {:hex, :ok, "2.0.0", "bbfa96d5c35956b8a687aa1513a08f1287f77993a7b5554cd3db33c19c9e76fa", [:mix], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "plug": {:hex, :plug, "1.5.0", "224b25b4039bedc1eac149fb52ed456770b9678bbf0349cdd810460e1e09195b", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, diff --git a/native/transaction_processor/Cargo.lock b/native/transaction_processor/Cargo.lock index 3b99e59..9c0024c 100644 --- a/native/transaction_processor/Cargo.lock +++ b/native/transaction_processor/Cargo.lock @@ -132,6 +132,11 @@ dependencies = [ "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "idna" version = "0.1.5" @@ -604,6 +609,7 @@ name = "vm" version = "0.1.0" dependencies = [ "heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "redis 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustler 0.18.1-alpha.0 (git+https://github.com/hansihe/rustler.git)", @@ -687,6 +693,7 @@ dependencies = [ "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum half 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee6c0438de3ca4d8cac2eec62b228e2f8865cfe9ebefea720406774223fa2d2e" "checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" +"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" diff --git a/native/transaction_processor/src/main.rs b/native/transaction_processor/src/main.rs index e08db7e..179be03 100644 --- a/native/transaction_processor/src/main.rs +++ b/native/transaction_processor/src/main.rs @@ -1,34 +1,36 @@ extern crate redis; extern crate vm; -use std::mem::transmute; -use vm::{ - Commands, - Client, - Transaction, - transaction_from_slice, - run_transaction, -}; +use vm::{run_transaction, transaction_from_slice, Client, Commands, Transaction}; fn main() { let client: Client = vm::Client::open("redis://127.0.0.1/").unwrap(); let conn = client.get_connection().unwrap(); loop { - let transaction_bytes: Vec = conn.brpoplpush("transactions::queued", "transactions::processing", 0).unwrap(); - let transaction : Transaction = transaction_from_slice(&transaction_bytes); + let transaction_bytes: Vec = conn + .brpoplpush("transactions::queued", "transactions::processing", 0) + .unwrap(); + let transaction: Transaction = transaction_from_slice(&transaction_bytes); let output = run_transaction(&transaction, &conn); + // let nonce_bytes: [u8; 8] = unsafe { transmute(transaction.nonce.unwrap_or(0)) }; let mut transaction_result: Vec = Vec::new(); transaction_result.extend(transaction.env.get("sender").unwrap().to_vec()); - + // transaction_result.extend(nonce_bytes.to_vec()); transaction_result.extend(output); - println!("done: {:?}", transaction_result); let _: () = vm::pipe() .atomic() - .cmd("LPOP").arg("transactions::processing").ignore() - .cmd("RPUSH").arg("transactions::done").arg(transaction_result).ignore().query(&conn).unwrap(); + .cmd("LPOP") + .arg("transactions::processing") + .ignore() + .cmd("RPUSH") + .arg("transactions::done") + .arg(transaction_result) + .ignore() + .query(&conn) + .unwrap(); // // conn.publish::<&str, Vec, ()>("transactions", message).unwrap(); diff --git a/native/vm/Cargo.toml b/native/vm/Cargo.toml index 333dbae..a8186e2 100644 --- a/native/vm/Cargo.toml +++ b/native/vm/Cargo.toml @@ -12,3 +12,4 @@ time = "*" lazy_static = "0.2" wasmi = { path = "../../../wasmi" } serde_cbor = { path = "../../../cbor" } +hex = "*" diff --git a/native/vm/src/db/redis.rs b/native/vm/src/db/redis.rs index 9bad528..e92758a 100644 --- a/native/vm/src/db/redis.rs +++ b/native/vm/src/db/redis.rs @@ -4,11 +4,18 @@ use db::DB; impl DB for redis::Connection { fn write(&self, key: &[u8], value: &[u8]) { - let _ : () = redis::pipe() - .atomic() - .cmd("SET").arg(key).arg(value).ignore() - .cmd("RPUSH").arg("state_changes").arg(([&key[..], &value[..]]).concat()).ignore() - .query(self).unwrap(); + let _: () = redis::pipe() + .atomic() + .cmd("SET") + .arg(key) + .arg(value) + .ignore() + .cmd("RPUSH") + .arg("state_changes") + .arg(([&key[..], &value[..]]).concat()) + .ignore() + .query(self) + .unwrap(); } fn read(&self, key: &[u8]) -> Vec { diff --git a/native/vm/src/ellipticoin_api.rs b/native/vm/src/ellipticoin_api.rs index 2cc6670..0491a74 100644 --- a/native/vm/src/ellipticoin_api.rs +++ b/native/vm/src/ellipticoin_api.rs @@ -1,9 +1,8 @@ -use vm::*; -use wasmi::{Error as InterpreterError}; use serde_cbor::{from_slice, to_vec, Value}; -use wasmi::*; use std::str; - +use vm::*; +use wasmi::Error as InterpreterError; +use wasmi::*; const SENDER_FUNC_INDEX: usize = 0; const BLOCK_HASH_FUNC_INDEX: usize = 1; @@ -21,10 +20,7 @@ impl EllipticoinAPI { let mut imports = ImportsBuilder::new(); imports.push_resolver("env", &EllipticoinAPI); - ModuleInstance::new( - &module, - &imports - ) + ModuleInstance::new(&module, &imports) .expect("Failed to instantiate module") .run_start(&mut NopExternals) .expect("Failed to run start function in module") @@ -34,7 +30,7 @@ impl EllipticoinAPI { vm: &mut VM, index: usize, args: RuntimeArgs, - ) -> Result, Trap> { + ) -> Result, Trap> { match index { SENDER_FUNC_INDEX => { if let Some(sender) = vm.env.get("sender") { @@ -60,16 +56,12 @@ impl EllipticoinAPI { Ok(None) } - THROW_FUNC_INDEX => { - Ok(None) - } + THROW_FUNC_INDEX => Ok(None), CALL_FUNC_INDEX => { let code = vm.read_pointer(args.nth(0)); let method = vm.read_pointer(args.nth(1)); let args_value = from_slice::(&vm.read_pointer(args.nth(2))).unwrap(); - let args_iter: &Vec = args_value - .as_array() - .unwrap(); + let args_iter: &Vec = args_value.as_array().unwrap(); let _storage = vm.read_pointer(args.nth(3)); let module = EllipticoinAPI::new_module(&code); @@ -89,10 +81,8 @@ impl EllipticoinAPI { let result = inner_vm.read_pointer(result_ptr).clone(); Ok(Some(vm.write_pointer(result.to_vec()).into())) } - LOG_WRITE => { - Ok(None) - } - _ => panic!("unknown function index") + LOG_WRITE => Ok(None), + _ => panic!("unknown function index"), } } } @@ -102,24 +92,50 @@ impl<'a> ModuleImportResolver for EllipticoinAPI { &self, field_name: &str, _signature: &Signature, - ) -> Result { + ) -> Result { let func_ref = match field_name { - "_sender" => { - FuncInstance::alloc_host(Signature::new(&[][..], Some(ValueType::I32)), SENDER_FUNC_INDEX) - }, - "_block_hash" => { - FuncInstance::alloc_host(Signature::new(&[][..], Some(ValueType::I32)), BLOCK_HASH_FUNC_INDEX) - }, - "_read" => FuncInstance::alloc_host(Signature::new(&[ValueType::I32][..], Some(ValueType::I32)), READ_FUNC_INDEX), - "_write" => FuncInstance::alloc_host(Signature::new(&[ValueType::I32, ValueType::I32][..], None), WRITE_FUNC_INDEX), - "throw" => FuncInstance::alloc_host(Signature::new(&[ValueType::I32][..], None), THROW_FUNC_INDEX), - "_call" => FuncInstance::alloc_host(Signature::new(&[ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32][..], Some(ValueType::I32)), CALL_FUNC_INDEX), - "log_write" => FuncInstance::alloc_host(Signature::new(&[ValueType::I32, ValueType::I32][..], None), LOG_WRITE), - _ => return Err( - InterpreterError::Function( - format!("host module doesn't export function with name {}", field_name) - ) - ) + "_sender" => FuncInstance::alloc_host( + Signature::new(&[][..], Some(ValueType::I32)), + SENDER_FUNC_INDEX, + ), + "_block_hash" => FuncInstance::alloc_host( + Signature::new(&[][..], Some(ValueType::I32)), + BLOCK_HASH_FUNC_INDEX, + ), + "_read" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32][..], Some(ValueType::I32)), + READ_FUNC_INDEX, + ), + "_write" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32, ValueType::I32][..], None), + WRITE_FUNC_INDEX, + ), + "throw" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32][..], None), + THROW_FUNC_INDEX, + ), + "_call" => FuncInstance::alloc_host( + Signature::new( + &[ + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ][..], + Some(ValueType::I32), + ), + CALL_FUNC_INDEX, + ), + "log_write" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32, ValueType::I32][..], None), + LOG_WRITE, + ), + _ => { + return Err(InterpreterError::Function(format!( + "host module doesn't export function with name {}", + field_name + ))) + } }; Ok(func_ref) } diff --git a/native/vm/src/helpers.rs b/native/vm/src/helpers.rs index 31a76d3..c906bff 100644 --- a/native/vm/src/helpers.rs +++ b/native/vm/src/helpers.rs @@ -7,7 +7,8 @@ pub unsafe trait VecWithLength { unsafe impl VecWithLength for Vec { fn to_vec_with_length(&self) -> Vec { - let length_slice: [u8; LENGTH_BYTE_COUNT] = unsafe{ transmute::(self.len() as u32) }; + let length_slice: [u8; LENGTH_BYTE_COUNT] = + unsafe { transmute::(self.len() as u32) }; [&length_slice, &self[..]].concat() } } diff --git a/native/vm/src/lib.rs b/native/vm/src/lib.rs index 95b61e4..015328f 100644 --- a/native/vm/src/lib.rs +++ b/native/vm/src/lib.rs @@ -1,45 +1,27 @@ -#![feature( - custom_attribute, -)] -#[macro_use] extern crate lazy_static; -extern crate time; +#![feature(custom_attribute,)] +#[macro_use] +extern crate lazy_static; extern crate heck; +extern crate redis; extern crate rustler; +extern crate serde_cbor; extern crate sha3; -extern crate redis; +extern crate time; extern crate wasmi; -extern crate serde_cbor; +mod db; mod ellipticoin_api; mod helpers; -mod vm; -mod db; mod transaction; -pub use ellipticoin_api::{ - EllipticoinAPI, -}; +mod vm; +pub use ellipticoin_api::EllipticoinAPI; -pub use transaction::{ - Transaction, - transaction_from_slice, - run_transaction, -}; -pub use vm::{ - VM, -}; -pub use db::{ - DB, -}; -pub use wasmi::{ - RuntimeValue, -}; +pub use db::DB; +pub use transaction::{run_transaction, transaction_from_slice, Transaction}; +pub use vm::VM; +pub use wasmi::RuntimeValue; -pub use rustler::{Env, Term, NifResult, Encoder, Decoder}; -pub use rustler::types::atom::{Atom}; -pub use redis::{ - Connection, - Client, - Commands, - pipe, -}; +pub use redis::{pipe, Client, Commands, Connection}; pub use rustler::resource::ResourceArc; +pub use rustler::types::atom::Atom; +pub use rustler::{Decoder, Encoder, Env, NifResult, Term}; diff --git a/native/vm/src/transaction.rs b/native/vm/src/transaction.rs index 7f2e1bc..4546b65 100644 --- a/native/vm/src/transaction.rs +++ b/native/vm/src/transaction.rs @@ -1,69 +1,71 @@ -use time::PreciseTime; +use ellipticoin_api::EllipticoinAPI; +use heck::SnakeCase; +use redis::{Commands, Connection}; +use std::collections::{BTreeMap, HashMap}; use std::fs::File; use std::io::Read; use vm::VM; -use ellipticoin_api::EllipticoinAPI; -use redis::{ - Connection, - Commands, -}; -use std::collections::{ - BTreeMap, - HashMap, -}; -use heck::SnakeCase; const BASE_CONTRACTS_PATH: &str = "base_contracts"; const USER_CONTRACTS_NAME: &str = "UserContracts"; lazy_static! { - static ref SYSTEM_ADDRESS: Vec = vec![0;32]; + static ref SYSTEM_ADDRESS: Vec = vec![0; 32]; } lazy_static! { static ref SYSTEM_CONTRACTS: HashMap<&'static str, Vec> = { - let system_contracts = vec!{ - "BaseApi", - "BaseToken", - USER_CONTRACTS_NAME, - }; - system_contracts.iter().map(|&system_contract| { - let filename = format!("{}.wasm", system_contract.clone().to_snake_case()); - let mut file = File::open(format!("{}/{}", BASE_CONTRACTS_PATH, filename)).unwrap(); - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).unwrap(); - (system_contract, buffer) - }).collect() + let system_contracts = vec!["BaseApi", "BaseToken", USER_CONTRACTS_NAME]; + system_contracts + .iter() + .map(|&system_contract| { + let filename = format!("{}.wasm", system_contract.clone().to_snake_case()); + let mut file = File::open(format!("{}/{}", BASE_CONTRACTS_PATH, filename)).unwrap(); + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).unwrap(); + (system_contract, buffer) + }).collect() }; } -pub use wasmi::{ - RuntimeValue, -}; -use serde_cbor::{ - to_vec, - from_slice, - Value, -}; +use serde_cbor::{from_slice, to_vec, Value}; +pub use wasmi::RuntimeValue; #[derive(Clone, Debug)] pub struct Transaction { pub code: Vec, pub env: HashMap>, pub method: String, - pub params: Vec + pub params: Vec, } - impl From> for Transaction { fn from(transaction: BTreeMap) -> Self { - let params = transaction.get("params").unwrap().as_array().unwrap().to_vec(); + let params = transaction + .get("params") + .unwrap() + .as_array() + .unwrap() + .to_vec(); let mut env: HashMap> = HashMap::new(); let map = transaction.get("env").unwrap().as_object().unwrap(); for (key, value) in map { - env.insert(key.as_string().unwrap().to_string(), to_vec(value).unwrap()); + env.insert( + key.as_string().unwrap().to_string(), + value.as_bytes().unwrap().to_vec(), + ); } Transaction { - code: transaction.get("code").unwrap().as_bytes().unwrap().to_vec(), - method: transaction.get("method").unwrap().as_string().unwrap().to_string(), + code: transaction + .get("code") + .unwrap() + .as_bytes() + .unwrap() + .to_vec(), + method: transaction + .get("method") + .unwrap() + .as_string() + .unwrap() + .to_string(), env: env, params: params, } @@ -71,21 +73,27 @@ impl From> for Transaction { } pub fn transaction_from_slice(transaction_bytes: &[u8]) -> Transaction { - from_slice::>(&transaction_bytes).unwrap().into() + from_slice::>(&transaction_bytes) + .unwrap() + .into() } -pub fn run_transaction(transaction: &Transaction, db: &Connection) -> Vec{ - +pub fn run_transaction(transaction: &Transaction, db: &Connection) -> Vec { let module = EllipticoinAPI::new_module(&transaction.code); let mut vm = VM::new(db, &transaction.env, &module); - let params: Vec = transaction.params.iter().map(|param| { - let param_vec = to_vec(param).unwrap(); - let param_pointer = vm.write_pointer(param_vec); - RuntimeValue::I32(param_pointer as i32) - }).collect(); + let params: Vec = transaction + .params + .iter() + .map(|param| { + let param_vec = to_vec(param).unwrap(); + let param_pointer = vm.write_pointer(param_vec); + RuntimeValue::I32(param_pointer as i32) + }).collect(); let pointer = vm.call(&transaction.method, ¶ms); - vm.read_pointer(pointer) + let result = vm.read_pointer(pointer); + + result } // fn get_code(conn: &Connection, address: Vec, contract_name: &str) -> Vec { diff --git a/native/vm/src/vm.rs b/native/vm/src/vm.rs index 92f13d0..7cbfa5d 100644 --- a/native/vm/src/vm.rs +++ b/native/vm/src/vm.rs @@ -1,11 +1,12 @@ +extern crate hex; +use self::memory_units::Pages; +use db::DB; +use ellipticoin_api::*; use helpers::*; -use wasmi::*; use std::collections::HashMap; -use wasmi::RuntimeValue; -use self::memory_units::Pages; use std::mem::transmute; -use ellipticoin_api::*; -use db::DB; +use wasmi::RuntimeValue; +use wasmi::*; pub struct VM<'a> { pub instance: &'a ModuleRef, @@ -25,34 +26,42 @@ impl<'a> VM<'a> { pub fn write_pointer(&mut self, vec: Vec) -> u32 { let vec_with_length = vec.to_vec_with_length(); let vec_pointer = self.call(&"alloc", &[RuntimeValue::I32(vec_with_length.len() as i32)]); - self.memory().set(vec_pointer, vec_with_length.as_slice()).unwrap(); + self.memory() + .set(vec_pointer, vec_with_length.as_slice()) + .unwrap(); vec_pointer } pub fn read(&mut self, key: Vec) -> Vec { - let contracts_address = self.env.get("address").unwrap().to_vec(); - let contract_name = self.env.get("contract_name").unwrap().to_vec(); + let contract_address = self.env.get("address").unwrap().to_vec(); + let mut contract_name = self.env.get("contract_name").unwrap().to_vec(); - let key = [contracts_address, contract_name, key].concat(); - self.db.read(key.as_slice()) + let contract_name_len = contract_name.clone().len(); + contract_name.extend_from_slice(&vec![0; 32 - contract_name_len]); - } + let key = [contract_address.clone(), contract_name, key].concat(); + let result = self.db.read(key.as_slice()); + result + } pub fn write(&mut self, key: Vec, value: Vec) { let contracts_address = self.env.get("address").unwrap().to_vec(); - let contract_name = self.env.get("contract_name").unwrap().to_vec(); + let mut contract_name = self.env.get("contract_name").unwrap().to_vec(); + let contract_name_len = contract_name.clone().len(); + contract_name.extend_from_slice(&vec![0; 32 - contract_name_len]); let key = [contracts_address, contract_name, key].concat(); self.db.write(key.as_slice(), value.as_slice()); } - pub fn read_pointer(&mut self, ptr: u32) -> Vec{ + pub fn read_pointer(&mut self, ptr: u32) -> Vec { let length_slice = self.memory().get(ptr, 4).unwrap(); let mut length_u8 = [0 as u8; LENGTH_BYTE_COUNT]; length_u8.clone_from_slice(&length_slice); - let length: u32 = unsafe {(transmute::<[u8; 4], u32>(length_u8))}; - self.memory().get(ptr + 4, length as usize).unwrap() + let length: u32 = unsafe { (transmute::<[u8; 4], u32>(length_u8)) }; + let mem = self.memory().get(ptr + 4, length as usize).unwrap(); + mem } pub fn call(&mut self, func: &str, args: &[RuntimeValue]) -> u32 { @@ -77,7 +86,7 @@ impl<'a> Externals for VM<'a> { &mut self, index: usize, args: RuntimeArgs, - ) -> Result, Trap> { + ) -> Result, Trap> { EllipticoinAPI::invoke_index(self, index, args) } } diff --git a/native/vm_nif/Cargo.lock b/native/vm_nif/Cargo.lock index 6ac27c6..7f7b0cb 100644 --- a/native/vm_nif/Cargo.lock +++ b/native/vm_nif/Cargo.lock @@ -59,6 +59,11 @@ dependencies = [ "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "idna" version = "0.1.5" @@ -279,6 +284,7 @@ name = "vm" version = "0.1.0" dependencies = [ "heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "redis 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustler 0.18.1-alpha.0 (git+https://github.com/hansihe/rustler.git)", @@ -344,6 +350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum half 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee6c0438de3ca4d8cac2eec62b228e2f8865cfe9ebefea720406774223fa2d2e" "checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" +"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" diff --git a/native/vm_nif/src/lib.rs b/native/vm_nif/src/lib.rs index 948c277..ae4c573 100644 --- a/native/vm_nif/src/lib.rs +++ b/native/vm_nif/src/lib.rs @@ -1,20 +1,14 @@ -#[macro_use] extern crate rustler; -#[macro_use] extern crate lazy_static; -extern crate vm; +#[macro_use] +extern crate rustler; +#[macro_use] +extern crate lazy_static; extern crate redis; +extern crate vm; -use rustler::{Env, Term, NifResult}; -use rustler::types::{ - Encoder, - OwnedBinary, - Binary, -}; +use rustler::types::{Binary, Encoder, OwnedBinary}; +use rustler::{Env, NifResult, Term}; use std::io::Write; -use vm::{ - Client, - transaction_from_slice, - run_transaction, -}; +use vm::{run_transaction, transaction_from_slice, Client}; mod atoms { rustler_atoms! { @@ -30,7 +24,6 @@ rustler_export_nifs! { None } - fn run<'a>(nif_env: Env<'a>, args: &[Term<'a>]) -> NifResult> { let conn_string: &str = try!(args[0].decode()); let transaction_binary: Binary = try!(args[1].decode()); diff --git a/priv/repo/migrations/20181012140639_add_contracts_table.exs b/priv/repo/migrations/20181012140639_add_contracts_table.exs new file mode 100644 index 0000000..c76fd43 --- /dev/null +++ b/priv/repo/migrations/20181012140639_add_contracts_table.exs @@ -0,0 +1,12 @@ +defmodule Blacksmith.Repo.Migrations.AddContractsTable do + use Ecto.Migration + + def change do + create table("contracts") do + add :code, :binary + add :name, :binary + + timestamps() + end + end +end diff --git a/test/blockchain_test.exs b/test/blockchain_test.exs deleted file mode 100644 index 6121e91..0000000 --- a/test/blockchain_test.exs +++ /dev/null @@ -1,9 +0,0 @@ -defmodule BlockchainTest do - use ExUnit.Case - - test "Blockchain.last_n_blocks" do - for number <- 1..5, do: Blockchain.insert(%Block{number: number}) - - # Blockchain.forge() - end -end diff --git a/test/integration/base_token_test.exs b/test/integration/base_token_test.exs index 0e00691..e8a59c9 100644 --- a/test/integration/base_token_test.exs +++ b/test/integration/base_token_test.exs @@ -1,56 +1,41 @@ defmodule Integration.BaseTokenTest do - @db Db.Redis @host "http://localhost:4047" - @sender Base.decode16!("509c3480af8118842da87369eb616eb7b158724927c212b676c41ce6430d334a", - case: :lower - ) - @sender_private_key Base.decode16!( - "01a596e2624497da63a15ef7dbe31f5ca2ebba5bed3d30f3319ef22c481022fd509c3480af8118842da87369eb616eb7b158724927c212b676c41ce6430d334a", - case: :lower - ) - @receiver Base.decode16!("027da28b6a46ec1124e7c3c33677b71f4ac4eae2485ff8cb33346aac54c11a30", - case: :lower - ) @adder_contract_code File.read!("test/support/wasm/adder.wasm") + import Test.Utils + use NamedAccounts use ExUnit.Case setup do - reset_db() + Redis.reset() + Forger.enable_auto_forging() + + on_exit(fn -> + Redis.reset() + Forger.disable_auto_forging() + end) :ok end test "send tokens asynchronously" do - post(%{ - private_key: @sender_private_key, - nonce: 0, - method: :constructor, - params: [100] + set_balances(%{ + @alice => 100, + @bob => 100 }) - {:ok, response} = - get(%{ - private_key: @sender_private_key, - method: :balance_of, - params: [@sender] - }) - - assert Cbor.decode!(response.body) == 100 - post(%{ - private_key: @sender_private_key, + private_key: @alices_private_key, nonce: 2, method: :transfer, - params: [@receiver, 50] + params: [@bob, 50] }) {:ok, response} = get(%{ - private_key: @sender_private_key, - nonce: 3, + private_key: @alices_private_key, method: :balance_of, - params: [@sender] + params: [@alice] }) assert Cbor.decode!(response.body) == 50 @@ -60,7 +45,7 @@ defmodule Integration.BaseTokenTest do # nonce = 0 # contract_name = "Adder" # path = "/contracts" - # sender = Crypto.public_key_from_private_key(@sender_private_key) + # sender = Crypto.public_key_from_private_key(@alices_private_key) # deployment = Cbor.encode(%{ # contract_name: contract_name, # sender: sender, @@ -69,12 +54,12 @@ defmodule Integration.BaseTokenTest do # nonce: nonce, # }) # - # put_signed(path, deployment, @sender_private_key) + # put_signed(path, deployment, @alices_private_key) # # {:ok, response} = get(%{ - # private_key: @sender_private_key, + # private_key: @alices_private_key, # contract_name: "Adder", - # address: @sender, + # address: @alice, # nonce: 1, # method: :add, # params: [1, 2], @@ -85,7 +70,7 @@ defmodule Integration.BaseTokenTest do # test "updates the blockhash" do # post(%{ - # private_key: @sender_private_key, + # private_key: @alices_private_key, # nonce: 0, # method: :constructor, # params: [100], @@ -100,10 +85,10 @@ defmodule Integration.BaseTokenTest do # assert Cbor.decode!(response.body) == Base.decode16!("D2F64658686ADEE3EBE827A27AE1F691D21A24BA37975B3F18856307D0A0283D") # # post(%{ - # private_key: @sender_private_key, + # private_key: @alices_private_key, # nonce: 2, # method: :transfer, - # params: [@receiver, 50], + # params: [@bob, 50], # }) # # {:ok, response} = get(%{ diff --git a/test/models/block_test.exs b/test/models/block_test.exs deleted file mode 100644 index 77433fe..0000000 --- a/test/models/block_test.exs +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Models.BlockTest do - alias Models.Block - alias Blacksmith.Repo - import Blacksmith.Factory - use ExUnit.Case - - test "Blockchain.latest" do - insert_list(5, :block) - - assert Block.latest(3) - |> Repo.all() - |> length == 3 - end -end diff --git a/test/models/contract_test.exs b/test/models/contract_test.exs new file mode 100644 index 0000000..e0b5649 --- /dev/null +++ b/test/models/contract_test.exs @@ -0,0 +1,55 @@ +defmodule Models.ContractTest do + import Test.Utils + + alias Models.Contract + use ExUnit.Case + use NamedAccounts + use OK.Pipe + + setup_all do + Redis.reset() + Forger.enable_auto_forging() + + on_exit(fn -> + Redis.reset() + Forger.disable_auto_forging() + end) + end + + describe "Contract.execute/1" do + test "it works with token transfers" do + set_balances(%{ + @alice => 100, + @bob => 100 + }) + + Contract.post(%{ + address: Constants.system_address(), + contract_name: Constants.base_token_name(), + method: :transfer, + params: [@bob, 50], + sender: @alice + }) + + Forger.wait_for_block(self()) + + assert (Contract.get(%{ + address: Constants.system_address(), + contract_name: Constants.base_token_name(), + method: :balance_of, + params: [@alice] + }) + ~>> Cbor.decode!() + ) == 50 + + assert Contract.get(%{ + address: Constants.system_address(), + contract_name: Constants.base_token_name(), + method: :balance_of, + params: [@bob] + }) + ~>> Cbor.decode!() + == 150 + end + end +end diff --git a/test/support/named_accounts.ex b/test/support/named_accounts.ex new file mode 100644 index 0000000..5332be1 --- /dev/null +++ b/test/support/named_accounts.ex @@ -0,0 +1,12 @@ +defmodule NamedAccounts do + defmacro __using__(_) do + quote do + @alice "509c3480af8118842da87369eb616eb7b158724927c212b676c41ce6430d334a" + |> Base.decode16!(case: :lower) + @alices_private_key "01a596e2624497da63a15ef7dbe31f5ca2ebba5bed3d30f3319ef22c481022fd509c3480af8118842da87369eb616eb7b158724927c212b676c41ce6430d334a" + |> Base.decode16!(case: :lower) + @bob "027da28b6a46ec1124e7c3c33677b71f4ac4eae2485ff8cb33346aac54c11a30" + |> Base.decode16!(case: :lower) + end + end +end diff --git a/test/support/utils.ex b/test/support/utils.ex new file mode 100644 index 0000000..4bb76b6 --- /dev/null +++ b/test/support/utils.ex @@ -0,0 +1,11 @@ +defmodule Test.Utils do + import Binary + + def set_balances(balances) do + token_balance_address = Constants.system_address() <> (Constants.base_token_name() |> pad_trailing(32)) + + for {address, balance} <- balances do + Redis.set_binary(token_balance_address <> address, <>) + end + end +end diff --git a/test/utils_test.exs b/test/utils_test.exs deleted file mode 100644 index 416ef2f..0000000 --- a/test/utils_test.exs +++ /dev/null @@ -1,4 +0,0 @@ -defmodule UtilsTest do - use ExUnit.Case, async: true - doctest Utils -end diff --git a/test/vm_test.exs b/test/vm_test.exs index f9d19ea..2c38732 100644 --- a/test/vm_test.exs +++ b/test/vm_test.exs @@ -1,15 +1,15 @@ defmodule VMTest do - @db Db.Redis @sender "509c3480af8118842da87369eb616eb7b158724927c212b676c41ce6430d334a" |> Base.decode16!(case: :lower) use ExUnit.Case setup_all do + Redis.reset() Forger.enable_auto_forging() on_exit(fn -> - @db.reset() + Redis.reset() Forger.disable_auto_forging() end) end @@ -22,7 +22,7 @@ defmodule VMTest do env: %{ sender: @sender, address: @sender, - contract_name: :Counter + contract_name: "Counter" }, method: :increment_by, params: [ @@ -37,25 +37,26 @@ defmodule VMTest do env: %{ sender: @sender, address: @sender, - contract_name: :Counter + contract_name: "Counter" }, method: :increment_by, params: [ 1 ] }) + Forger.wait_for_block(self()) assert VM.get(%{ - code: counter_code, - env: %{ - sender: @sender, - address: @sender, - contract_name: :Counter - }, - method: :get_count, - params: [] - }) == {:ok, Cbor.encode(2)} + code: counter_code, + env: %{ + sender: @sender, + address: @sender, + contract_name: "Counter" + }, + method: :get_count, + params: [] + }) == {:ok, Cbor.encode(2)} end def read_test_wasm(file_name) do