{"action":"created","comment":{"url":"https://api.github.com/repos/aeternity/elixir-node/pulls/comments/214385053","pull_request_review_id":151436324,"id":214385053,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxNDM4NTA1Mw==","diff_hunk":"@@ -0,0 +1,843 @@\n+defmodule Aecore.Sync.Sync do\n+  @moduledoc \"\"\"\n+  Handle the Sync functionality between different nodes.\n+  Syncing with every new node starts a worker that it's going to execute its job.\n+  In other words sync until the biggest remote hash,\n+  starting from the agreed height -> the point at which we agree on the header hash (it's equal among us)\n+  \"\"\"\n+\n+  use GenServer\n+\n+  alias Aecore.Sync.{Jobs, Chain, Task}\n+  alias Aecore.Chain.Worker, as: Chainstate\n+  alias Aecore.Chain.BlockValidation\n+  alias Aecore.Peers.PeerConnection\n+  alias Aecore.Peers.Worker, as: Peers\n+  alias Aeutil.Events\n+  alias Aecore.Governance.GenesisConstants, as: Genesis\n+\n+  alias __MODULE__\n+\n+  require Logger\n+\n+  @max_headers_per_chunk 100\n+\n+  @type chain_id :: reference()\n+  @type task_id :: reference()\n+  @type block_height :: non_neg_integer()\n+  @type header_hash :: binary()\n+  @type peer_id :: pid()\n+  @type last_result ::\n+          :none\n+          | {:agreed_height, Chain.chain()}\n+          | {:hash_pool, list(Task.pool_elem())}\n+          | {:get_block, block_height(), header_hash(), peer_id(), {:ok, Block.t()}}\n+          | {:post_blocks, :ok | {:error, Block.t(), block_height()}}\n+  @type t :: %Sync{sync_tasks: list(Task.t())}\n+\n+  defstruct sync_tasks: []\n+\n+  ### ===================================================\n+  ### API calls\n+  ### ===================================================\n+\n+  def start_sync(peer_id, remote_hash) do\n+    if sync_in_progress?(peer_id) do\n+      Logger.info(\"#{__MODULE__}: Already syncing with #{inspect(peer_id)}\")\n+    else\n+      GenServer.cast(__MODULE__, {:start_sync, peer_id, remote_hash})\n+    end\n+  end\n+\n+  def schedule_ping(peer_id) do\n+    GenServer.cast(__MODULE__, {:schedule_ping, peer_id})\n+  end\n+\n+  @spec sync_in_progress?(peer_id) :: true | false\n+  def sync_in_progress?(peer_id) do\n+    GenServer.call(__MODULE__, {:sync_in_progress, peer_id})\n+  end\n+\n+  @spec known_chain(Chain.t(), Chain.t() | :none) ::\n+          {:inconclusive, Chain.t()}\n+          | {:existing, Task.task_id()}\n+          | {:new, Chain.t(), Task.task_id()}\n+  def known_chain(chain, extra_info) do\n+    GenServer.call(__MODULE__, {:known_chain, chain, extra_info})\n+  end\n+\n+  def update_sync_task(update, task) do\n+    GenServer.call(__MODULE__, {:update_sync_task, update, task})\n+  end\n+\n+  def next_work_item(task, peer_id, last_result) do\n+    GenServer.call(__MODULE__, {:next_work_item, task, peer_id, last_result})\n+  end\n+\n+  def handle_worker(task_id, action) do\n+    GenServer.cast(__MODULE__, {:handle_worker, task_id, action})\n+  end\n+\n+  ### ==================================================================\n+  ### GenServer functions\n+  ### ==================================================================\n+\n+  ## When we Ping a node with at least as much difficulty as we have,\n+  ## then we are going to sync with it.\n+  ## We already agree upon the genesis block and need to find the highest common\n+  ## block we agree upon. We use a binary search to find out.\n+  ## From the height we agree upon, we start asking for blocks to add to the chain.\n+  ##\n+  ## When an additional Ping arrives for which we agree upon genesis, we have\n+  ## the following possibilities:\n+  ## 1. It has worst top hash than our node, do not include in sync\n+  ## 2. It has better top hash than our node\n+  ##    We binary search for a block that we agree upon (could be genesis)\n+  ## We add new node to sync pool to sync agreed block upto top of new\n+  ## 1. If we are already synchronizing, we ignore it,\n+  ##    the ongoing sync will pick that up later\n+  ## 2. If we are not already synchronizing, we start doing so.\n+  ##\n+  ## We sync with several nodes at the same time and use as strategy\n+  ## to pick a random hash from the hashes in the pool.\n+\n+  def start_link(_args) do\n+    GenServer.start_link(__MODULE__, %Sync{sync_tasks: []}, name: __MODULE__)\n+  end\n+\n+  def init(state) do\n+    ## Subscribe to receive if we have mined/received new block\n+    ## and if new tx is created/received\n+    Events.subscribe(:new_top_block)\n+    Events.subscribe(:tx_created)\n+\n+    ## Init the jobs queues\n+    Jobs.init_queues()\n+\n+    ## Set trap_exit, to receive crash messages from async processes\n+    Process.flag(:trap_exit, true)\n+\n+    {:ok, state}\n+  end\n+\n+  def handle_call({:sync_in_progress, peer_id}, _from, state) do\n+    {:reply, peer_in_sync?(state, peer_id), state}\n+  end\n+\n+  def handle_call(\n+        {:known_chain, %Chain{chain_id: chain_id} = chain, new_chain_info},\n+        _from,\n+        state\n+      ) do\n+    {new_chain, updated_state} =\n+      case new_chain_info do\n+        :none ->\n+          {chain, state}\n+\n+        %Chain{chain_id: cid} when cid == chain_id ->\n+          {Chain.merge_chains(chain, new_chain_info), state}\n+\n+        %Chain{chain_id: _cid} ->\n+          {chain, Chain.add_chain_info(new_chain_info, state)}\n+      end\n+\n+    {res, new_state} = sync_task_for_chain(new_chain, updated_state)\n+    {:reply, res, new_state}\n+  end\n+\n+  def handle_call({:update_sync_task, update, task_id}, _from, state) do\n+    new_state = Task.do_update_sync_task(state, task_id, update)\n+    {:reply, :ok, new_state}\n+  end\n+\n+  def handle_call({:next_work_item, task_id, peer_id, {:error, _reason}}, _from, state) do\n+    new_state = Task.do_update_sync_task(state, task_id, {:error, peer_id})\n+    {:reply, :abort_work, new_state}\n+  end\n+\n+  def handle_call({:next_work_item, task_id, peer_id, last_result}, _from, state) do\n+    state_applied_last_result = handle_last_result(state, task_id, last_result)\n+\n+    {reply, state_applied_next_work_item} =\n+      get_next_work_item(state_applied_last_result, task_id, peer_id)\n+\n+    {:reply, reply, state_applied_next_work_item}\n+  end\n+\n+  ## Handle casts\n+\n+  def handle_cast({:start_sync, peer_id, remote_header_hash}, state) do\n+    Jobs.run_job(:sync_task_workers, fn -> do_start_sync(peer_id, remote_header_hash) end)\n+    {:noreply, state}\n+  end\n+\n+  def handle_cast({:schedule_ping, peer_id}, state) do\n+    if peer_in_sync?(state, peer_id) do\n+      :ok\n+    else\n+      Jobs.run_job(:sync_ping_workers, fn -> ping_peer(peer_id) end)\n+    end\n+\n+    {:noreply, state}\n+  end\n+\n+  def handle_cast({:handle_worker, task_id, action}, state) do\n+    new_state =\n+      case Task.get_sync_task(task_id, state) do\n+        {:ok, task} ->\n+          updated_task = do_handle_worker(action, task)\n+          Task.set_sync_task(updated_task, state)\n+\n+        {:error, :not_found} ->\n+          state\n+      end\n+\n+    {:noreply, new_state}\n+  end\n+\n+  def handle_cast(_, state), do: {:noreply, state}\n+\n+  def handle_info({:gproc_ps_event, event, %{info: info}}, state) do\n+    peer_ids = Peers.all_pids()\n+    non_syncing_peer_ids = Enum.filter(peer_ids, fn pid -> not peer_in_sync?(state, pid) end)\n+\n+    case event do\n+      :new_top_block -> Jobs.enqueue(:block, info, non_syncing_peer_ids)\n+      :tx_created -> Jobs.enqueue(:tx, info, peer_ids)\n+      :tx_received -> Jobs.enqueue(:tx, info, peer_ids)\n+      _ -> :ignore\n+    end\n+\n+    {:noreply, state}\n+  end\n+\n+  def handle_info({:EXIT, pid, :normal}, state) do\n+    {:noreply, do_terminate_worker(pid, state)}\n+  end\n+\n+  def handle_info({:EXIT, pid, reason}, state) do\n+    ## It might be one of our syncing workers that crashed\n+    Logger.info(\"#{__MODULE__}: Worker stopped with reason: #{inspect(reason)}\")\n+    {:noreply, do_terminate_worker(pid, state)}\n+  end\n+\n+  @spec sync_task_for_chain(Chain.t(), Sync.t()) ::\n+          {:inconclusive, Chain.t(), {:get_header, chain_id(), peer_id(), block_height()}}\n+          | {{:existing, Task.task_id()}, Sync.t()}\n+          | {{:new, Chain.t(), Task.task_id()}, Sync.t()}\n+  def sync_task_for_chain(chain, %Sync{sync_tasks: tasks} = state) do\n+    case Task.match_chain_to_task(chain, tasks, []) do\n+      :no_match ->\n+        ## Starting new task for the given chain\n+        ## We are here if this is out first task\n+        ## or if the given chain is a fork\n+        task = %Task{id: task_id} = Task.init_sync_task(chain)\n+        {{:new, chain, task_id}, Task.set_sync_task(task, state)}\n+\n+      {:match, %Task{id: task_id, chain: matched_chain} = task} ->\n+        ## The given chain matches the chain in the current Task\n+        ## Merge the chains (possibly get more blocks if the new chain is longer)\n+        new_chain = Chain.merge_chains(%Chain{chain | chain_id: task_id}, matched_chain)\n+        updated_task = %Task{task | chain: new_chain}\n+        {{:existing, task_id}, Task.set_sync_task(updated_task, state)}\n+\n+      {:inconclusive, _, _} = res ->\n+        {res, state}\n+    end\n+  end\n+\n+  @spec handle_last_result(Sync.t(), Task.task_id(), last_result()) :: Sync.t()\n+  def handle_last_result(state, task_id, last_result) do\n+    case Task.get_sync_task(task_id, state) do\n+      {:ok, task} ->\n+        updated_task = handle_last_result(task, last_result)\n+        Task.maybe_end_sync_task(state, updated_task)\n+\n+      {:error, :not_found} ->\n+        state\n+    end\n+  end\n+\n+  def handle_last_result(task, :none), do: task\n+\n+  def handle_last_result(%Task{agreed: nil} = task, {:agreed_height, agreed}),\n+    do: %Task{task | agreed: agreed}\n+\n+  def handle_last_result(%Task{pool: []} = task, {:hash_pool, hash_pool}) do\n+    {block_height, header_hash, false} = List.last(hash_pool)\n+    %Task{task | pool: hash_pool, agreed: %{height: block_height, hash: header_hash}}\n+  end\n+\n+  def handle_last_result(%Task{} = task, {:hash_pool, _hash_pool}), do: task\n+\n+  def handle_last_result(\n+        %Task{pool: pool} = task,\n+        {:get_block, block_height, header_hash, peer_id, {:ok, block}}\n+      ) do\n+    pool_with_added_block =\n+      Enum.map(pool, fn\n+        {^block_height, _, _} -> {block_height, header_hash, {peer_id, block}}\n+        elem -> elem\n+      end)\n+\n+    %Task{task | pool: pool_with_added_block}\n+  end\n+\n+  def handle_last_result(%Task{} = task, {:post_blocks, :ok}), do: %Task{task | adding: []}\n+\n+  def handle_last_result(\n+        %Task{adding: adds, pending: pends, pool: pool, chain: chain} = task,\n+        {:post_blocks, {:error, block_from_peer_id, block_height}}\n+      ) do\n+    ## Put back the blocks we did not manage to post, and schedule\n+    ## failing block for another retraival","path":"apps/aecore/lib/aecore/sync/sync.ex","position":293,"original_position":293,"commit_id":"10485e3bb6c5f9e83512ac1e9bf8d6a4ff27fc5e","original_commit_id":"10485e3bb6c5f9e83512ac1e9bf8d6a4ff27fc5e","user":{"login":"grzegorz225","id":1352848,"node_id":"MDQ6VXNlcjEzNTI4NDg=","avatar_url":"https://avatars2.githubusercontent.com/u/1352848?v=4","gravatar_id":"","url":"https://api.github.com/users/grzegorz225","html_url":"https://github.com/grzegorz225","followers_url":"https://api.github.com/users/grzegorz225/followers","following_url":"https://api.github.com/users/grzegorz225/following{/other_user}","gists_url":"https://api.github.com/users/grzegorz225/gists{/gist_id}","starred_url":"https://api.github.com/users/grzegorz225/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/grzegorz225/subscriptions","organizations_url":"https://api.github.com/users/grzegorz225/orgs","repos_url":"https://api.github.com/users/grzegorz225/repos","events_url":"https://api.github.com/users/grzegorz225/events{/privacy}","received_events_url":"https://api.github.com/users/grzegorz225/received_events","type":"User","site_admin":false},"body":"Fix typo in comment","created_at":"2018-08-31T15:08:44Z","updated_at":"2018-08-31T15:08:44Z","html_url":"https://github.com/aeternity/elixir-node/pull/567#discussion_r214385053","pull_request_url":"https://api.github.com/repos/aeternity/elixir-node/pulls/567","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/aeternity/elixir-node/pulls/comments/214385053"},"html":{"href":"https://github.com/aeternity/elixir-node/pull/567#discussion_r214385053"},"pull_request":{"href":"https://api.github.com/repos/aeternity/elixir-node/pulls/567"}}},"pull_request":{"url":"https://api.github.com/repos/aeternity/elixir-node/pulls/567","id":211776198,"node_id":"MDExOlB1bGxSZXF1ZXN0MjExNzc2MTk4","html_url":"https://github.com/aeternity/elixir-node/pull/567","diff_url":"https://github.com/aeternity/elixir-node/pull/567.diff","patch_url":"https://github.com/aeternity/elixir-node/pull/567.patch","issue_url":"https://api.github.com/repos/aeternity/elixir-node/issues/567","number":567,"state":"open","locked":false,"title":"[GH-524] Rewrite Sync module","user":{"login":"rdimitrovbg","id":11372861,"node_id":"MDQ6VXNlcjExMzcyODYx","avatar_url":"https://avatars2.githubusercontent.com/u/11372861?v=4","gravatar_id":"","url":"https://api.github.com/users/rdimitrovbg","html_url":"https://github.com/rdimitrovbg","followers_url":"https://api.github.com/users/rdimitrovbg/followers","following_url":"https://api.github.com/users/rdimitrovbg/following{/other_user}","gists_url":"https://api.github.com/users/rdimitrovbg/gists{/gist_id}","starred_url":"https://api.github.com/users/rdimitrovbg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rdimitrovbg/subscriptions","organizations_url":"https://api.github.com/users/rdimitrovbg/orgs","repos_url":"https://api.github.com/users/rdimitrovbg/repos","events_url":"https://api.github.com/users/rdimitrovbg/events{/privacy}","received_events_url":"https://api.github.com/users/rdimitrovbg/received_events","type":"User","site_admin":false},"body":"Resolves #524 resolves #560 resolves #451","created_at":"2018-08-29T14:33:38Z","updated_at":"2018-08-31T15:08:44Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e7b5dff2ac9473ce75d241e22d1e6e602596fea0","assignee":{"login":"rdimitrovbg","id":11372861,"node_id":"MDQ6VXNlcjExMzcyODYx","avatar_url":"https://avatars2.githubusercontent.com/u/11372861?v=4","gravatar_id":"","url":"https://api.github.com/users/rdimitrovbg","html_url":"https://github.com/rdimitrovbg","followers_url":"https://api.github.com/users/rdimitrovbg/followers","following_url":"https://api.github.com/users/rdimitrovbg/following{/other_user}","gists_url":"https://api.github.com/users/rdimitrovbg/gists{/gist_id}","starred_url":"https://api.github.com/users/rdimitrovbg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rdimitrovbg/subscriptions","organizations_url":"https://api.github.com/users/rdimitrovbg/orgs","repos_url":"https://api.github.com/users/rdimitrovbg/repos","events_url":"https://api.github.com/users/rdimitrovbg/events{/privacy}","received_events_url":"https://api.github.com/users/rdimitrovbg/received_events","type":"User","site_admin":false},"assignees":[{"login":"rdimitrovbg","id":11372861,"node_id":"MDQ6VXNlcjExMzcyODYx","avatar_url":"https://avatars2.githubusercontent.com/u/11372861?v=4","gravatar_id":"","url":"https://api.github.com/users/rdimitrovbg","html_url":"https://github.com/rdimitrovbg","followers_url":"https://api.github.com/users/rdimitrovbg/followers","following_url":"https://api.github.com/users/rdimitrovbg/following{/other_user}","gists_url":"https://api.github.com/users/rdimitrovbg/gists{/gist_id}","starred_url":"https://api.github.com/users/rdimitrovbg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rdimitrovbg/subscriptions","organizations_url":"https://api.github.com/users/rdimitrovbg/orgs","repos_url":"https://api.github.com/users/rdimitrovbg/repos","events_url":"https://api.github.com/users/rdimitrovbg/events{/privacy}","received_events_url":"https://api.github.com/users/rdimitrovbg/received_events","type":"User","site_admin":false},{"login":"gspasov","id":25534303,"node_id":"MDQ6VXNlcjI1NTM0MzAz","avatar_url":"https://avatars3.githubusercontent.com/u/25534303?v=4","gravatar_id":"","url":"https://api.github.com/users/gspasov","html_url":"https://github.com/gspasov","followers_url":"https://api.github.com/users/gspasov/followers","following_url":"https://api.github.com/users/gspasov/following{/other_user}","gists_url":"https://api.github.com/users/gspasov/gists{/gist_id}","starred_url":"https://api.github.com/users/gspasov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gspasov/subscriptions","organizations_url":"https://api.github.com/users/gspasov/orgs","repos_url":"https://api.github.com/users/gspasov/repos","events_url":"https://api.github.com/users/gspasov/events{/privacy}","received_events_url":"https://api.github.com/users/gspasov/received_events","type":"User","site_admin":false}],"requested_reviewers":[{"login":"cytadela8","id":3736271,"node_id":"MDQ6VXNlcjM3MzYyNzE=","avatar_url":"https://avatars0.githubusercontent.com/u/3736271?v=4","gravatar_id":"","url":"https://api.github.com/users/cytadela8","html_url":"https://github.com/cytadela8","followers_url":"https://api.github.com/users/cytadela8/followers","following_url":"https://api.github.com/users/cytadela8/following{/other_user}","gists_url":"https://api.github.com/users/cytadela8/gists{/gist_id}","starred_url":"https://api.github.com/users/cytadela8/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cytadela8/subscriptions","organizations_url":"https://api.github.com/users/cytadela8/orgs","repos_url":"https://api.github.com/users/cytadela8/repos","events_url":"https://api.github.com/users/cytadela8/events{/privacy}","received_events_url":"https://api.github.com/users/cytadela8/received_events","type":"User","site_admin":false},{"login":"nedelcho-delchev-tues","id":5245055,"node_id":"MDQ6VXNlcjUyNDUwNTU=","avatar_url":"https://avatars3.githubusercontent.com/u/5245055?v=4","gravatar_id":"","url":"https://api.github.com/users/nedelcho-delchev-tues","html_url":"https://github.com/nedelcho-delchev-tues","followers_url":"https://api.github.com/users/nedelcho-delchev-tues/followers","following_url":"https://api.github.com/users/nedelcho-delchev-tues/following{/other_user}","gists_url":"https://api.github.com/users/nedelcho-delchev-tues/gists{/gist_id}","starred_url":"https://api.github.com/users/nedelcho-delchev-tues/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nedelcho-delchev-tues/subscriptions","organizations_url":"https://api.github.com/users/nedelcho-delchev-tues/orgs","repos_url":"https://api.github.com/users/nedelcho-delchev-tues/repos","events_url":"https://api.github.com/users/nedelcho-delchev-tues/events{/privacy}","received_events_url":"https://api.github.com/users/nedelcho-delchev-tues/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":686148098,"node_id":"MDU6TGFiZWw2ODYxNDgwOTg=","url":"https://api.github.com/repos/aeternity/elixir-node/labels/enhancement","name":"enhancement","color":"84b6eb","default":true},{"id":860555733,"node_id":"MDU6TGFiZWw4NjA1NTU3MzM=","url":"https://api.github.com/repos/aeternity/elixir-node/labels/spec-adjust","name":"spec-adjust","color":"e89561","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/aeternity/elixir-node/pulls/567/commits","review_comments_url":"https://api.github.com/repos/aeternity/elixir-node/pulls/567/comments","review_comment_url":"https://api.github.com/repos/aeternity/elixir-node/pulls/comments{/number}","comments_url":"https://api.github.com/repos/aeternity/elixir-node/issues/567/comments","statuses_url":"https://api.github.com/repos/aeternity/elixir-node/statuses/10485e3bb6c5f9e83512ac1e9bf8d6a4ff27fc5e","head":{"label":"aeternity:GH-524","ref":"GH-524","sha":"10485e3bb6c5f9e83512ac1e9bf8d6a4ff27fc5e","user":{"login":"aeternity","id":21989234,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIxOTg5MjM0","avatar_url":"https://avatars1.githubusercontent.com/u/21989234?v=4","gravatar_id":"","url":"https://api.github.com/users/aeternity","html_url":"https://github.com/aeternity","followers_url":"https://api.github.com/users/aeternity/followers","following_url":"https://api.github.com/users/aeternity/following{/other_user}","gists_url":"https://api.github.com/users/aeternity/gists{/gist_id}","starred_url":"https://api.github.com/users/aeternity/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aeternity/subscriptions","organizations_url":"https://api.github.com/users/aeternity/orgs","repos_url":"https://api.github.com/users/aeternity/repos","events_url":"https://api.github.com/users/aeternity/events{/privacy}","received_events_url":"https://api.github.com/users/aeternity/received_events","type":"Organization","site_admin":false},"repo":{"id":102751238,"node_id":"MDEwOlJlcG9zaXRvcnkxMDI3NTEyMzg=","name":"elixir-node","full_name":"aeternity/elixir-node","owner":{"login":"aeternity","id":21989234,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIxOTg5MjM0","avatar_url":"https://avatars1.githubusercontent.com/u/21989234?v=4","gravatar_id":"","url":"https://api.github.com/users/aeternity","html_url":"https://github.com/aeternity","followers_url":"https://api.github.com/users/aeternity/followers","following_url":"https://api.github.com/users/aeternity/following{/other_user}","gists_url":"https://api.github.com/users/aeternity/gists{/gist_id}","starred_url":"https://api.github.com/users/aeternity/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aeternity/subscriptions","organizations_url":"https://api.github.com/users/aeternity/orgs","repos_url":"https://api.github.com/users/aeternity/repos","events_url":"https://api.github.com/users/aeternity/events{/privacy}","received_events_url":"https://api.github.com/users/aeternity/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/aeternity/elixir-node","description":"Elixir full node implementation of the aeternity specification","fork":false,"url":"https://api.github.com/repos/aeternity/elixir-node","forks_url":"https://api.github.com/repos/aeternity/elixir-node/forks","keys_url":"https://api.github.com/repos/aeternity/elixir-node/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aeternity/elixir-node/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aeternity/elixir-node/teams","hooks_url":"https://api.github.com/repos/aeternity/elixir-node/hooks","issue_events_url":"https://api.github.com/repos/aeternity/elixir-node/issues/events{/number}","events_url":"https://api.github.com/repos/aeternity/elixir-node/events","assignees_url":"https://api.github.com/repos/aeternity/elixir-node/assignees{/user}","branches_url":"https://api.github.com/repos/aeternity/elixir-node/branches{/branch}","tags_url":"https://api.github.com/repos/aeternity/elixir-node/tags","blobs_url":"https://api.github.com/repos/aeternity/elixir-node/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aeternity/elixir-node/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aeternity/elixir-node/git/refs{/sha}","trees_url":"https://api.github.com/repos/aeternity/elixir-node/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aeternity/elixir-node/statuses/{sha}","languages_url":"https://api.github.com/repos/aeternity/elixir-node/languages","stargazers_url":"https://api.github.com/repos/aeternity/elixir-node/stargazers","contributors_url":"https://api.github.com/repos/aeternity/elixir-node/contributors","subscribers_url":"https://api.github.com/repos/aeternity/elixir-node/subscribers","subscription_url":"https://api.github.com/repos/aeternity/elixir-node/subscription","commits_url":"https://api.github.com/repos/aeternity/elixir-node/commits{/sha}","git_commits_url":"https://api.github.com/repos/aeternity/elixir-node/git/commits{/sha}","comments_url":"https://api.github.com/repos/aeternity/elixir-node/comments{/number}","issue_comment_url":"https://api.github.com/repos/aeternity/elixir-node/issues/comments{/number}","contents_url":"https://api.github.com/repos/aeternity/elixir-node/contents/{+path}","compare_url":"https://api.github.com/repos/aeternity/elixir-node/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aeternity/elixir-node/merges","archive_url":"https://api.github.com/repos/aeternity/elixir-node/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aeternity/elixir-node/downloads","issues_url":"https://api.github.com/repos/aeternity/elixir-node/issues{/number}","pulls_url":"https://api.github.com/repos/aeternity/elixir-node/pulls{/number}","milestones_url":"https://api.github.com/repos/aeternity/elixir-node/milestones{/number}","notifications_url":"https://api.github.com/repos/aeternity/elixir-node/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aeternity/elixir-node/labels{/name}","releases_url":"https://api.github.com/repos/aeternity/elixir-node/releases{/id}","deployments_url":"https://api.github.com/repos/aeternity/elixir-node/deployments","created_at":"2017-09-07T15:04:18Z","updated_at":"2018-08-31T14:15:24Z","pushed_at":"2018-08-31T14:15:22Z","git_url":"git://github.com/aeternity/elixir-node.git","ssh_url":"git@github.com:aeternity/elixir-node.git","clone_url":"https://github.com/aeternity/elixir-node.git","svn_url":"https://github.com/aeternity/elixir-node","homepage":"","size":4249,"stargazers_count":177,"watchers_count":177,"language":"Elixir","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":33,"mirror_url":null,"archived":false,"open_issues_count":25,"license":{"key":"isc","name":"ISC License","spdx_id":"ISC","url":"https://api.github.com/licenses/isc","node_id":"MDc6TGljZW5zZTEw"},"forks":33,"open_issues":25,"watchers":177,"default_branch":"master"}},"base":{"label":"aeternity:master","ref":"master","sha":"60737bac9ae75f007f7f69b569e9a9f3c621c2bd","user":{"login":"aeternity","id":21989234,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIxOTg5MjM0","avatar_url":"https://avatars1.githubusercontent.com/u/21989234?v=4","gravatar_id":"","url":"https://api.github.com/users/aeternity","html_url":"https://github.com/aeternity","followers_url":"https://api.github.com/users/aeternity/followers","following_url":"https://api.github.com/users/aeternity/following{/other_user}","gists_url":"https://api.github.com/users/aeternity/gists{/gist_id}","starred_url":"https://api.github.com/users/aeternity/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aeternity/subscriptions","organizations_url":"https://api.github.com/users/aeternity/orgs","repos_url":"https://api.github.com/users/aeternity/repos","events_url":"https://api.github.com/users/aeternity/events{/privacy}","received_events_url":"https://api.github.com/users/aeternity/received_events","type":"Organization","site_admin":false},"repo":{"id":102751238,"node_id":"MDEwOlJlcG9zaXRvcnkxMDI3NTEyMzg=","name":"elixir-node","full_name":"aeternity/elixir-node","owner":{"login":"aeternity","id":21989234,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIxOTg5MjM0","avatar_url":"https://avatars1.githubusercontent.com/u/21989234?v=4","gravatar_id":"","url":"https://api.github.com/users/aeternity","html_url":"https://github.com/aeternity","followers_url":"https://api.github.com/users/aeternity/followers","following_url":"https://api.github.com/users/aeternity/following{/other_user}","gists_url":"https://api.github.com/users/aeternity/gists{/gist_id}","starred_url":"https://api.github.com/users/aeternity/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aeternity/subscriptions","organizations_url":"https://api.github.com/users/aeternity/orgs","repos_url":"https://api.github.com/users/aeternity/repos","events_url":"https://api.github.com/users/aeternity/events{/privacy}","received_events_url":"https://api.github.com/users/aeternity/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/aeternity/elixir-node","description":"Elixir full node implementation of the aeternity specification","fork":false,"url":"https://api.github.com/repos/aeternity/elixir-node","forks_url":"https://api.github.com/repos/aeternity/elixir-node/forks","keys_url":"https://api.github.com/repos/aeternity/elixir-node/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aeternity/elixir-node/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aeternity/elixir-node/teams","hooks_url":"https://api.github.com/repos/aeternity/elixir-node/hooks","issue_events_url":"https://api.github.com/repos/aeternity/elixir-node/issues/events{/number}","events_url":"https://api.github.com/repos/aeternity/elixir-node/events","assignees_url":"https://api.github.com/repos/aeternity/elixir-node/assignees{/user}","branches_url":"https://api.github.com/repos/aeternity/elixir-node/branches{/branch}","tags_url":"https://api.github.com/repos/aeternity/elixir-node/tags","blobs_url":"https://api.github.com/repos/aeternity/elixir-node/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aeternity/elixir-node/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aeternity/elixir-node/git/refs{/sha}","trees_url":"https://api.github.com/repos/aeternity/elixir-node/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aeternity/elixir-node/statuses/{sha}","languages_url":"https://api.github.com/repos/aeternity/elixir-node/languages","stargazers_url":"https://api.github.com/repos/aeternity/elixir-node/stargazers","contributors_url":"https://api.github.com/repos/aeternity/elixir-node/contributors","subscribers_url":"https://api.github.com/repos/aeternity/elixir-node/subscribers","subscription_url":"https://api.github.com/repos/aeternity/elixir-node/subscription","commits_url":"https://api.github.com/repos/aeternity/elixir-node/commits{/sha}","git_commits_url":"https://api.github.com/repos/aeternity/elixir-node/git/commits{/sha}","comments_url":"https://api.github.com/repos/aeternity/elixir-node/comments{/number}","issue_comment_url":"https://api.github.com/repos/aeternity/elixir-node/issues/comments{/number}","contents_url":"https://api.github.com/repos/aeternity/elixir-node/contents/{+path}","compare_url":"https://api.github.com/repos/aeternity/elixir-node/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aeternity/elixir-node/merges","archive_url":"https://api.github.com/repos/aeternity/elixir-node/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aeternity/elixir-node/downloads","issues_url":"https://api.github.com/repos/aeternity/elixir-node/issues{/number}","pulls_url":"https://api.github.com/repos/aeternity/elixir-node/pulls{/number}","milestones_url":"https://api.github.com/repos/aeternity/elixir-node/milestones{/number}","notifications_url":"https://api.github.com/repos/aeternity/elixir-node/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aeternity/elixir-node/labels{/name}","releases_url":"https://api.github.com/repos/aeternity/elixir-node/releases{/id}","deployments_url":"https://api.github.com/repos/aeternity/elixir-node/deployments","created_at":"2017-09-07T15:04:18Z","updated_at":"2018-08-31T14:15:24Z","pushed_at":"2018-08-31T14:15:22Z","git_url":"git://github.com/aeternity/elixir-node.git","ssh_url":"git@github.com:aeternity/elixir-node.git","clone_url":"https://github.com/aeternity/elixir-node.git","svn_url":"https://github.com/aeternity/elixir-node","homepage":"","size":4249,"stargazers_count":177,"watchers_count":177,"language":"Elixir","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":33,"mirror_url":null,"archived":false,"open_issues_count":25,"license":{"key":"isc","name":"ISC License","spdx_id":"ISC","url":"https://api.github.com/licenses/isc","node_id":"MDc6TGljZW5zZTEw"},"forks":33,"open_issues":25,"watchers":177,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/aeternity/elixir-node/pulls/567"},"html":{"href":"https://github.com/aeternity/elixir-node/pull/567"},"issue":{"href":"https://api.github.com/repos/aeternity/elixir-node/issues/567"},"comments":{"href":"https://api.github.com/repos/aeternity/elixir-node/issues/567/comments"},"review_comments":{"href":"https://api.github.com/repos/aeternity/elixir-node/pulls/567/comments"},"review_comment":{"href":"https://api.github.com/repos/aeternity/elixir-node/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/aeternity/elixir-node/pulls/567/commits"},"statuses":{"href":"https://api.github.com/repos/aeternity/elixir-node/statuses/10485e3bb6c5f9e83512ac1e9bf8d6a4ff27fc5e"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"grzegorz225"}}