{"action":"created","comment":{"url":"https://api.github.com/repos/ethereum/go-ethereum/pulls/comments/41773765","id":41773765,"diff_hunk":"@@ -210,96 +310,160 @@ func (q *queue) Insert(headers []*types.Header, from uint64) []*types.Header {\n \t\t\tglog.V(logger.Warn).Infof(\"Header #%v [%x] broke chain ancestry\", header.Number, hash[:4])\n \t\t\tbreak\n \t\t}\n-\t\t// Queue the header for body retrieval\n+\t\t// Make sure no duplicate requests are executed\n+\t\tif _, ok := q.blockTaskPool[hash]; ok {\n+\t\t\tglog.V(logger.Warn).Infof(\"Header #%d [%x] already scheduled for block fetch\", header.Number.Uint64(), hash[:4])\n+\t\t\tcontinue\n+\t\t}\n+\t\tif _, ok := q.receiptTaskPool[hash]; ok {\n+\t\t\tglog.V(logger.Warn).Infof(\"Header #%d [%x] already scheduled for receipt fetch\", header.Number.Uint64(), hash[:4])\n+\t\t\tcontinue\n+\t\t}\n+\t\t// Queue the header for content retrieval\n+\t\tq.blockTaskPool[hash] = header\n+\t\tq.blockTaskQueue.Push(header, -float32(header.Number.Uint64()))\n+\n+\t\tif q.mode == FastSync && header.Number.Uint64() <= q.fastSyncPivot {\n+\t\t\t// Fast phase of the fast sync, retrieve receipts too\n+\t\t\tq.receiptTaskPool[hash] = header\n+\t\t\tq.receiptTaskQueue.Push(header, -float32(header.Number.Uint64()))\n+\t\t}\n+\t\tif q.mode == FastSync && header.Number.Uint64() == q.fastSyncPivot {\n+\t\t\t// Pivoting point of the fast sync, retrieve the state tries\n+\t\t\tq.stateSchedLock.Lock()\n+\t\t\tq.stateScheduler = state.NewStateSync(header.Root, q.stateDatabase)\n+\t\t\tq.stateSchedLock.Unlock()\n+\t\t}\n \t\tinserts = append(inserts, header)\n-\t\tq.headerPool[hash] = header\n-\t\tq.headerQueue.Push(header, -float32(header.Number.Uint64()))\n \t\tq.headerHead = hash\n \t\tfrom++\n \t}\n \treturn inserts\n }\n \n-// GetHeadBlock retrieves the first block from the cache, or nil if it hasn't\n+// GetHeadResult retrieves the first fetch result from the cache, or nil if it hasn't\n // been downloaded yet (or simply non existent).\n-func (q *queue) GetHeadBlock() *Block {\n+func (q *queue) GetHeadResult() *fetchResult {\n \tq.lock.RLock()\n \tdefer q.lock.RUnlock()\n \n-\tif len(q.blockCache) == 0 {\n+\tif len(q.resultCache) == 0 || q.resultCache[0] == nil {\n \t\treturn nil\n \t}\n-\treturn q.blockCache[0]\n-}\n-\n-// GetBlock retrieves a downloaded block, or nil if non-existent.\n-func (q *queue) GetBlock(hash common.Hash) *Block {\n-\tq.lock.RLock()\n-\tdefer q.lock.RUnlock()\n-\n-\t// Short circuit if the block hasn't been downloaded yet\n-\tindex, ok := q.blockPool[hash]\n-\tif !ok {\n+\tif q.resultCache[0].Pending > 0 {\n \t\treturn nil\n \t}\n-\t// Return the block if it's still available in the cache\n-\tif q.blockOffset <= index && index < q.blockOffset+uint64(len(q.blockCache)) {\n-\t\treturn q.blockCache[index-q.blockOffset]\n+\tif q.mode == FastSync && q.resultCache[0].Header.Number.Uint64() == q.fastSyncPivot {\n+\t\tif len(q.stateTaskPool) > 0 {\n+\t\t\treturn nil\n+\t\t}\n+\t\tif q.PendingNodeData() > 0 {\n+\t\t\treturn nil\n+\t\t}\n \t}\n-\treturn nil\n+\treturn q.resultCache[0]\n }\n \n-// TakeBlocks retrieves and permanently removes a batch of blocks from the cache.\n-func (q *queue) TakeBlocks() []*Block {\n+// TakeResults retrieves and permanently removes a batch of fetch results from\n+// the cache.\n+func (q *queue) TakeResults() []*fetchResult {\n \tq.lock.Lock()\n \tdefer q.lock.Unlock()\n \n-\t// Accumulate all available blocks\n-\tblocks := []*Block{}\n-\tfor _, block := range q.blockCache {\n-\t\tif block == nil {\n+\t// Accumulate all available results\n+\tresults := []*fetchResult{}\n+\tfor _, result := range q.resultCache {\n+\t\t// Stop if no more results are ready\n+\t\tif result == nil || result.Pending > 0 {\n+\t\t\tbreak\n+\t\t}\n+\t\t// The fast sync pivot block may only be processed after state fetch completes\n+\t\tif q.mode == FastSync && result.Header.Number.Uint64() == q.fastSyncPivot {\n+\t\t\tif len(q.stateTaskPool) > 0 {\n+\t\t\t\tbreak\n+\t\t\t}\n+\t\t\tif q.PendingNodeData() > 0 {\n+\t\t\t\tbreak\n+\t\t\t}\n+\t\t}\n+\t\t// If we've just inserted the fast sync pivot, stop as the following batch needs different insertion\n+\t\tif q.mode == FastSync && result.Header.Number.Uint64() == q.fastSyncPivot+1 && len(results) > 0 {\n \t\t\tbreak\n \t\t}\n-\t\tblocks = append(blocks, block)\n-\t\tdelete(q.blockPool, block.RawBlock.Hash())\n+\t\tresults = append(results, result)\n+\n+\t\thash := result.Header.Hash()\n+\t\tdelete(q.blockDonePool, hash)\n+\t\tdelete(q.receiptDonePool, hash)\n \t}\n-\t// Delete the blocks from the slice and let them be garbage collected\n-\t// without this slice trick the blocks would stay in memory until nil\n-\t// would be assigned to q.blocks\n-\tcopy(q.blockCache, q.blockCache[len(blocks):])\n-\tfor k, n := len(q.blockCache)-len(blocks), len(q.blockCache); k < n; k++ {\n-\t\tq.blockCache[k] = nil\n+\t// Delete the results from the slice and let them be garbage collected\n+\t// without this slice trick the results would stay in memory until nil\n+\t// would be assigned to them.\n+\tcopy(q.resultCache, q.resultCache[len(results):])\n+\tfor k, n := len(q.resultCache)-len(results), len(q.resultCache); k < n; k++ {\n+\t\tq.resultCache[k] = nil\n \t}\n-\tq.blockOffset += uint64(len(blocks))\n+\tq.resultOffset += uint64(len(results))\n \n-\treturn blocks\n+\treturn results\n+}\n+\n+// ReserveBlocks reserves a set of block hashes for the given peer, skipping any\n+// previously failed download.\n+func (q *queue) ReserveBlocks(p *peer, count int) *fetchRequest {\n+\treturn q.reserveHashes(p, count, q.hashQueue, nil, q.blockPendPool, len(q.resultCache)-len(q.blockDonePool))\n+}\n+\n+// ReserveNodeData reserves a set of node data hashes for the given peer, skipping\n+// any previously failed download.\n+func (q *queue) ReserveNodeData(p *peer, count int) *fetchRequest {\n+\t// Create a task generator to fetch status-fetch tasks if all schedules ones are done\n+\tgenerator := func(max int) {\n+\t\tq.stateSchedLock.Lock()\n+\t\tdefer q.stateSchedLock.Unlock()\n+\n+\t\tif q.stateScheduler != nil {\n+\t\t\tfor _, hash := range q.stateScheduler.Missing(max) {\n+\t\t\t\tq.stateTaskPool[hash] = q.stateTaskIndex\n+\t\t\t\tq.stateTaskQueue.Push(hash, -float32(q.stateTaskIndex))\n+\t\t\t\tq.stateTaskIndex++\n+\t\t\t}\n+\t\t}\n+\t}\n+\treturn q.reserveHashes(p, count, q.stateTaskQueue, generator, q.statePendPool, count)\n }\n \n-// Reserve61 reserves a set of hashes for the given peer, skipping any previously\n-// failed download.\n-func (q *queue) Reserve61(p *peer, count int) *fetchRequest {\n+// reserveHashes reserves a set of hashes for the given peer, skipping previously\n+// failed ones.\n+func (q *queue) reserveHashes(p *peer, count int, taskQueue *prque.Prque, taskGen func(int), pendPool map[string]*fetchRequest, maxPending int) *fetchRequest {\n \tq.lock.Lock()\n \tdefer q.lock.Unlock()\n \n-\t// Short circuit if the pool has been depleted, or if the peer's already\n-\t// downloading something (sanity check not to corrupt state)\n-\tif q.hashQueue.Empty() {\n-\t\treturn nil\n-\t}\n-\tif _, ok := q.pendPool[p.id]; ok {\n+\t// Short circuit if the peer's already downloading something (sanity check not","path":"eth/downloader/queue.go","position":507,"original_position":507,"commit_id":"4fc1e1a914f90ce1d4b84e01629b14868c51f5e2","original_commit_id":"4fc1e1a914f90ce1d4b84e01629b14868c51f5e2","user":{"login":"Gustav-Simonsson","id":293096,"avatar_url":"https://avatars.githubusercontent.com/u/293096?v=3","gravatar_id":"","url":"https://api.github.com/users/Gustav-Simonsson","html_url":"https://github.com/Gustav-Simonsson","followers_url":"https://api.github.com/users/Gustav-Simonsson/followers","following_url":"https://api.github.com/users/Gustav-Simonsson/following{/other_user}","gists_url":"https://api.github.com/users/Gustav-Simonsson/gists{/gist_id}","starred_url":"https://api.github.com/users/Gustav-Simonsson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Gustav-Simonsson/subscriptions","organizations_url":"https://api.github.com/users/Gustav-Simonsson/orgs","repos_url":"https://api.github.com/users/Gustav-Simonsson/repos","events_url":"https://api.github.com/users/Gustav-Simonsson/events{/privacy}","received_events_url":"https://api.github.com/users/Gustav-Simonsson/received_events","type":"User","site_admin":false},"body":"to not","created_at":"2015-10-12T16:20:48Z","updated_at":"2015-10-12T16:20:48Z","html_url":"https://github.com/ethereum/go-ethereum/pull/1889#discussion_r41773765","pull_request_url":"https://api.github.com/repos/ethereum/go-ethereum/pulls/1889","_links":{"self":{"href":"https://api.github.com/repos/ethereum/go-ethereum/pulls/comments/41773765"},"html":{"href":"https://github.com/ethereum/go-ethereum/pull/1889#discussion_r41773765"},"pull_request":{"href":"https://api.github.com/repos/ethereum/go-ethereum/pulls/1889"}}},"pull_request":{"url":"https://api.github.com/repos/ethereum/go-ethereum/pulls/1889","id":47241762,"html_url":"https://github.com/ethereum/go-ethereum/pull/1889","diff_url":"https://github.com/ethereum/go-ethereum/pull/1889.diff","patch_url":"https://github.com/ethereum/go-ethereum/pull/1889.patch","issue_url":"https://api.github.com/repos/ethereum/go-ethereum/issues/1889","number":1889,"state":"open","locked":false,"title":"eth/63 fast synchronization algorithm","user":{"login":"karalabe","id":129561,"avatar_url":"https://avatars.githubusercontent.com/u/129561?v=3","gravatar_id":"","url":"https://api.github.com/users/karalabe","html_url":"https://github.com/karalabe","followers_url":"https://api.github.com/users/karalabe/followers","following_url":"https://api.github.com/users/karalabe/following{/other_user}","gists_url":"https://api.github.com/users/karalabe/gists{/gist_id}","starred_url":"https://api.github.com/users/karalabe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/karalabe/subscriptions","organizations_url":"https://api.github.com/users/karalabe/orgs","repos_url":"https://api.github.com/users/karalabe/repos","events_url":"https://api.github.com/users/karalabe/events{/privacy}","received_events_url":"https://api.github.com/users/karalabe/received_events","type":"User","site_admin":false},"body":"This PR aggregates a lot of small modifications to `core`, `trie`, `eth` and other packages to collectively implement the eth/63 fast synchronization algorithm. In short, `geth --fast`.\r\n\r\n### Algorithm\r\n\r\nThe goal of the the fast sync algorithm is to exchange processing power for bandwidth usage. *Instead of processing the entire block-chain one link at a time, and replay all transactions that ever happened in history, fast syncing downloads the transaction receipts along the blocks, and pulls an entire recent state database.* This allows a fast synced node to still retain its status an an archive node containing all historical data for user queries (and thus not influence the network's health in general), but at the same time to reassemble a recent network state at a fraction of the time it would take full block processing.\r\n\r\nAn outline of the fast sync algorithm would be:\r\n * Similarly to classical sync, download the block headers and bodies that make up the blockchain\r\n * Similarly to classical sync, verify the header chain's consistency (POW, total difficulty, etc)\r\n * Instead of processing the blocks, download the transaction receipts as defined by the header\r\n * Store the downloaded blockchain, along with the receipt chain, enabling all historical queries\r\n * When the chain reaches a recent enough state (head - 1024 blocks), pause for state sync:\r\n   * Retrieve the entire Merkel Patricia state trie defined by the root hash of the pivot point\r\n   * For every account found in the trie, retrieve it's contract code and internal storage state trie\r\n * Upon successful trie download, mark the pivot point (head - 1024 blocks) as the current head\r\n * Import all remaining blocks (1024) by fully processing them as in the classical sync\r\n\r\n### Analysis\r\n\r\nBy downloading and verifying the entire header chain, we can guarantee with all the security of the classical sync, that the hashes (receipts, state tries, etc) contained within the headers are valid. Based on those hashes, we can confidently download transaction receipts and the entire state trie afterwards, maintaining the guarantee that it's impossible to forge invalid data. Additionally, by placing the pivoting point (where fast sync switches to block processing) a bit below the current head (1024 blocks), we can ensure that even larger chain reorganizations can be handled without the need of a new sync (as we have all the state going that many blocks back).\r\n\r\n### Caveats\r\n\r\nThe historical block-processing based synchronization mechanism has two (approximately similarly costing) bottlenecks: *transaction processing* and *PoW verification*. The baseline fast sync algorithm successfully circumvents the transaction processing, skipping the need to iterate over every single state the system ever was in. However, verifying the proof of work associated with each header is still a notably CPU intensive operation.\r\n\r\nHowever, we can notice an interesting phenomenon during header verification. With a negligible probability of error, we can still guarantee the validity of the chain, only by verifying every `K`-th header, instead of each and every one. By selecting a single header at random out of every `K` headers to verify, we guarantee the validity of an `N`-length chain with the probability of `(1/K)^(N/K)` (i.e. we have `1/K` chance to spot a forgery in `K` blocks, a verification that's repeated `N/K` times).\r\n\r\nLet's define the *negligible probability* `Pn` as the probability of obtaining a 256 bit SHA3 collision (i.e. the hash Ethereum is built upon): `1/2^128`. To honor the Ethereum security requirements, we need to choose the minimum chain length `N` (below which we veriy every header) and maximum `K` verification batch size such as `(1/K)^(N/K) <= Pn` holds. Calculating this for various `{N, K}` pairs is pretty straighforward, a simple and lenient solution being http://play.golang.org/p/B-8sX_6Dq0.\r\n\r\n| N    | K   |     | N    | K   |     | N    | K   |     | N    | K   |\r\n| ---- | --- | --- | ---- | --- | --- | ---- | --- | --- | ---- | --- |\r\n| 1024 |  43 |     | 1792 |  91 |     | 2560 | 143 |     | 3328 | 198 |\r\n| 1152 |  51 |     | 1920 |  99 |     | 2688 | 152 |     | 3456 | 207 |\r\n| 1280 |  58 |     | 2048 | 108 |     | 2816 | 161 |     | 3584 | 217 |\r\n| 1408 |  66 |     | 2176 | 116 |     | 2944 | 170 |     | 3712 | 226 |\r\n| 1536 |  74 |     | 2304 | 128 |     | 3072 | 179 |     | 3840 | 236 |\r\n| 1664 |  82 |     | 2432 | 134 |     | 3200 | 189 |     | 3968 | 246 |\r\n\r\nThe above table should be interpreted in such a way, that if we verify every `K`-th header, after `N` headers the probability of a forgery is smaller than the probability of an attacker producing a SHA3 collision. Any `{N, K}` pair may be chosen from the above table, the optimal solution being one that minimizes the overall number of headers verified `(chain_size - N)/K + N`. A pristine sync from the genesis block would curently be `(~356000 - N)/K + N`, requiring around a miminum of 5000 header verifications, with K being in the +- 128 range. To keep the numbers reasonably looking, we chose `N=2048, K=100`.\r\n\r\nWith this caveat calculated, the fast sync should be modified so that up to the pivoting point `N=2048`, only every `K=100`-th header should be verified (**at random**), after which the remaining `N` headers should be fully verified before starting state database downloading. **Note: if a sync fails due to header verification the last `N` headers must be discarded as they cannot be trusted enough.**\r\n\r\n### Performance\r\n\r\nTo benchmark the performance of the new algorithm, four separate tests were run: full syncing from scrath on Frontier and Olympic, using both the classical sync as well as the new sync mechanism. In all scenarios there were two nodes running on a single machine: a seed node featuring a fully synced database, and a leech node with only the genesis block pulling the data. In all test scenarios the seed node had a fast-synced database (smaller, less disk contention) and both nodes were given 1GB database cache (`--cache=1024`).\r\n\r\nThe machine running the tests was a Zenbook Pro, Core i7 4720HQ, 12GB RAM, 256GB m.2 SSD, Ubuntu 15.04.\r\n\r\n|       Dataset (blocks, states)        |    Normal sync     |     Fast sync       |\r\n| ------------------------------------- | ------------------ | ------------------- |\r\n| Frontier, 357677 blocks, 42.4K states | 12:21 mins, 1.6 GB | 2:49 mins, 235.2 MB |\r\n| Olympic, 837869 blocks, 10.2M states  |                    | 31:32 mins, 3.8 GB  |\r\n\r\nThe resulting databases contain the entire blockchain (all blocks, all uncles, all transactions), every transaction receipt and generated logs, and the entire state trie of the head 1024 blocks. This allows a fast synced node to act as a full archive node from all intents and purposes.\r\n\r\n### Closing remarks\r\n\r\nThe fast sync algorithm requires the functionality defined by eth/63. Because of this, testing in the live network requires for at least a handful of discoverable peers to update their nodes to eth/63. On the same note, verifying that the implementation is truly correct will also entail waiting for the wider deployment of eth/63.","created_at":"2015-10-09T07:30:48Z","updated_at":"2015-10-12T16:20:48Z","closed_at":null,"merged_at":null,"merge_commit_sha":"cc01e519c5675ab6252013c331c0eb4d88551217","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/ethereum/go-ethereum/pulls/1889/commits","review_comments_url":"https://api.github.com/repos/ethereum/go-ethereum/pulls/1889/comments","review_comment_url":"https://api.github.com/repos/ethereum/go-ethereum/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ethereum/go-ethereum/issues/1889/comments","statuses_url":"https://api.github.com/repos/ethereum/go-ethereum/statuses/4fc1e1a914f90ce1d4b84e01629b14868c51f5e2","head":{"label":"karalabe:fast-sync-rebase","ref":"fast-sync-rebase","sha":"4fc1e1a914f90ce1d4b84e01629b14868c51f5e2","user":{"login":"karalabe","id":129561,"avatar_url":"https://avatars.githubusercontent.com/u/129561?v=3","gravatar_id":"","url":"https://api.github.com/users/karalabe","html_url":"https://github.com/karalabe","followers_url":"https://api.github.com/users/karalabe/followers","following_url":"https://api.github.com/users/karalabe/following{/other_user}","gists_url":"https://api.github.com/users/karalabe/gists{/gist_id}","starred_url":"https://api.github.com/users/karalabe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/karalabe/subscriptions","organizations_url":"https://api.github.com/users/karalabe/orgs","repos_url":"https://api.github.com/users/karalabe/repos","events_url":"https://api.github.com/users/karalabe/events{/privacy}","received_events_url":"https://api.github.com/users/karalabe/received_events","type":"User","site_admin":false},"repo":{"id":33668567,"name":"go-ethereum","full_name":"karalabe/go-ethereum","owner":{"login":"karalabe","id":129561,"avatar_url":"https://avatars.githubusercontent.com/u/129561?v=3","gravatar_id":"","url":"https://api.github.com/users/karalabe","html_url":"https://github.com/karalabe","followers_url":"https://api.github.com/users/karalabe/followers","following_url":"https://api.github.com/users/karalabe/following{/other_user}","gists_url":"https://api.github.com/users/karalabe/gists{/gist_id}","starred_url":"https://api.github.com/users/karalabe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/karalabe/subscriptions","organizations_url":"https://api.github.com/users/karalabe/orgs","repos_url":"https://api.github.com/users/karalabe/repos","events_url":"https://api.github.com/users/karalabe/events{/privacy}","received_events_url":"https://api.github.com/users/karalabe/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/karalabe/go-ethereum","description":"Jeffrey Wilcke's Go implementation of the Ethereum y/w paper","fork":true,"url":"https://api.github.com/repos/karalabe/go-ethereum","forks_url":"https://api.github.com/repos/karalabe/go-ethereum/forks","keys_url":"https://api.github.com/repos/karalabe/go-ethereum/keys{/key_id}","collaborators_url":"https://api.github.com/repos/karalabe/go-ethereum/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/karalabe/go-ethereum/teams","hooks_url":"https://api.github.com/repos/karalabe/go-ethereum/hooks","issue_events_url":"https://api.github.com/repos/karalabe/go-ethereum/issues/events{/number}","events_url":"https://api.github.com/repos/karalabe/go-ethereum/events","assignees_url":"https://api.github.com/repos/karalabe/go-ethereum/assignees{/user}","branches_url":"https://api.github.com/repos/karalabe/go-ethereum/branches{/branch}","tags_url":"https://api.github.com/repos/karalabe/go-ethereum/tags","blobs_url":"https://api.github.com/repos/karalabe/go-ethereum/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/karalabe/go-ethereum/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/karalabe/go-ethereum/git/refs{/sha}","trees_url":"https://api.github.com/repos/karalabe/go-ethereum/git/trees{/sha}","statuses_url":"https://api.github.com/repos/karalabe/go-ethereum/statuses/{sha}","languages_url":"https://api.github.com/repos/karalabe/go-ethereum/languages","stargazers_url":"https://api.github.com/repos/karalabe/go-ethereum/stargazers","contributors_url":"https://api.github.com/repos/karalabe/go-ethereum/contributors","subscribers_url":"https://api.github.com/repos/karalabe/go-ethereum/subscribers","subscription_url":"https://api.github.com/repos/karalabe/go-ethereum/subscription","commits_url":"https://api.github.com/repos/karalabe/go-ethereum/commits{/sha}","git_commits_url":"https://api.github.com/repos/karalabe/go-ethereum/git/commits{/sha}","comments_url":"https://api.github.com/repos/karalabe/go-ethereum/comments{/number}","issue_comment_url":"https://api.github.com/repos/karalabe/go-ethereum/issues/comments{/number}","contents_url":"https://api.github.com/repos/karalabe/go-ethereum/contents/{+path}","compare_url":"https://api.github.com/repos/karalabe/go-ethereum/compare/{base}...{head}","merges_url":"https://api.github.com/repos/karalabe/go-ethereum/merges","archive_url":"https://api.github.com/repos/karalabe/go-ethereum/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/karalabe/go-ethereum/downloads","issues_url":"https://api.github.com/repos/karalabe/go-ethereum/issues{/number}","pulls_url":"https://api.github.com/repos/karalabe/go-ethereum/pulls{/number}","milestones_url":"https://api.github.com/repos/karalabe/go-ethereum/milestones{/number}","notifications_url":"https://api.github.com/repos/karalabe/go-ethereum/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/karalabe/go-ethereum/labels{/name}","releases_url":"https://api.github.com/repos/karalabe/go-ethereum/releases{/id}","created_at":"2015-04-09T12:51:45Z","updated_at":"2015-04-17T13:40:48Z","pushed_at":"2015-10-12T13:22:12Z","git_url":"git://github.com/karalabe/go-ethereum.git","ssh_url":"git@github.com:karalabe/go-ethereum.git","clone_url":"https://github.com/karalabe/go-ethereum.git","svn_url":"https://github.com/karalabe/go-ethereum","homepage":"","size":49762,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"ethereum:develop","ref":"develop","sha":"315a422ba754eae10db21990a809f608f7af62d4","user":{"login":"ethereum","id":6250754,"avatar_url":"https://avatars.githubusercontent.com/u/6250754?v=3","gravatar_id":"","url":"https://api.github.com/users/ethereum","html_url":"https://github.com/ethereum","followers_url":"https://api.github.com/users/ethereum/followers","following_url":"https://api.github.com/users/ethereum/following{/other_user}","gists_url":"https://api.github.com/users/ethereum/gists{/gist_id}","starred_url":"https://api.github.com/users/ethereum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ethereum/subscriptions","organizations_url":"https://api.github.com/users/ethereum/orgs","repos_url":"https://api.github.com/users/ethereum/repos","events_url":"https://api.github.com/users/ethereum/events{/privacy}","received_events_url":"https://api.github.com/users/ethereum/received_events","type":"Organization","site_admin":false},"repo":{"id":15452919,"name":"go-ethereum","full_name":"ethereum/go-ethereum","owner":{"login":"ethereum","id":6250754,"avatar_url":"https://avatars.githubusercontent.com/u/6250754?v=3","gravatar_id":"","url":"https://api.github.com/users/ethereum","html_url":"https://github.com/ethereum","followers_url":"https://api.github.com/users/ethereum/followers","following_url":"https://api.github.com/users/ethereum/following{/other_user}","gists_url":"https://api.github.com/users/ethereum/gists{/gist_id}","starred_url":"https://api.github.com/users/ethereum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ethereum/subscriptions","organizations_url":"https://api.github.com/users/ethereum/orgs","repos_url":"https://api.github.com/users/ethereum/repos","events_url":"https://api.github.com/users/ethereum/events{/privacy}","received_events_url":"https://api.github.com/users/ethereum/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ethereum/go-ethereum","description":"Official golang implementation of the Ethereum protocol","fork":false,"url":"https://api.github.com/repos/ethereum/go-ethereum","forks_url":"https://api.github.com/repos/ethereum/go-ethereum/forks","keys_url":"https://api.github.com/repos/ethereum/go-ethereum/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ethereum/go-ethereum/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ethereum/go-ethereum/teams","hooks_url":"https://api.github.com/repos/ethereum/go-ethereum/hooks","issue_events_url":"https://api.github.com/repos/ethereum/go-ethereum/issues/events{/number}","events_url":"https://api.github.com/repos/ethereum/go-ethereum/events","assignees_url":"https://api.github.com/repos/ethereum/go-ethereum/assignees{/user}","branches_url":"https://api.github.com/repos/ethereum/go-ethereum/branches{/branch}","tags_url":"https://api.github.com/repos/ethereum/go-ethereum/tags","blobs_url":"https://api.github.com/repos/ethereum/go-ethereum/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ethereum/go-ethereum/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ethereum/go-ethereum/git/refs{/sha}","trees_url":"https://api.github.com/repos/ethereum/go-ethereum/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ethereum/go-ethereum/statuses/{sha}","languages_url":"https://api.github.com/repos/ethereum/go-ethereum/languages","stargazers_url":"https://api.github.com/repos/ethereum/go-ethereum/stargazers","contributors_url":"https://api.github.com/repos/ethereum/go-ethereum/contributors","subscribers_url":"https://api.github.com/repos/ethereum/go-ethereum/subscribers","subscription_url":"https://api.github.com/repos/ethereum/go-ethereum/subscription","commits_url":"https://api.github.com/repos/ethereum/go-ethereum/commits{/sha}","git_commits_url":"https://api.github.com/repos/ethereum/go-ethereum/git/commits{/sha}","comments_url":"https://api.github.com/repos/ethereum/go-ethereum/comments{/number}","issue_comment_url":"https://api.github.com/repos/ethereum/go-ethereum/issues/comments{/number}","contents_url":"https://api.github.com/repos/ethereum/go-ethereum/contents/{+path}","compare_url":"https://api.github.com/repos/ethereum/go-ethereum/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ethereum/go-ethereum/merges","archive_url":"https://api.github.com/repos/ethereum/go-ethereum/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ethereum/go-ethereum/downloads","issues_url":"https://api.github.com/repos/ethereum/go-ethereum/issues{/number}","pulls_url":"https://api.github.com/repos/ethereum/go-ethereum/pulls{/number}","milestones_url":"https://api.github.com/repos/ethereum/go-ethereum/milestones{/number}","notifications_url":"https://api.github.com/repos/ethereum/go-ethereum/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ethereum/go-ethereum/labels{/name}","releases_url":"https://api.github.com/repos/ethereum/go-ethereum/releases{/id}","created_at":"2013-12-26T13:05:46Z","updated_at":"2015-10-12T15:09:20Z","pushed_at":"2015-10-12T13:22:13Z","git_url":"git://github.com/ethereum/go-ethereum.git","ssh_url":"git@github.com:ethereum/go-ethereum.git","clone_url":"https://github.com/ethereum/go-ethereum.git","svn_url":"https://github.com/ethereum/go-ethereum","homepage":"http://ethereum.github.io/go-ethereum/","size":82958,"stargazers_count":666,"watchers_count":666,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":228,"mirror_url":null,"open_issues_count":115,"forks":228,"open_issues":115,"watchers":666,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ethereum/go-ethereum/pulls/1889"},"html":{"href":"https://github.com/ethereum/go-ethereum/pull/1889"},"issue":{"href":"https://api.github.com/repos/ethereum/go-ethereum/issues/1889"},"comments":{"href":"https://api.github.com/repos/ethereum/go-ethereum/issues/1889/comments"},"review_comments":{"href":"https://api.github.com/repos/ethereum/go-ethereum/pulls/1889/comments"},"review_comment":{"href":"https://api.github.com/repos/ethereum/go-ethereum/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ethereum/go-ethereum/pulls/1889/commits"},"statuses":{"href":"https://api.github.com/repos/ethereum/go-ethereum/statuses/4fc1e1a914f90ce1d4b84e01629b14868c51f5e2"}}}}