mirror of https://github.com/ethereum/go-ethereum
commit
0fb1bcd321
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,351 +0,0 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"bytes" |
||||
"container/list" |
||||
"fmt" |
||||
"math" |
||||
"math/big" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/ethutil" |
||||
"github.com/ethereum/go-ethereum/logger" |
||||
"github.com/ethereum/go-ethereum/wire" |
||||
) |
||||
|
||||
var poollogger = logger.NewLogger("BPOOL") |
||||
|
||||
type block struct { |
||||
from *Peer |
||||
peer *Peer |
||||
block *types.Block |
||||
reqAt time.Time |
||||
requested int |
||||
} |
||||
|
||||
type BlockPool struct { |
||||
mut sync.Mutex |
||||
|
||||
eth *Ethereum |
||||
|
||||
hashes [][]byte |
||||
pool map[string]*block |
||||
|
||||
td *big.Int |
||||
quit chan bool |
||||
|
||||
fetchingHashes bool |
||||
downloadStartedAt time.Time |
||||
|
||||
ChainLength, BlocksProcessed int |
||||
|
||||
peer *Peer |
||||
} |
||||
|
||||
func NewBlockPool(eth *Ethereum) *BlockPool { |
||||
return &BlockPool{ |
||||
eth: eth, |
||||
pool: make(map[string]*block), |
||||
td: ethutil.Big0, |
||||
quit: make(chan bool), |
||||
} |
||||
} |
||||
|
||||
func (self *BlockPool) Len() int { |
||||
return len(self.hashes) |
||||
} |
||||
|
||||
func (self *BlockPool) Reset() { |
||||
self.pool = make(map[string]*block) |
||||
self.hashes = nil |
||||
} |
||||
|
||||
func (self *BlockPool) HasLatestHash() bool { |
||||
self.mut.Lock() |
||||
defer self.mut.Unlock() |
||||
|
||||
return self.pool[string(self.eth.ChainManager().CurrentBlock.Hash())] != nil |
||||
} |
||||
|
||||
func (self *BlockPool) HasCommonHash(hash []byte) bool { |
||||
return self.eth.ChainManager().GetBlock(hash) != nil |
||||
} |
||||
|
||||
func (self *BlockPool) Blocks() (blocks types.Blocks) { |
||||
for _, item := range self.pool { |
||||
if item.block != nil { |
||||
blocks = append(blocks, item.block) |
||||
} |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
func (self *BlockPool) FetchHashes(peer *Peer) bool { |
||||
highestTd := self.eth.HighestTDPeer() |
||||
|
||||
if (self.peer == nil && peer.td.Cmp(highestTd) >= 0) || (self.peer != nil && peer.td.Cmp(self.peer.td) > 0) || self.peer == peer { |
||||
if self.peer != peer { |
||||
poollogger.Infof("Found better suitable peer (%v vs %v)\n", self.td, peer.td) |
||||
|
||||
if self.peer != nil { |
||||
self.peer.doneFetchingHashes = true |
||||
} |
||||
} |
||||
|
||||
self.peer = peer |
||||
self.td = peer.td |
||||
|
||||
if !self.HasLatestHash() { |
||||
self.fetchHashes() |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
func (self *BlockPool) fetchHashes() { |
||||
peer := self.peer |
||||
|
||||
peer.doneFetchingHashes = false |
||||
|
||||
const amount = 256 |
||||
peerlogger.Debugf("Fetching hashes (%d) %x...\n", amount, peer.lastReceivedHash[0:4]) |
||||
peer.QueueMessage(wire.NewMessage(wire.MsgGetBlockHashesTy, []interface{}{peer.lastReceivedHash, uint32(amount)})) |
||||
} |
||||
|
||||
func (self *BlockPool) AddHash(hash []byte, peer *Peer) { |
||||
self.mut.Lock() |
||||
defer self.mut.Unlock() |
||||
|
||||
if self.pool[string(hash)] == nil { |
||||
self.pool[string(hash)] = &block{peer, nil, nil, time.Now(), 0} |
||||
|
||||
self.hashes = append([][]byte{hash}, self.hashes...) |
||||
} |
||||
} |
||||
|
||||
func (self *BlockPool) Add(b *types.Block, peer *Peer) { |
||||
self.addBlock(b, peer, false) |
||||
} |
||||
|
||||
func (self *BlockPool) AddNew(b *types.Block, peer *Peer) { |
||||
self.addBlock(b, peer, true) |
||||
} |
||||
|
||||
func (self *BlockPool) addBlock(b *types.Block, peer *Peer, newBlock bool) { |
||||
self.mut.Lock() |
||||
defer self.mut.Unlock() |
||||
|
||||
hash := string(b.Hash()) |
||||
|
||||
if self.pool[hash] == nil && !self.eth.ChainManager().HasBlock(b.Hash()) { |
||||
poollogger.Infof("Got unrequested block (%x...)\n", hash[0:4]) |
||||
|
||||
self.hashes = append(self.hashes, b.Hash()) |
||||
self.pool[hash] = &block{peer, peer, b, time.Now(), 0} |
||||
|
||||
// The following is only performed on an unrequested new block
|
||||
if newBlock { |
||||
fmt.Println("1.", !self.eth.ChainManager().HasBlock(b.PrevHash), ethutil.Bytes2Hex(b.Hash()[0:4]), ethutil.Bytes2Hex(b.PrevHash[0:4])) |
||||
fmt.Println("2.", self.pool[string(b.PrevHash)] == nil) |
||||
fmt.Println("3.", !self.fetchingHashes) |
||||
if !self.eth.ChainManager().HasBlock(b.PrevHash) /*&& self.pool[string(b.PrevHash)] == nil*/ && !self.fetchingHashes { |
||||
poollogger.Infof("Unknown chain, requesting (%x...)\n", b.PrevHash[0:4]) |
||||
peer.QueueMessage(wire.NewMessage(wire.MsgGetBlockHashesTy, []interface{}{b.Hash(), uint32(256)})) |
||||
} |
||||
} |
||||
} else if self.pool[hash] != nil { |
||||
self.pool[hash].block = b |
||||
} |
||||
|
||||
self.BlocksProcessed++ |
||||
} |
||||
|
||||
func (self *BlockPool) Remove(hash []byte) { |
||||
self.mut.Lock() |
||||
defer self.mut.Unlock() |
||||
|
||||
self.hashes = ethutil.DeleteFromByteSlice(self.hashes, hash) |
||||
delete(self.pool, string(hash)) |
||||
} |
||||
|
||||
func (self *BlockPool) DistributeHashes() { |
||||
self.mut.Lock() |
||||
defer self.mut.Unlock() |
||||
|
||||
var ( |
||||
peerLen = self.eth.peers.Len() |
||||
amount = 256 * peerLen |
||||
dist = make(map[*Peer][][]byte) |
||||
) |
||||
|
||||
num := int(math.Min(float64(amount), float64(len(self.pool)))) |
||||
for i, j := 0, 0; i < len(self.hashes) && j < num; i++ { |
||||
hash := self.hashes[i] |
||||
item := self.pool[string(hash)] |
||||
|
||||
if item != nil && item.block == nil { |
||||
var peer *Peer |
||||
lastFetchFailed := time.Since(item.reqAt) > 5*time.Second |
||||
|
||||
// Handle failed requests
|
||||
if lastFetchFailed && item.requested > 5 && item.peer != nil { |
||||
if item.requested < 100 { |
||||
// Select peer the hash was retrieved off
|
||||
peer = item.from |
||||
} else { |
||||
// Remove it
|
||||
self.hashes = ethutil.DeleteFromByteSlice(self.hashes, hash) |
||||
delete(self.pool, string(hash)) |
||||
} |
||||
} else if lastFetchFailed || item.peer == nil { |
||||
// Find a suitable, available peer
|
||||
eachPeer(self.eth.peers, func(p *Peer, v *list.Element) { |
||||
if peer == nil && len(dist[p]) < amount/peerLen && p.statusKnown { |
||||
peer = p |
||||
} |
||||
}) |
||||
} |
||||
|
||||
if peer != nil { |
||||
item.reqAt = time.Now() |
||||
item.peer = peer |
||||
item.requested++ |
||||
|
||||
dist[peer] = append(dist[peer], hash) |
||||
} |
||||
} |
||||
} |
||||
|
||||
for peer, hashes := range dist { |
||||
peer.FetchBlocks(hashes) |
||||
} |
||||
|
||||
if len(dist) > 0 { |
||||
self.downloadStartedAt = time.Now() |
||||
} |
||||
} |
||||
|
||||
func (self *BlockPool) Start() { |
||||
go self.downloadThread() |
||||
go self.chainThread() |
||||
} |
||||
|
||||
func (self *BlockPool) Stop() { |
||||
close(self.quit) |
||||
} |
||||
|
||||
func (self *BlockPool) downloadThread() { |
||||
serviceTimer := time.NewTicker(100 * time.Millisecond) |
||||
out: |
||||
for { |
||||
select { |
||||
case <-self.quit: |
||||
break out |
||||
case <-serviceTimer.C: |
||||
// Check if we're catching up. If not distribute the hashes to
|
||||
// the peers and download the blockchain
|
||||
self.fetchingHashes = false |
||||
eachPeer(self.eth.peers, func(p *Peer, v *list.Element) { |
||||
if p.statusKnown && p.FetchingHashes() { |
||||
self.fetchingHashes = true |
||||
} |
||||
}) |
||||
|
||||
if len(self.hashes) > 0 { |
||||
self.DistributeHashes() |
||||
} |
||||
|
||||
if self.ChainLength < len(self.hashes) { |
||||
self.ChainLength = len(self.hashes) |
||||
} |
||||
|
||||
if self.peer != nil && |
||||
!self.peer.doneFetchingHashes && |
||||
time.Since(self.peer.lastHashAt) > 10*time.Second && |
||||
time.Since(self.peer.lastHashRequestedAt) > 5*time.Second { |
||||
self.fetchHashes() |
||||
} |
||||
|
||||
/* |
||||
if !self.fetchingHashes { |
||||
blocks := self.Blocks() |
||||
chain.BlockBy(chain.Number).Sort(blocks) |
||||
|
||||
if len(blocks) > 0 { |
||||
if !self.eth.ChainManager().HasBlock(b.PrevHash) && self.pool[string(b.PrevHash)] == nil && !self.fetchingHashes { |
||||
} |
||||
} |
||||
} |
||||
*/ |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (self *BlockPool) chainThread() { |
||||
procTimer := time.NewTicker(500 * time.Millisecond) |
||||
out: |
||||
for { |
||||
select { |
||||
case <-self.quit: |
||||
break out |
||||
case <-procTimer.C: |
||||
blocks := self.Blocks() |
||||
types.BlockBy(types.Number).Sort(blocks) |
||||
|
||||
// Find common block
|
||||
for i, block := range blocks { |
||||
if self.eth.ChainManager().HasBlock(block.PrevHash) { |
||||
blocks = blocks[i:] |
||||
break |
||||
} |
||||
} |
||||
|
||||
if len(blocks) > 0 { |
||||
if self.eth.ChainManager().HasBlock(blocks[0].PrevHash) { |
||||
for i, block := range blocks[1:] { |
||||
// NOTE: The Ith element in this loop refers to the previous block in
|
||||
// outer "blocks"
|
||||
if bytes.Compare(block.PrevHash, blocks[i].Hash()) != 0 { |
||||
blocks = blocks[:i] |
||||
|
||||
break |
||||
} |
||||
} |
||||
} else { |
||||
blocks = nil |
||||
} |
||||
} |
||||
|
||||
if len(blocks) > 0 { |
||||
chainman := self.eth.ChainManager() |
||||
|
||||
err := chainman.InsertChain(blocks) |
||||
if err != nil { |
||||
poollogger.Debugln(err) |
||||
|
||||
self.Reset() |
||||
|
||||
if self.peer != nil && self.peer.conn != nil { |
||||
poollogger.Debugf("Punishing peer for supplying bad chain (%v)\n", self.peer.conn.RemoteAddr()) |
||||
} |
||||
|
||||
// This peer gave us bad hashes and made us fetch a bad chain, therefor he shall be punished.
|
||||
self.eth.BlacklistPeer(self.peer) |
||||
self.peer.StopWithReason(DiscBadPeer) |
||||
self.td = ethutil.Big0 |
||||
self.peer = nil |
||||
} |
||||
|
||||
for _, block := range blocks { |
||||
self.Remove(block.Hash()) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
{ |
||||
"directory": "example/js/", |
||||
"cwd": "./", |
||||
"analytics": false |
||||
} |
@ -0,0 +1,12 @@ |
||||
root = true |
||||
|
||||
[*] |
||||
indent_style = space |
||||
indent_size = 4 |
||||
end_of_line = lf |
||||
charset = utf-8 |
||||
trim_trailing_whitespace = true |
||||
insert_final_newline = true |
||||
|
||||
[*.md] |
||||
trim_trailing_whitespace = false |
@ -0,0 +1,50 @@ |
||||
{ |
||||
"predef": [ |
||||
"console", |
||||
"require", |
||||
"equal", |
||||
"test", |
||||
"testBoth", |
||||
"testWithDefault", |
||||
"raises", |
||||
"deepEqual", |
||||
"start", |
||||
"stop", |
||||
"ok", |
||||
"strictEqual", |
||||
"module", |
||||
"expect", |
||||
"reject", |
||||
"impl" |
||||
], |
||||
|
||||
"esnext": true, |
||||
"proto": true, |
||||
"node" : true, |
||||
"browser" : true, |
||||
"browserify" : true, |
||||
|
||||
"boss" : true, |
||||
"curly": false, |
||||
"debug": true, |
||||
"devel": true, |
||||
"eqeqeq": true, |
||||
"evil": true, |
||||
"forin": false, |
||||
"immed": false, |
||||
"laxbreak": false, |
||||
"newcap": true, |
||||
"noarg": true, |
||||
"noempty": false, |
||||
"nonew": false, |
||||
"nomen": false, |
||||
"onevar": false, |
||||
"plusplus": false, |
||||
"regexp": false, |
||||
"undef": true, |
||||
"sub": true, |
||||
"strict": false, |
||||
"white": false, |
||||
"shadow": true, |
||||
"eqnull": true |
||||
} |
@ -0,0 +1,9 @@ |
||||
example/js |
||||
node_modules |
||||
test |
||||
.gitignore |
||||
.editorconfig |
||||
.travis.yml |
||||
.npmignore |
||||
component.json |
||||
testling.html |
@ -0,0 +1,11 @@ |
||||
language: node_js |
||||
node_js: |
||||
- "0.11" |
||||
- "0.10" |
||||
before_script: |
||||
- npm install |
||||
- npm install jshint |
||||
script: |
||||
- "jshint *.js lib" |
||||
after_script: |
||||
- npm run-script gulp |
@ -0,0 +1,14 @@ |
||||
This file is part of ethereum.js. |
||||
|
||||
ethereum.js is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU Lesser General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
ethereum.js is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU Lesser General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License |
||||
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>. |
@ -0,0 +1,79 @@ |
||||
# Ethereum JavaScript API |
||||
|
||||
This is the Ethereum compatible JavaScript API using `Promise`s |
||||
which implements the [Generic JSON RPC](https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC) spec. It's available on npm as a node module and also for bower and component as an embeddable js |
||||
|
||||
[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![dependency status][dep-image]][dep-url] [![dev dependency status][dep-dev-image]][dep-dev-url] |
||||
|
||||
<!-- [![browser support](https://ci.testling.com/ethereum/ethereum.js.png)](https://ci.testling.com/ethereum/ethereum.js) --> |
||||
|
||||
## Installation |
||||
|
||||
### Node.js |
||||
|
||||
npm install ethereum.js |
||||
|
||||
### For browser |
||||
Bower |
||||
|
||||
bower install ethereum.js |
||||
|
||||
Component |
||||
|
||||
component install ethereum/ethereum.js |
||||
|
||||
* Include `ethereum.min.js` in your html file. |
||||
* Include [es6-promise](https://github.com/jakearchibald/es6-promise) or another ES6-Shim if your browser doesn't support ECMAScript 6. |
||||
|
||||
## Usage |
||||
Require the library: |
||||
|
||||
var web3 = require('web3'); |
||||
|
||||
Set a provider (QtProvider, WebSocketProvider, HttpRpcProvider) |
||||
|
||||
var web3.setProvider(new web3.providers.WebSocketProvider('ws://localhost:40404/eth')); |
||||
|
||||
There you go, now you can use it: |
||||
|
||||
``` |
||||
web3.eth.coinbase.then(function(result){ |
||||
console.log(result); |
||||
return web3.eth.balanceAt(result); |
||||
}).then(function(balance){ |
||||
console.log(web3.toDecimal(balance)); |
||||
}).catch(function(err){ |
||||
console.log(err); |
||||
}); |
||||
``` |
||||
|
||||
|
||||
For another example see `example/index.html`. |
||||
|
||||
## Building |
||||
|
||||
* `gulp build` |
||||
|
||||
|
||||
### Testing |
||||
|
||||
**Please note this repo is in it's early stage.** |
||||
|
||||
If you'd like to run a WebSocket ethereum node check out |
||||
[go-ethereum](https://github.com/ethereum/go-ethereum). |
||||
|
||||
To install ethereum and spawn a node: |
||||
|
||||
``` |
||||
go get github.com/ethereum/go-ethereum/ethereum |
||||
ethereum -ws -loglevel=4 |
||||
``` |
||||
|
||||
[npm-image]: https://badge.fury.io/js/ethereum.js.png |
||||
[npm-url]: https://npmjs.org/package/ethereum.js |
||||
[travis-image]: https://travis-ci.org/ethereum/ethereum.js.svg |
||||
[travis-url]: https://travis-ci.org/ethereum/ethereum.js |
||||
[dep-image]: https://david-dm.org/ethereum/ethereum.js.svg |
||||
[dep-url]: https://david-dm.org/ethereum/ethereum.js |
||||
[dep-dev-image]: https://david-dm.org/ethereum/ethereum.js/dev-status.svg |
||||
[dep-dev-url]: https://david-dm.org/ethereum/ethereum.js#info=devDependencies |
@ -0,0 +1,51 @@ |
||||
{ |
||||
"name": "ethereum.js", |
||||
"namespace": "ethereum", |
||||
"version": "0.0.3", |
||||
"description": "Ethereum Compatible JavaScript API", |
||||
"main": ["./dist/ethereum.js", "./dist/ethereum.min.js"], |
||||
"dependencies": { |
||||
"es6-promise": "#master" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "https://github.com/ethereum/ethereum.js.git" |
||||
}, |
||||
"homepage": "https://github.com/ethereum/ethereum.js", |
||||
"bugs": { |
||||
"url": "https://github.com/ethereum/ethereum.js/issues" |
||||
}, |
||||
"keywords": [ |
||||
"ethereum", |
||||
"javascript", |
||||
"API" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Marek Kotewicz", |
||||
"email": "marek@ethdev.com", |
||||
"homepage": "https://github.com/debris" |
||||
}, |
||||
{ |
||||
"name": "Marian Oancea", |
||||
"email": "marian@ethdev.com", |
||||
"homepage": "https://github.com/cubedro" |
||||
} |
||||
], |
||||
"license": "LGPL-3.0", |
||||
"ignore": [ |
||||
"example", |
||||
"lib", |
||||
"node_modules", |
||||
"package.json", |
||||
".bowerrc", |
||||
".editorconfig", |
||||
".gitignore", |
||||
".jshintrc", |
||||
".npmignore", |
||||
".travis.yml", |
||||
"gulpfile.js", |
||||
"index.js", |
||||
"**/*.txt" |
||||
] |
||||
} |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,75 @@ |
||||
<!doctype> |
||||
<html> |
||||
|
||||
<head> |
||||
<script type="text/javascript" src="js/es6-promise/promise.min.js"></script> |
||||
<script type="text/javascript" src="../dist/ethereum.js"></script> |
||||
<script type="text/javascript"> |
||||
|
||||
var web3 = require('web3'); |
||||
web3.setProvider(new web3.providers.AutoProvider()); |
||||
|
||||
// solidity source code |
||||
var source = "" + |
||||
"contract test {\n" + |
||||
" function multiply(uint a) returns(uint d) {\n" + |
||||
" return a * 7;\n" + |
||||
" }\n" + |
||||
"}\n"; |
||||
|
||||
// contract description, this will be autogenerated somehow |
||||
var desc = [{ |
||||
"name": "multiply", |
||||
"inputs": [ |
||||
{ |
||||
"name": "a", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"outputs": [ |
||||
{ |
||||
"name": "d", |
||||
"type": "uint256" |
||||
} |
||||
] |
||||
}]; |
||||
|
||||
var contract; |
||||
|
||||
function createExampleContract() { |
||||
// hide create button |
||||
document.getElementById('create').style.visibility = 'hidden'; |
||||
document.getElementById('source').innerText = source; |
||||
|
||||
// create contract |
||||
web3.eth.transact({code: web3.eth.solidity(source)}).then(function (address) { |
||||
contract = web3.contract(address, desc); |
||||
document.getElementById('call').style.visibility = 'visible'; |
||||
}); |
||||
} |
||||
|
||||
function callExampleContract() { |
||||
// this should be generated by ethereum |
||||
var param = document.getElementById('value').value; |
||||
|
||||
// call the contract |
||||
contract.multiply(param).call().then(function(res) { |
||||
document.getElementById('result').innerText = res[0]; |
||||
}); |
||||
} |
||||
|
||||
</script> |
||||
</head> |
||||
<body> |
||||
<h1>contract</h1> |
||||
<div id="source"></div> |
||||
<div id='create'> |
||||
<button type="button" onClick="createExampleContract();">create example contract</button> |
||||
</div> |
||||
<div id='call' style='visibility: hidden;'> |
||||
<input type="number" id="value" onkeyup='callExampleContract()'></input> |
||||
</div> |
||||
<div id="result"></div> |
||||
</body> |
||||
</html> |
||||
|
@ -0,0 +1,41 @@ |
||||
<!doctype> |
||||
<html> |
||||
|
||||
<head> |
||||
<script type="text/javascript" src="js/es6-promise/promise.min.js"></script> |
||||
<script type="text/javascript" src="../dist/ethereum.js"></script> |
||||
<script type="text/javascript"> |
||||
|
||||
var web3 = require('web3'); |
||||
web3.setProvider(new web3.providers.AutoProvider()); |
||||
|
||||
function watchBalance() { |
||||
var coinbase = web3.eth.coinbase; |
||||
var originalBalance = 0; |
||||
|
||||
web3.eth.balanceAt(coinbase).then(function (balance) { |
||||
originalBalance = web3.toDecimal(balance); |
||||
document.getElementById('original').innerText = 'original balance: ' + originalBalance + ' watching...'; |
||||
}); |
||||
|
||||
web3.eth.watch({altered: coinbase}).changed(function() { |
||||
web3.eth.balanceAt(coinbase).then(function (balance) { |
||||
var currentBalance = web3.toDecimal(balance); |
||||
document.getElementById("current").innerText = 'current: ' + currentBalance; |
||||
document.getElementById("diff").innerText = 'diff: ' + (currentBalance - originalBalance); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
</script> |
||||
</head> |
||||
<body> |
||||
<h1>coinbase balance</h1> |
||||
<button type="button" onClick="watchBalance();">watch balance</button> |
||||
<div></div> |
||||
<div id="original"></div> |
||||
<div id="current"></div> |
||||
<div id="diff"></div> |
||||
</body> |
||||
</html> |
||||
|
@ -0,0 +1,16 @@ |
||||
#!/usr/bin/env node
|
||||
|
||||
require('es6-promise').polyfill(); |
||||
|
||||
var web3 = require("../index.js"); |
||||
|
||||
web3.setProvider(new web3.providers.HttpRpcProvider('http://localhost:8080')); |
||||
|
||||
web3.eth.coinbase.then(function(result){ |
||||
console.log(result); |
||||
return web3.eth.balanceAt(result); |
||||
}).then(function(balance){ |
||||
console.log(web3.toDecimal(balance)); |
||||
}).catch(function(err){ |
||||
console.log(err); |
||||
}); |
@ -0,0 +1,123 @@ |
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict'; |
||||
|
||||
var path = require('path'); |
||||
|
||||
var del = require('del'); |
||||
var gulp = require('gulp'); |
||||
var browserify = require('browserify'); |
||||
var jshint = require('gulp-jshint'); |
||||
var uglify = require('gulp-uglify'); |
||||
var rename = require('gulp-rename'); |
||||
var envify = require('envify/custom'); |
||||
var unreach = require('unreachable-branch-transform'); |
||||
var source = require('vinyl-source-stream'); |
||||
var exorcist = require('exorcist'); |
||||
var bower = require('bower'); |
||||
|
||||
var DEST = './dist/'; |
||||
|
||||
var build = function(src, dst) { |
||||
return browserify({ |
||||
debug: true, |
||||
insert_global_vars: false, |
||||
detectGlobals: false, |
||||
bundleExternal: false |
||||
}) |
||||
.require('./' + src + '.js', {expose: 'web3'}) |
||||
.add('./' + src + '.js') |
||||
.transform('envify', { |
||||
NODE_ENV: 'build' |
||||
}) |
||||
.transform('unreachable-branch-transform') |
||||
.transform('uglifyify', { |
||||
mangle: false, |
||||
compress: { |
||||
dead_code: false, |
||||
conditionals: true, |
||||
unused: false, |
||||
hoist_funs: true, |
||||
hoist_vars: true, |
||||
negate_iife: false |
||||
}, |
||||
beautify: true, |
||||
warnings: true |
||||
}) |
||||
.bundle() |
||||
.pipe(exorcist(path.join( DEST, dst + '.js.map'))) |
||||
.pipe(source(dst + '.js')) |
||||
.pipe(gulp.dest( DEST )); |
||||
}; |
||||
|
||||
var buildDev = function(src, dst) { |
||||
return browserify({ |
||||
debug: true, |
||||
insert_global_vars: false, |
||||
detectGlobals: false, |
||||
bundleExternal: false |
||||
}) |
||||
.require('./' + src + '.js', {expose: 'web3'}) |
||||
.add('./' + src + '.js') |
||||
.transform('envify', { |
||||
NODE_ENV: 'build' |
||||
}) |
||||
.transform('unreachable-branch-transform') |
||||
.bundle() |
||||
.pipe(exorcist(path.join( DEST, dst + '.js.map'))) |
||||
.pipe(source(dst + '.js')) |
||||
.pipe(gulp.dest( DEST )); |
||||
}; |
||||
|
||||
var uglifyFile = function(file) { |
||||
return gulp.src( DEST + file + '.js') |
||||
.pipe(uglify()) |
||||
.pipe(rename(file + '.min.js')) |
||||
.pipe(gulp.dest( DEST )); |
||||
}; |
||||
|
||||
gulp.task('bower', function(cb){ |
||||
bower.commands.install().on('end', function (installed){ |
||||
console.log(installed); |
||||
cb(); |
||||
}); |
||||
}); |
||||
|
||||
gulp.task('lint', function(){ |
||||
return gulp.src(['./*.js', './lib/*.js']) |
||||
.pipe(jshint()) |
||||
.pipe(jshint.reporter('default')); |
||||
}); |
||||
|
||||
gulp.task('clean', ['lint'], function(cb) { |
||||
del([ DEST ], cb); |
||||
}); |
||||
|
||||
gulp.task('build', ['clean'], function () { |
||||
return build('index', 'ethereum'); |
||||
}); |
||||
|
||||
gulp.task('buildQt', ['clean'], function () { |
||||
return build('index_qt', 'ethereum'); |
||||
}); |
||||
|
||||
gulp.task('buildDev', ['clean'], function () { |
||||
return buildDev('index', 'ethereum'); |
||||
}); |
||||
|
||||
gulp.task('uglify', ['build'], function(){ |
||||
return uglifyFile('ethereum'); |
||||
}); |
||||
|
||||
gulp.task('uglifyQt', ['buildQt'], function () { |
||||
return uglifyFile('ethereum'); |
||||
}); |
||||
|
||||
gulp.task('watch', function() { |
||||
gulp.watch(['./lib/*.js'], ['lint', 'prepare', 'build']); |
||||
}); |
||||
|
||||
gulp.task('default', ['bower', 'lint', 'build', 'uglify']); |
||||
gulp.task('qt', ['bower', 'lint', 'buildQt', 'uglifyQt']); |
||||
gulp.task('dev', ['bower', 'lint', 'buildDev']); |
||||
|
@ -0,0 +1,8 @@ |
||||
var web3 = require('./lib/main'); |
||||
web3.providers.WebSocketProvider = require('./lib/websocket'); |
||||
web3.providers.HttpRpcProvider = require('./lib/httprpc'); |
||||
web3.providers.QtProvider = require('./lib/qt'); |
||||
web3.providers.AutoProvider = require('./lib/autoprovider'); |
||||
web3.contract = require('./lib/contract'); |
||||
|
||||
module.exports = web3; |
@ -0,0 +1,5 @@ |
||||
var web3 = require('./lib/main'); |
||||
web3.providers.QtProvider = require('./lib/qt'); |
||||
web3.contract = require('./lib/contract'); |
||||
|
||||
module.exports = web3; |
@ -0,0 +1,218 @@ |
||||
/* |
||||
This file is part of ethereum.js. |
||||
|
||||
ethereum.js is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU Lesser General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
ethereum.js is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU Lesser General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License |
||||
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
/** @file abi.js |
||||
* @authors: |
||||
* Marek Kotewicz <marek@ethdev.com> |
||||
* Gav Wood <g@ethdev.com> |
||||
* @date 2014 |
||||
*/ |
||||
|
||||
// TODO: make these be actually accurate instead of falling back onto JS's doubles.
|
||||
var hexToDec = function (hex) { |
||||
return parseInt(hex, 16).toString(); |
||||
}; |
||||
|
||||
var decToHex = function (dec) { |
||||
return parseInt(dec).toString(16); |
||||
}; |
||||
|
||||
var findIndex = function (array, callback) { |
||||
var end = false; |
||||
var i = 0; |
||||
for (; i < array.length && !end; i++) { |
||||
end = callback(array[i]); |
||||
} |
||||
return end ? i - 1 : -1; |
||||
}; |
||||
|
||||
var findMethodIndex = function (json, methodName) { |
||||
return findIndex(json, function (method) { |
||||
return method.name === methodName; |
||||
}); |
||||
}; |
||||
|
||||
var padLeft = function (string, chars) { |
||||
return Array(chars - string.length + 1).join("0") + string; |
||||
}; |
||||
|
||||
var setupInputTypes = function () { |
||||
var prefixedType = function (prefix) { |
||||
return function (type, value) { |
||||
var expected = prefix; |
||||
if (type.indexOf(expected) !== 0) { |
||||
return false; |
||||
} |
||||
|
||||
var padding = parseInt(type.slice(expected.length)) / 8; |
||||
if (typeof value === "number") |
||||
value = value.toString(16); |
||||
else if (value.indexOf('0x') === 0) |
||||
value = value.substr(2); |
||||
else |
||||
value = (+value).toString(16); |
||||
return padLeft(value, padding * 2); |
||||
}; |
||||
}; |
||||
|
||||
var namedType = function (name, padding, formatter) { |
||||
return function (type, value) { |
||||
if (type !== name) { |
||||
return false; |
||||
} |
||||
|
||||
return padLeft(formatter ? formatter(value) : value, padding * 2); |
||||
}; |
||||
}; |
||||
|
||||
var formatBool = function (value) { |
||||
return value ? '0x1' : '0x0'; |
||||
}; |
||||
|
||||
return [ |
||||
prefixedType('uint'), |
||||
prefixedType('int'), |
||||
prefixedType('hash'), |
||||
namedType('address', 20), |
||||
namedType('bool', 1, formatBool), |
||||
]; |
||||
}; |
||||
|
||||
var inputTypes = setupInputTypes(); |
||||
|
||||
var toAbiInput = function (json, methodName, params) { |
||||
var bytes = ""; |
||||
var index = findMethodIndex(json, methodName); |
||||
|
||||
if (index === -1) { |
||||
return; |
||||
} |
||||
|
||||
bytes = "0x" + padLeft(index.toString(16), 2); |
||||
var method = json[index]; |
||||
|
||||
for (var i = 0; i < method.inputs.length; i++) { |
||||
var found = false; |
||||
for (var j = 0; j < inputTypes.length && !found; j++) { |
||||
found = inputTypes[j](method.inputs[i].type, params[i]); |
||||
} |
||||
if (!found) { |
||||
console.error('unsupported json type: ' + method.inputs[i].type); |
||||
} |
||||
bytes += found; |
||||
} |
||||
return bytes; |
||||
}; |
||||
|
||||
var setupOutputTypes = function () { |
||||
var prefixedType = function (prefix) { |
||||
return function (type) { |
||||
var expected = prefix; |
||||
if (type.indexOf(expected) !== 0) { |
||||
return -1; |
||||
} |
||||
|
||||
var padding = parseInt(type.slice(expected.length)) / 8; |
||||
return padding * 2; |
||||
}; |
||||
}; |
||||
|
||||
var namedType = function (name, padding) { |
||||
return function (type) { |
||||
return name === type ? padding * 2 : -1; |
||||
}; |
||||
}; |
||||
|
||||
var formatInt = function (value) { |
||||
return value.length <= 8 ? +parseInt(value, 16) : hexToDec(value); |
||||
}; |
||||
|
||||
var formatHash = function (value) { |
||||
return "0x" + value; |
||||
}; |
||||
|
||||
var formatBool = function (value) { |
||||
return value === '1' ? true : false; |
||||
}; |
||||
|
||||
return [ |
||||
{ padding: prefixedType('uint'), format: formatInt }, |
||||
{ padding: prefixedType('int'), format: formatInt }, |
||||
{ padding: prefixedType('hash'), format: formatHash }, |
||||
{ padding: namedType('address', 20) }, |
||||
{ padding: namedType('bool', 1), format: formatBool } |
||||
]; |
||||
}; |
||||
|
||||
var outputTypes = setupOutputTypes(); |
||||
|
||||
var fromAbiOutput = function (json, methodName, output) { |
||||
var index = findMethodIndex(json, methodName); |
||||
|
||||
if (index === -1) { |
||||
return; |
||||
} |
||||
|
||||
output = output.slice(2); |
||||
|
||||
var result = []; |
||||
var method = json[index]; |
||||
for (var i = 0; i < method.outputs.length; i++) { |
||||
var padding = -1; |
||||
for (var j = 0; j < outputTypes.length && padding === -1; j++) { |
||||
padding = outputTypes[j].padding(method.outputs[i].type); |
||||
} |
||||
|
||||
if (padding === -1) { |
||||
// not found output parsing
|
||||
continue; |
||||
} |
||||
var res = output.slice(0, padding); |
||||
var formatter = outputTypes[j - 1].format; |
||||
result.push(formatter ? formatter(res) : ("0x" + res)); |
||||
output = output.slice(padding); |
||||
} |
||||
|
||||
return result; |
||||
}; |
||||
|
||||
var inputParser = function (json) { |
||||
var parser = {}; |
||||
json.forEach(function (method) { |
||||
parser[method.name] = function () { |
||||
var params = Array.prototype.slice.call(arguments); |
||||
return toAbiInput(json, method.name, params); |
||||
}; |
||||
}); |
||||
|
||||
return parser; |
||||
}; |
||||
|
||||
var outputParser = function (json) { |
||||
var parser = {}; |
||||
json.forEach(function (method) { |
||||
parser[method.name] = function (output) { |
||||
return fromAbiOutput(json, method.name, output); |
||||
}; |
||||
}); |
||||
|
||||
return parser; |
||||
}; |
||||
|
||||
module.exports = { |
||||
inputParser: inputParser, |
||||
outputParser: outputParser |
||||
}; |
@ -0,0 +1,103 @@ |
||||
/* |
||||
This file is part of ethereum.js. |
||||
|
||||
ethereum.js is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU Lesser General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
ethereum.js is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU Lesser General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License |
||||
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
/** @file autoprovider.js |
||||
* @authors: |
||||
* Marek Kotewicz <marek@ethdev.com> |
||||
* Marian Oancea <marian@ethdev.com> |
||||
* @date 2014 |
||||
*/ |
||||
|
||||
/* |
||||
* @brief if qt object is available, uses QtProvider, |
||||
* if not tries to connect over websockets |
||||
* if it fails, it uses HttpRpcProvider |
||||
*/ |
||||
|
||||
// TODO: work out which of the following two lines it is supposed to be...
|
||||
//if (process.env.NODE_ENV !== 'build') {
|
||||
if ("build" !== 'build') {/* |
||||
var WebSocket = require('ws'); // jshint ignore:line
|
||||
var web3 = require('./main.js'); // jshint ignore:line
|
||||
*/} |
||||
|
||||
var AutoProvider = function (userOptions) { |
||||
if (web3.haveProvider()) { |
||||
return; |
||||
} |
||||
|
||||
// before we determine what provider we are, we have to cache request
|
||||
this.sendQueue = []; |
||||
this.onmessageQueue = []; |
||||
|
||||
if (navigator.qt) { |
||||
this.provider = new web3.providers.QtProvider(); |
||||
return; |
||||
} |
||||
|
||||
userOptions = userOptions || {}; |
||||
var options = { |
||||
httprpc: userOptions.httprpc || 'http://localhost:8080', |
||||
websockets: userOptions.websockets || 'ws://localhost:40404/eth' |
||||
}; |
||||
|
||||
var self = this; |
||||
var closeWithSuccess = function (success) { |
||||
ws.close(); |
||||
if (success) { |
||||
self.provider = new web3.providers.WebSocketProvider(options.websockets); |
||||
} else { |
||||
self.provider = new web3.providers.HttpRpcProvider(options.httprpc); |
||||
self.poll = self.provider.poll.bind(self.provider); |
||||
} |
||||
self.sendQueue.forEach(function (payload) { |
||||
self.provider(payload); |
||||
}); |
||||
self.onmessageQueue.forEach(function (handler) { |
||||
self.provider.onmessage = handler; |
||||
}); |
||||
}; |
||||
|
||||
var ws = new WebSocket(options.websockets); |
||||
|
||||
ws.onopen = function() { |
||||
closeWithSuccess(true); |
||||
}; |
||||
|
||||
ws.onerror = function() { |
||||
closeWithSuccess(false); |
||||
}; |
||||
}; |
||||
|
||||
AutoProvider.prototype.send = function (payload) { |
||||
if (this.provider) { |
||||
this.provider.send(payload); |
||||
return; |
||||
} |
||||
this.sendQueue.push(payload); |
||||
}; |
||||
|
||||
Object.defineProperty(AutoProvider.prototype, 'onmessage', { |
||||
set: function (handler) { |
||||
if (this.provider) { |
||||
this.provider.onmessage = handler; |
||||
return; |
||||
} |
||||
this.onmessageQueue.push(handler); |
||||
} |
||||
}); |
||||
|
||||
module.exports = AutoProvider; |
@ -0,0 +1,65 @@ |
||||
/* |
||||
This file is part of ethereum.js. |
||||
|
||||
ethereum.js is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU Lesser General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
ethereum.js is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU Lesser General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License |
||||
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
/** @file contract.js |
||||
* @authors: |
||||
* Marek Kotewicz <marek@ethdev.com> |
||||
* @date 2014 |
||||
*/ |
||||
|
||||
// TODO: work out which of the following two lines it is supposed to be...
|
||||
//if (process.env.NODE_ENV !== 'build') {
|
||||
if ("build" !== 'build') {/* |
||||
var web3 = require('./web3'); // jshint ignore:line
|
||||
*/} |
||||
var abi = require('./abi'); |
||||
|
||||
var contract = function (address, desc) { |
||||
var inputParser = abi.inputParser(desc); |
||||
var outputParser = abi.outputParser(desc); |
||||
|
||||
var contract = {}; |
||||
|
||||
desc.forEach(function (method) { |
||||
contract[method.name] = function () { |
||||
var params = Array.prototype.slice.call(arguments); |
||||
var parsed = inputParser[method.name].apply(null, params); |
||||
|
||||
var onSuccess = function (result) { |
||||
return outputParser[method.name](result); |
||||
}; |
||||
|
||||
return { |
||||
call: function (extra) { |
||||
extra = extra || {}; |
||||
extra.to = address; |
||||
extra.data = parsed; |
||||
return web3.eth.call(extra).then(onSuccess); |
||||
}, |
||||
transact: function (extra) { |
||||
extra = extra || {}; |
||||
extra.to = address; |
||||
extra.data = parsed; |
||||
return web3.eth.transact(extra).then(onSuccess); |
||||
} |
||||
}; |
||||
}; |
||||
}); |
||||
|
||||
return contract; |
||||
}; |
||||
|
||||
module.exports = contract; |
@ -0,0 +1,95 @@ |
||||
/* |
||||
This file is part of ethereum.js. |
||||
|
||||
ethereum.js is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU Lesser General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
ethereum.js is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU Lesser General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License |
||||
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
/** @file httprpc.js |
||||
* @authors: |
||||
* Marek Kotewicz <marek@ethdev.com> |
||||
* Marian Oancea <marian@ethdev.com> |
||||
* @date 2014 |
||||
*/ |
||||
|
||||
// TODO: work out which of the following two lines it is supposed to be...
|
||||
//if (process.env.NODE_ENV !== 'build') {
|
||||
if ("build" !== "build") {/* |
||||
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line
|
||||
*/} |
||||
|
||||
var HttpRpcProvider = function (host) { |
||||
this.handlers = []; |
||||
this.host = host; |
||||
}; |
||||
|
||||
function formatJsonRpcObject(object) { |
||||
return { |
||||
jsonrpc: '2.0', |
||||
method: object.call, |
||||
params: object.args, |
||||
id: object._id |
||||
}; |
||||
} |
||||
|
||||
function formatJsonRpcMessage(message) { |
||||
var object = JSON.parse(message); |
||||
|
||||
return { |
||||
_id: object.id, |
||||
data: object.result, |
||||
error: object.error |
||||
}; |
||||
} |
||||
|
||||
HttpRpcProvider.prototype.sendRequest = function (payload, cb) { |
||||
var data = formatJsonRpcObject(payload); |
||||
|
||||
var request = new XMLHttpRequest(); |
||||
request.open("POST", this.host, true); |
||||
request.send(JSON.stringify(data)); |
||||
request.onreadystatechange = function () { |
||||
if (request.readyState === 4 && cb) { |
||||
cb(request); |
||||
} |
||||
}; |
||||
}; |
||||
|
||||
HttpRpcProvider.prototype.send = function (payload) { |
||||
var self = this; |
||||
this.sendRequest(payload, function (request) { |
||||
self.handlers.forEach(function (handler) { |
||||
handler.call(self, formatJsonRpcMessage(request.responseText)); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
HttpRpcProvider.prototype.poll = function (payload, id) { |
||||
var self = this; |
||||
this.sendRequest(payload, function (request) { |
||||
var parsed = JSON.parse(request.responseText); |
||||
if (parsed.error || (parsed.result instanceof Array ? parsed.result.length === 0 : !parsed.result)) { |
||||
return; |
||||
} |
||||
self.handlers.forEach(function (handler) { |
||||
handler.call(self, {_event: payload.call, _id: id, data: parsed.result}); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
Object.defineProperty(HttpRpcProvider.prototype, "onmessage", { |
||||
set: function (handler) { |
||||
this.handlers.push(handler); |
||||
} |
||||
}); |
||||
|
||||
module.exports = HttpRpcProvider; |
@ -0,0 +1,494 @@ |
||||
/* |
||||
This file is part of ethereum.js. |
||||
|
||||
ethereum.js is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU Lesser General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
ethereum.js is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU Lesser General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License |
||||
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
/** @file main.js |
||||
* @authors: |
||||
* Jeffrey Wilcke <jeff@ethdev.com> |
||||
* Marek Kotewicz <marek@ethdev.com> |
||||
* Marian Oancea <marian@ethdev.com> |
||||
* Gav Wood <g@ethdev.com> |
||||
* @date 2014 |
||||
*/ |
||||
|
||||
function flattenPromise (obj) { |
||||
if (obj instanceof Promise) { |
||||
return Promise.resolve(obj); |
||||
} |
||||
|
||||
if (obj instanceof Array) { |
||||
return new Promise(function (resolve) { |
||||
var promises = obj.map(function (o) { |
||||
return flattenPromise(o); |
||||
}); |
||||
|
||||
return Promise.all(promises).then(function (res) { |
||||
for (var i = 0; i < obj.length; i++) { |
||||
obj[i] = res[i]; |
||||
} |
||||
resolve(obj); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
if (obj instanceof Object) { |
||||
return new Promise(function (resolve) { |
||||
var keys = Object.keys(obj); |
||||
var promises = keys.map(function (key) { |
||||
return flattenPromise(obj[key]); |
||||
}); |
||||
|
||||
return Promise.all(promises).then(function (res) { |
||||
for (var i = 0; i < keys.length; i++) { |
||||
obj[keys[i]] = res[i]; |
||||
} |
||||
resolve(obj); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
return Promise.resolve(obj); |
||||
} |
||||
|
||||
var web3Methods = function () { |
||||
return [ |
||||
{ name: 'sha3', call: 'web3_sha3' } |
||||
]; |
||||
}; |
||||
|
||||
var ethMethods = function () { |
||||
var blockCall = function (args) { |
||||
return typeof args[0] === "string" ? "eth_blockByHash" : "eth_blockByNumber"; |
||||
}; |
||||
|
||||
var transactionCall = function (args) { |
||||
return typeof args[0] === "string" ? 'eth_transactionByHash' : 'eth_transactionByNumber'; |
||||
}; |
||||
|
||||
var uncleCall = function (args) { |
||||
return typeof args[0] === "string" ? 'eth_uncleByHash' : 'eth_uncleByNumber'; |
||||
}; |
||||
|
||||
var methods = [ |
||||
{ name: 'balanceAt', call: 'eth_balanceAt' }, |
||||
{ name: 'stateAt', call: 'eth_stateAt' }, |
||||
{ name: 'storageAt', call: 'eth_storageAt' }, |
||||
{ name: 'countAt', call: 'eth_countAt'}, |
||||
{ name: 'codeAt', call: 'eth_codeAt' }, |
||||
{ name: 'transact', call: 'eth_transact' }, |
||||
{ name: 'call', call: 'eth_call' }, |
||||
{ name: 'block', call: blockCall }, |
||||
{ name: 'transaction', call: transactionCall }, |
||||
{ name: 'uncle', call: uncleCall }, |
||||
{ name: 'compilers', call: 'eth_compilers' }, |
||||
{ name: 'lll', call: 'eth_lll' }, |
||||
{ name: 'solidity', call: 'eth_solidity' }, |
||||
{ name: 'serpent', call: 'eth_serpent' }, |
||||
{ name: 'logs', call: 'eth_logs' } |
||||
]; |
||||
return methods; |
||||
}; |
||||
|
||||
var ethProperties = function () { |
||||
return [ |
||||
{ name: 'coinbase', getter: 'eth_coinbase', setter: 'eth_setCoinbase' }, |
||||
{ name: 'listening', getter: 'eth_listening', setter: 'eth_setListening' }, |
||||
{ name: 'mining', getter: 'eth_mining', setter: 'eth_setMining' }, |
||||
{ name: 'gasPrice', getter: 'eth_gasPrice' }, |
||||
{ name: 'account', getter: 'eth_account' }, |
||||
{ name: 'accounts', getter: 'eth_accounts' }, |
||||
{ name: 'peerCount', getter: 'eth_peerCount' }, |
||||
{ name: 'defaultBlock', getter: 'eth_defaultBlock', setter: 'eth_setDefaultBlock' }, |
||||
{ name: 'number', getter: 'eth_number'} |
||||
]; |
||||
}; |
||||
|
||||
var dbMethods = function () { |
||||
return [ |
||||
{ name: 'put', call: 'db_put' }, |
||||
{ name: 'get', call: 'db_get' }, |
||||
{ name: 'putString', call: 'db_putString' }, |
||||
{ name: 'getString', call: 'db_getString' } |
||||
]; |
||||
}; |
||||
|
||||
var shhMethods = function () { |
||||
return [ |
||||
{ name: 'post', call: 'shh_post' }, |
||||
{ name: 'newIdentity', call: 'shh_newIdentity' }, |
||||
{ name: 'haveIdentity', call: 'shh_haveIdentity' }, |
||||
{ name: 'newGroup', call: 'shh_newGroup' }, |
||||
{ name: 'addToGroup', call: 'shh_addToGroup' } |
||||
]; |
||||
}; |
||||
|
||||
var ethWatchMethods = function () { |
||||
var newFilter = function (args) { |
||||
return typeof args[0] === 'string' ? 'eth_newFilterString' : 'eth_newFilter'; |
||||
}; |
||||
|
||||
return [ |
||||
{ name: 'newFilter', call: newFilter }, |
||||
{ name: 'uninstallFilter', call: 'eth_uninstallFilter' }, |
||||
{ name: 'getMessages', call: 'eth_filterLogs' } |
||||
]; |
||||
}; |
||||
|
||||
var shhWatchMethods = function () { |
||||
return [ |
||||
{ name: 'newFilter', call: 'shh_newFilter' }, |
||||
{ name: 'uninstallFilter', call: 'shh_uninstallFilter' }, |
||||
{ name: 'getMessage', call: 'shh_getMessages' } |
||||
]; |
||||
}; |
||||
|
||||
var setupMethods = function (obj, methods) { |
||||
methods.forEach(function (method) { |
||||
obj[method.name] = function () { |
||||
return flattenPromise(Array.prototype.slice.call(arguments)).then(function (args) { |
||||
var call = typeof method.call === "function" ? method.call(args) : method.call; |
||||
return {call: call, args: args}; |
||||
}).then(function (request) { |
||||
return new Promise(function (resolve, reject) { |
||||
web3.provider.send(request, function (err, result) { |
||||
if (!err) { |
||||
resolve(result); |
||||
return; |
||||
} |
||||
reject(err); |
||||
}); |
||||
}); |
||||
}).catch(function(err) { |
||||
console.error(err); |
||||
}); |
||||
}; |
||||
}); |
||||
}; |
||||
|
||||
var setupProperties = function (obj, properties) { |
||||
properties.forEach(function (property) { |
||||
var proto = {}; |
||||
proto.get = function () { |
||||
return new Promise(function(resolve, reject) { |
||||
web3.provider.send({call: property.getter}, function(err, result) { |
||||
if (!err) { |
||||
resolve(result); |
||||
return; |
||||
} |
||||
reject(err); |
||||
}); |
||||
}); |
||||
}; |
||||
if (property.setter) { |
||||
proto.set = function (val) { |
||||
return flattenPromise([val]).then(function (args) { |
||||
return new Promise(function (resolve) { |
||||
web3.provider.send({call: property.setter, args: args}, function (err, result) { |
||||
if (!err) { |
||||
resolve(result); |
||||
return; |
||||
} |
||||
reject(err); |
||||
}); |
||||
}); |
||||
}).catch(function (err) { |
||||
console.error(err); |
||||
}); |
||||
}; |
||||
} |
||||
Object.defineProperty(obj, property.name, proto); |
||||
}); |
||||
}; |
||||
|
||||
// TODO: import from a dependency, don't duplicate.
|
||||
var hexToDec = function (hex) { |
||||
return parseInt(hex, 16).toString(); |
||||
}; |
||||
|
||||
var decToHex = function (dec) { |
||||
return parseInt(dec).toString(16); |
||||
}; |
||||
|
||||
|
||||
var web3 = { |
||||
_callbacks: {}, |
||||
_events: {}, |
||||
providers: {}, |
||||
|
||||
toAscii: function(hex) { |
||||
// Find termination
|
||||
var str = ""; |
||||
var i = 0, l = hex.length; |
||||
if (hex.substring(0, 2) === '0x') |
||||
i = 2; |
||||
for(; i < l; i+=2) { |
||||
var code = hex.charCodeAt(i); |
||||
if(code === 0) { |
||||
break; |
||||
} |
||||
|
||||
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); |
||||
} |
||||
|
||||
return str; |
||||
}, |
||||
|
||||
fromAscii: function(str, pad) { |
||||
pad = pad === undefined ? 32 : pad; |
||||
var hex = this.toHex(str); |
||||
while(hex.length < pad*2) |
||||
hex += "00"; |
||||
return "0x" + hex; |
||||
}, |
||||
|
||||
toDecimal: function (val) { |
||||
return hexToDec(val.substring(2)); |
||||
}, |
||||
|
||||
fromDecimal: function (val) { |
||||
return "0x" + decToHex(val); |
||||
}, |
||||
|
||||
toEth: function(str) { |
||||
var val = typeof str === "string" ? str.indexOf('0x') == 0 ? parseInt(str.substr(2), 16) : parseInt(str) : str; |
||||
var unit = 0; |
||||
var units = [ 'wei', 'Kwei', 'Mwei', 'Gwei', 'szabo', 'finney', 'ether', 'grand', 'Mether', 'Gether', 'Tether', 'Pether', 'Eether', 'Zether', 'Yether', 'Nether', 'Dether', 'Vether', 'Uether' ]; |
||||
while (val > 3000 && unit < units.length - 1) |
||||
{ |
||||
val /= 1000; |
||||
unit++; |
||||
} |
||||
var s = val.toString().length < val.toFixed(2).length ? val.toString() : val.toFixed(2); |
||||
while (true) { |
||||
var o = s; |
||||
s = s.replace(/(\d)(\d\d\d[\.\,])/, function($0, $1, $2) { return $1 + ',' + $2; }); |
||||
if (o == s) |
||||
break; |
||||
} |
||||
return s + ' ' + units[unit]; |
||||
}, |
||||
|
||||
eth: { |
||||
prototype: Object(), // jshint ignore:line
|
||||
watch: function (params) { |
||||
return new Filter(params, ethWatch); |
||||
} |
||||
}, |
||||
|
||||
db: { |
||||
prototype: Object() // jshint ignore:line
|
||||
}, |
||||
|
||||
shh: { |
||||
prototype: Object(), // jshint ignore:line
|
||||
watch: function (params) { |
||||
return new Filter(params, shhWatch); |
||||
} |
||||
}, |
||||
|
||||
on: function(event, id, cb) { |
||||
if(web3._events[event] === undefined) { |
||||
web3._events[event] = {}; |
||||
} |
||||
|
||||
web3._events[event][id] = cb; |
||||
return this; |
||||
}, |
||||
|
||||
off: function(event, id) { |
||||
if(web3._events[event] !== undefined) { |
||||
delete web3._events[event][id]; |
||||
} |
||||
|
||||
return this; |
||||
}, |
||||
|
||||
trigger: function(event, id, data) { |
||||
var callbacks = web3._events[event]; |
||||
if (!callbacks || !callbacks[id]) { |
||||
return; |
||||
} |
||||
var cb = callbacks[id]; |
||||
cb(data); |
||||
} |
||||
}; |
||||
|
||||
setupMethods(web3, web3Methods()); |
||||
setupMethods(web3.eth, ethMethods()); |
||||
setupProperties(web3.eth, ethProperties()); |
||||
setupMethods(web3.db, dbMethods()); |
||||
setupMethods(web3.shh, shhMethods()); |
||||
|
||||
var ethWatch = { |
||||
changed: 'eth_changed' |
||||
}; |
||||
setupMethods(ethWatch, ethWatchMethods()); |
||||
var shhWatch = { |
||||
changed: 'shh_changed' |
||||
}; |
||||
setupMethods(shhWatch, shhWatchMethods()); |
||||
|
||||
var ProviderManager = function() { |
||||
this.queued = []; |
||||
this.polls = []; |
||||
this.ready = false; |
||||
this.provider = undefined; |
||||
this.id = 1; |
||||
|
||||
var self = this; |
||||
var poll = function () { |
||||
if (self.provider && self.provider.poll) { |
||||
self.polls.forEach(function (data) { |
||||
data.data._id = self.id; |
||||
self.id++; |
||||
self.provider.poll(data.data, data.id); |
||||
}); |
||||
} |
||||
setTimeout(poll, 12000); |
||||
}; |
||||
poll(); |
||||
}; |
||||
|
||||
ProviderManager.prototype.send = function(data, cb) { |
||||
data._id = this.id; |
||||
if (cb) { |
||||
web3._callbacks[data._id] = cb; |
||||
} |
||||
|
||||
data.args = data.args || []; |
||||
this.id++; |
||||
|
||||
if(this.provider !== undefined) { |
||||
this.provider.send(data); |
||||
} else { |
||||
console.warn("provider is not set"); |
||||
this.queued.push(data); |
||||
} |
||||
}; |
||||
|
||||
ProviderManager.prototype.set = function(provider) { |
||||
if(this.provider !== undefined && this.provider.unload !== undefined) { |
||||
this.provider.unload(); |
||||
} |
||||
|
||||
this.provider = provider; |
||||
this.ready = true; |
||||
}; |
||||
|
||||
ProviderManager.prototype.sendQueued = function() { |
||||
for(var i = 0; this.queued.length; i++) { |
||||
// Resend
|
||||
this.send(this.queued[i]); |
||||
} |
||||
}; |
||||
|
||||
ProviderManager.prototype.installed = function() { |
||||
return this.provider !== undefined; |
||||
}; |
||||
|
||||
ProviderManager.prototype.startPolling = function (data, pollId) { |
||||
if (!this.provider || !this.provider.poll) { |
||||
return; |
||||
} |
||||
this.polls.push({data: data, id: pollId}); |
||||
}; |
||||
|
||||
ProviderManager.prototype.stopPolling = function (pollId) { |
||||
for (var i = this.polls.length; i--;) { |
||||
var poll = this.polls[i]; |
||||
if (poll.id === pollId) { |
||||
this.polls.splice(i, 1); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
web3.provider = new ProviderManager(); |
||||
|
||||
web3.setProvider = function(provider) { |
||||
provider.onmessage = messageHandler; |
||||
web3.provider.set(provider); |
||||
web3.provider.sendQueued(); |
||||
}; |
||||
|
||||
web3.haveProvider = function() { |
||||
return !!web3.provider.provider; |
||||
}; |
||||
|
||||
var Filter = function(options, impl) { |
||||
this.impl = impl; |
||||
this.callbacks = []; |
||||
|
||||
var self = this; |
||||
this.promise = impl.newFilter(options); |
||||
this.promise.then(function (id) { |
||||
self.id = id; |
||||
web3.on(impl.changed, id, self.trigger.bind(self)); |
||||
web3.provider.startPolling({call: impl.changed, args: [id]}, id); |
||||
}); |
||||
}; |
||||
|
||||
Filter.prototype.arrived = function(callback) { |
||||
this.changed(callback); |
||||
}; |
||||
|
||||
Filter.prototype.changed = function(callback) { |
||||
var self = this; |
||||
this.promise.then(function(id) { |
||||
self.callbacks.push(callback); |
||||
}); |
||||
}; |
||||
|
||||
Filter.prototype.trigger = function(messages) { |
||||
for(var i = 0; i < this.callbacks.length; i++) { |
||||
this.callbacks[i].call(this, messages); |
||||
} |
||||
}; |
||||
|
||||
Filter.prototype.uninstall = function() { |
||||
var self = this; |
||||
this.promise.then(function (id) { |
||||
self.impl.uninstallFilter(id); |
||||
web3.provider.stopPolling(id); |
||||
web3.off(impl.changed, id); |
||||
}); |
||||
}; |
||||
|
||||
Filter.prototype.messages = function() { |
||||
var self = this; |
||||
return this.promise.then(function (id) { |
||||
return self.impl.getMessages(id); |
||||
}); |
||||
}; |
||||
|
||||
Filter.prototype.logs = function () { |
||||
return this.messages(); |
||||
}; |
||||
|
||||
function messageHandler(data) { |
||||
if(data._event !== undefined) { |
||||
web3.trigger(data._event, data._id, data.data); |
||||
return; |
||||
} |
||||
|
||||
if(data._id) { |
||||
var cb = web3._callbacks[data._id]; |
||||
if (cb) { |
||||
cb.call(this, data.error, data.data); |
||||
delete web3._callbacks[data._id]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
module.exports = web3; |
@ -0,0 +1,45 @@ |
||||
/* |
||||
This file is part of ethereum.js. |
||||
|
||||
ethereum.js is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU Lesser General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
ethereum.js is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU Lesser General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License |
||||
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
/** @file qt.js |
||||
* @authors: |
||||
* Jeffrey Wilcke <jeff@ethdev.com> |
||||
* Marek Kotewicz <marek@ethdev.com> |
||||
* @date 2014 |
||||
*/ |
||||
|
||||
var QtProvider = function() { |
||||
this.handlers = []; |
||||
|
||||
var self = this; |
||||
navigator.qt.onmessage = function (message) { |
||||
self.handlers.forEach(function (handler) { |
||||
handler.call(self, JSON.parse(message.data)); |
||||
}); |
||||
}; |
||||
}; |
||||
|
||||
QtProvider.prototype.send = function(payload) { |
||||
navigator.qt.postMessage(JSON.stringify(payload)); |
||||
}; |
||||
|
||||
Object.defineProperty(QtProvider.prototype, "onmessage", { |
||||
set: function(handler) { |
||||
this.handlers.push(handler); |
||||
} |
||||
}); |
||||
|
||||
module.exports = QtProvider; |
@ -0,0 +1,78 @@ |
||||
/* |
||||
This file is part of ethereum.js. |
||||
|
||||
ethereum.js is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU Lesser General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
ethereum.js is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU Lesser General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License |
||||
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
/** @file websocket.js |
||||
* @authors: |
||||
* Jeffrey Wilcke <jeff@ethdev.com> |
||||
* Marek Kotewicz <marek@ethdev.com> |
||||
* Marian Oancea <marian@ethdev.com> |
||||
* @date 2014 |
||||
*/ |
||||
|
||||
// TODO: work out which of the following two lines it is supposed to be...
|
||||
//if (process.env.NODE_ENV !== 'build') {
|
||||
if ("build" !== "build") {/* |
||||
var WebSocket = require('ws'); // jshint ignore:line
|
||||
*/} |
||||
|
||||
var WebSocketProvider = function(host) { |
||||
// onmessage handlers
|
||||
this.handlers = []; |
||||
// queue will be filled with messages if send is invoked before the ws is ready
|
||||
this.queued = []; |
||||
this.ready = false; |
||||
|
||||
this.ws = new WebSocket(host); |
||||
|
||||
var self = this; |
||||
this.ws.onmessage = function(event) { |
||||
for(var i = 0; i < self.handlers.length; i++) { |
||||
self.handlers[i].call(self, JSON.parse(event.data), event); |
||||
} |
||||
}; |
||||
|
||||
this.ws.onopen = function() { |
||||
self.ready = true; |
||||
|
||||
for(var i = 0; i < self.queued.length; i++) { |
||||
// Resend
|
||||
self.send(self.queued[i]); |
||||
} |
||||
}; |
||||
}; |
||||
|
||||
WebSocketProvider.prototype.send = function(payload) { |
||||
if(this.ready) { |
||||
var data = JSON.stringify(payload); |
||||
|
||||
this.ws.send(data); |
||||
} else { |
||||
this.queued.push(payload); |
||||
} |
||||
}; |
||||
|
||||
WebSocketProvider.prototype.onMessage = function(handler) { |
||||
this.handlers.push(handler); |
||||
}; |
||||
|
||||
WebSocketProvider.prototype.unload = function() { |
||||
this.ws.close(); |
||||
}; |
||||
Object.defineProperty(WebSocketProvider.prototype, "onmessage", { |
||||
set: function(provider) { this.onMessage(provider); } |
||||
}); |
||||
|
||||
module.exports = WebSocketProvider; |
@ -0,0 +1,67 @@ |
||||
{ |
||||
"name": "ethereum.js", |
||||
"namespace": "ethereum", |
||||
"version": "0.0.5", |
||||
"description": "Ethereum Compatible JavaScript API", |
||||
"main": "./index.js", |
||||
"directories": { |
||||
"lib": "./lib" |
||||
}, |
||||
"dependencies": { |
||||
"es6-promise": "*", |
||||
"ws": "*", |
||||
"xmlhttprequest": "*" |
||||
}, |
||||
"devDependencies": { |
||||
"bower": ">=1.3.0", |
||||
"browserify": ">=6.0", |
||||
"del": ">=0.1.1", |
||||
"envify": "^3.0.0", |
||||
"exorcist": "^0.1.6", |
||||
"gulp": ">=3.4.0", |
||||
"gulp-jshint": ">=1.5.0", |
||||
"gulp-rename": ">=1.2.0", |
||||
"gulp-uglify": ">=1.0.0", |
||||
"jshint": ">=2.5.0", |
||||
"uglifyify": "^2.6.0", |
||||
"unreachable-branch-transform": "^0.1.0", |
||||
"vinyl-source-stream": "^1.0.0" |
||||
}, |
||||
"scripts": { |
||||
"build": "gulp", |
||||
"watch": "gulp watch", |
||||
"lint": "gulp lint" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "https://github.com/ethereum/ethereum.js.git" |
||||
}, |
||||
"homepage": "https://github.com/ethereum/ethereum.js", |
||||
"bugs": { |
||||
"url": "https://github.com/ethereum/ethereum.js/issues" |
||||
}, |
||||
"keywords": [ |
||||
"ethereum", |
||||
"javascript", |
||||
"API" |
||||
], |
||||
"author": "ethdev.com", |
||||
"authors": [ |
||||
{ |
||||
"name": "Jeffery Wilcke", |
||||
"email": "jeff@ethdev.com", |
||||
"url": "https://github.com/obscuren" |
||||
}, |
||||
{ |
||||
"name": "Marek Kotewicz", |
||||
"email": "marek@ethdev.com", |
||||
"url": "https://github.com/debris" |
||||
}, |
||||
{ |
||||
"name": "Marian Oancea", |
||||
"email": "marian@ethdev.com", |
||||
"url": "https://github.com/cubedro" |
||||
} |
||||
], |
||||
"license": "LGPL-3.0" |
||||
} |
@ -0,0 +1,76 @@ |
||||
|
||||
import QtQuick 2.0 |
||||
import QtQuick.Controls 1.0; |
||||
import QtQuick.Layouts 1.0; |
||||
import QtQuick.Dialogs 1.0; |
||||
import QtQuick.Window 2.1; |
||||
import QtQuick.Controls.Styles 1.1 |
||||
import Ethereum 1.0 |
||||
|
||||
Rectangle { |
||||
id: root |
||||
property var title: "Whisper Traffic" |
||||
property var iconSource: "../facet.png" |
||||
property var menuItem |
||||
|
||||
objectName: "whisperView" |
||||
anchors.fill: parent |
||||
|
||||
property var identity: "" |
||||
Component.onCompleted: { |
||||
identity = shh.newIdentity() |
||||
console.log("New identity:", identity) |
||||
|
||||
var t = shh.watch({}, root) |
||||
} |
||||
|
||||
function onShhMessage(message, i) { |
||||
whisperModel.insert(0, {from: message.from, payload: eth.toAscii(message.payload)}) |
||||
} |
||||
|
||||
RowLayout { |
||||
id: input |
||||
anchors { |
||||
left: parent.left |
||||
leftMargin: 20 |
||||
top: parent.top |
||||
topMargin: 20 |
||||
} |
||||
|
||||
TextField { |
||||
id: to |
||||
placeholderText: "To" |
||||
} |
||||
TextField { |
||||
id: data |
||||
placeholderText: "Data" |
||||
} |
||||
TextField { |
||||
id: topics |
||||
placeholderText: "topic1, topic2, topic3, ..." |
||||
} |
||||
Button { |
||||
text: "Send" |
||||
onClicked: { |
||||
shh.post([eth.toHex(data.text)], "", identity, topics.text.split(","), 500, 50) |
||||
} |
||||
} |
||||
} |
||||
|
||||
TableView { |
||||
id: txTableView |
||||
anchors { |
||||
top: input.bottom |
||||
topMargin: 10 |
||||
bottom: parent.bottom |
||||
left: parent.left |
||||
right: parent.right |
||||
} |
||||
TableViewColumn{ id: fromRole; role: "from" ; title: "From"; width: 300 } |
||||
TableViewColumn{ role: "payload" ; title: "Payload" ; width: parent.width - fromRole.width - 2 } |
||||
|
||||
model: ListModel { |
||||
id: whisperModel |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package types |
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
"github.com/ethereum/go-ethereum/ethutil" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
func init() { |
||||
ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "") |
||||
ethutil.Config.Db, _ = ethdb.NewMemDatabase() |
||||
} |
||||
|
||||
func TestNewBlock(t *testing.T) { |
||||
block := GenesisBlock() |
||||
data := ethutil.Encode(block) |
||||
|
||||
var genesis Block |
||||
err := rlp.Decode(bytes.NewReader(data), &genesis) |
||||
} |
@ -0,0 +1,249 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"net" |
||||
"sync" |
||||
|
||||
"github.com/ethereum/go-ethereum/core" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/ethutil" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
ethlogger "github.com/ethereum/go-ethereum/logger" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/pow/ezp" |
||||
"github.com/ethereum/go-ethereum/rpc" |
||||
"github.com/ethereum/go-ethereum/whisper" |
||||
) |
||||
|
||||
const ( |
||||
seedNodeAddress = "poc-7.ethdev.com:30300" |
||||
) |
||||
|
||||
var logger = ethlogger.NewLogger("SERV") |
||||
|
||||
type Ethereum struct { |
||||
// Channel for shutting down the ethereum
|
||||
shutdownChan chan bool |
||||
quit chan bool |
||||
|
||||
// DB interface
|
||||
db ethutil.Database |
||||
blacklist p2p.Blacklist |
||||
|
||||
//*** SERVICES ***
|
||||
// State manager for processing new blocks and managing the over all states
|
||||
blockManager *core.BlockManager |
||||
txPool *core.TxPool |
||||
chainManager *core.ChainManager |
||||
blockPool *BlockPool |
||||
whisper *whisper.Whisper |
||||
|
||||
server *p2p.Server |
||||
eventMux *event.TypeMux |
||||
txSub event.Subscription |
||||
blockSub event.Subscription |
||||
|
||||
RpcServer *rpc.JsonRpcServer |
||||
keyManager *crypto.KeyManager |
||||
|
||||
clientIdentity p2p.ClientIdentity |
||||
|
||||
synclock sync.Mutex |
||||
syncGroup sync.WaitGroup |
||||
|
||||
Mining bool |
||||
} |
||||
|
||||
func New(db ethutil.Database, identity p2p.ClientIdentity, keyManager *crypto.KeyManager, nat p2p.NAT, port string, maxPeers int) (*Ethereum, error) { |
||||
|
||||
saveProtocolVersion(db) |
||||
ethutil.Config.Db = db |
||||
|
||||
eth := &Ethereum{ |
||||
shutdownChan: make(chan bool), |
||||
quit: make(chan bool), |
||||
db: db, |
||||
keyManager: keyManager, |
||||
clientIdentity: identity, |
||||
blacklist: p2p.NewBlacklist(), |
||||
eventMux: &event.TypeMux{}, |
||||
} |
||||
|
||||
eth.chainManager = core.NewChainManager(eth.EventMux()) |
||||
eth.txPool = core.NewTxPool(eth.chainManager, eth.EventMux()) |
||||
eth.blockManager = core.NewBlockManager(eth.txPool, eth.chainManager, eth.EventMux()) |
||||
eth.chainManager.SetProcessor(eth.blockManager) |
||||
eth.whisper = whisper.New() |
||||
|
||||
hasBlock := eth.chainManager.HasBlock |
||||
insertChain := eth.chainManager.InsertChain |
||||
eth.blockPool = NewBlockPool(hasBlock, insertChain, ezp.Verify) |
||||
|
||||
// Start services
|
||||
eth.txPool.Start() |
||||
|
||||
ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool) |
||||
protocols := []p2p.Protocol{ethProto, eth.whisper.Protocol()} |
||||
|
||||
server := &p2p.Server{ |
||||
Identity: identity, |
||||
MaxPeers: maxPeers, |
||||
Protocols: protocols, |
||||
ListenAddr: ":" + port, |
||||
Blacklist: eth.blacklist, |
||||
NAT: nat, |
||||
} |
||||
|
||||
eth.server = server |
||||
|
||||
return eth, nil |
||||
} |
||||
|
||||
func (s *Ethereum) KeyManager() *crypto.KeyManager { |
||||
return s.keyManager |
||||
} |
||||
|
||||
func (s *Ethereum) ClientIdentity() p2p.ClientIdentity { |
||||
return s.clientIdentity |
||||
} |
||||
|
||||
func (s *Ethereum) ChainManager() *core.ChainManager { |
||||
return s.chainManager |
||||
} |
||||
|
||||
func (s *Ethereum) BlockManager() *core.BlockManager { |
||||
return s.blockManager |
||||
} |
||||
|
||||
func (s *Ethereum) TxPool() *core.TxPool { |
||||
return s.txPool |
||||
} |
||||
|
||||
func (s *Ethereum) BlockPool() *BlockPool { |
||||
return s.blockPool |
||||
} |
||||
|
||||
func (s *Ethereum) Whisper() *whisper.Whisper { |
||||
return s.whisper |
||||
} |
||||
|
||||
func (s *Ethereum) EventMux() *event.TypeMux { |
||||
return s.eventMux |
||||
} |
||||
func (self *Ethereum) Db() ethutil.Database { |
||||
return self.db |
||||
} |
||||
|
||||
func (s *Ethereum) IsMining() bool { |
||||
return s.Mining |
||||
} |
||||
|
||||
func (s *Ethereum) IsListening() bool { |
||||
// XXX TODO
|
||||
return false |
||||
} |
||||
|
||||
func (s *Ethereum) PeerCount() int { |
||||
return s.server.PeerCount() |
||||
} |
||||
|
||||
func (s *Ethereum) Peers() []*p2p.Peer { |
||||
return s.server.Peers() |
||||
} |
||||
|
||||
func (s *Ethereum) MaxPeers() int { |
||||
return s.server.MaxPeers |
||||
} |
||||
|
||||
// Start the ethereum
|
||||
func (s *Ethereum) Start(seed bool) error { |
||||
err := s.server.Start() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
s.blockPool.Start() |
||||
s.whisper.Start() |
||||
|
||||
// broadcast transactions
|
||||
s.txSub = s.eventMux.Subscribe(core.TxPreEvent{}) |
||||
go s.txBroadcastLoop() |
||||
|
||||
// broadcast mined blocks
|
||||
s.blockSub = s.eventMux.Subscribe(core.NewMinedBlockEvent{}) |
||||
go s.blockBroadcastLoop() |
||||
|
||||
// TODO: read peers here
|
||||
if seed { |
||||
logger.Infof("Connect to seed node %v", seedNodeAddress) |
||||
if err := s.SuggestPeer(seedNodeAddress); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
logger.Infoln("Server started") |
||||
return nil |
||||
} |
||||
|
||||
func (self *Ethereum) SuggestPeer(addr string) error { |
||||
netaddr, err := net.ResolveTCPAddr("tcp", addr) |
||||
if err != nil { |
||||
logger.Errorf("couldn't resolve %s:", addr, err) |
||||
return err |
||||
} |
||||
|
||||
self.server.SuggestPeer(netaddr.IP, netaddr.Port, nil) |
||||
return nil |
||||
} |
||||
|
||||
func (s *Ethereum) Stop() { |
||||
// Close the database
|
||||
defer s.db.Close() |
||||
|
||||
close(s.quit) |
||||
|
||||
s.txSub.Unsubscribe() // quits txBroadcastLoop
|
||||
s.blockSub.Unsubscribe() // quits blockBroadcastLoop
|
||||
|
||||
if s.RpcServer != nil { |
||||
s.RpcServer.Stop() |
||||
} |
||||
s.txPool.Stop() |
||||
s.eventMux.Stop() |
||||
s.blockPool.Stop() |
||||
s.whisper.Stop() |
||||
|
||||
logger.Infoln("Server stopped") |
||||
close(s.shutdownChan) |
||||
} |
||||
|
||||
// This function will wait for a shutdown and resumes main thread execution
|
||||
func (s *Ethereum) WaitForShutdown() { |
||||
<-s.shutdownChan |
||||
} |
||||
|
||||
// now tx broadcasting is taken out of txPool
|
||||
// handled here via subscription, efficiency?
|
||||
func (self *Ethereum) txBroadcastLoop() { |
||||
// automatically stops if unsubscribe
|
||||
for obj := range self.txSub.Chan() { |
||||
event := obj.(core.TxPreEvent) |
||||
self.server.Broadcast("eth", TxMsg, []interface{}{event.Tx.RlpData()}) |
||||
} |
||||
} |
||||
|
||||
func (self *Ethereum) blockBroadcastLoop() { |
||||
// automatically stops if unsubscribe
|
||||
for obj := range self.txSub.Chan() { |
||||
event := obj.(core.NewMinedBlockEvent) |
||||
self.server.Broadcast("eth", NewBlockMsg, event.Block.RlpData()) |
||||
} |
||||
} |
||||
|
||||
func saveProtocolVersion(db ethutil.Database) { |
||||
d, _ := db.Get([]byte("ProtocolVersion")) |
||||
protocolVersion := ethutil.NewValue(d).Uint() |
||||
|
||||
if protocolVersion == 0 { |
||||
db.Put([]byte("ProtocolVersion"), ethutil.NewValue(ProtocolVersion).Bytes()) |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,198 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"sync" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/ethutil" |
||||
ethlogger "github.com/ethereum/go-ethereum/logger" |
||||
) |
||||
|
||||
var sys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugDetailLevel)) |
||||
|
||||
type testChainManager struct { |
||||
knownBlock func(hash []byte) bool |
||||
addBlock func(*types.Block) error |
||||
checkPoW func(*types.Block) bool |
||||
} |
||||
|
||||
func (self *testChainManager) KnownBlock(hash []byte) bool { |
||||
if self.knownBlock != nil { |
||||
return self.knownBlock(hash) |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func (self *testChainManager) AddBlock(block *types.Block) error { |
||||
if self.addBlock != nil { |
||||
return self.addBlock(block) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (self *testChainManager) CheckPoW(block *types.Block) bool { |
||||
if self.checkPoW != nil { |
||||
return self.checkPoW(block) |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func knownBlock(hashes ...[]byte) (f func([]byte) bool) { |
||||
f = func(block []byte) bool { |
||||
for _, hash := range hashes { |
||||
if bytes.Compare(block, hash) == 0 { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
return |
||||
} |
||||
|
||||
func addBlock(hashes ...[]byte) (f func(*types.Block) error) { |
||||
f = func(block *types.Block) error { |
||||
for _, hash := range hashes { |
||||
if bytes.Compare(block.Hash(), hash) == 0 { |
||||
return fmt.Errorf("invalid by test") |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
return |
||||
} |
||||
|
||||
func checkPoW(hashes ...[]byte) (f func(*types.Block) bool) { |
||||
f = func(block *types.Block) bool { |
||||
for _, hash := range hashes { |
||||
if bytes.Compare(block.Hash(), hash) == 0 { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
return |
||||
} |
||||
|
||||
func newTestChainManager(knownBlocks [][]byte, invalidBlocks [][]byte, invalidPoW [][]byte) *testChainManager { |
||||
return &testChainManager{ |
||||
knownBlock: knownBlock(knownBlocks...), |
||||
addBlock: addBlock(invalidBlocks...), |
||||
checkPoW: checkPoW(invalidPoW...), |
||||
} |
||||
} |
||||
|
||||
type intToHash map[int][]byte |
||||
|
||||
type hashToInt map[string]int |
||||
|
||||
type testHashPool struct { |
||||
intToHash |
||||
hashToInt |
||||
} |
||||
|
||||
func newHash(i int) []byte { |
||||
return crypto.Sha3([]byte(string(i))) |
||||
} |
||||
|
||||
func newTestBlockPool(knownBlockIndexes []int, invalidBlockIndexes []int, invalidPoWIndexes []int) (hashPool *testHashPool, blockPool *BlockPool) { |
||||
hashPool = &testHashPool{make(intToHash), make(hashToInt)} |
||||
knownBlocks := hashPool.indexesToHashes(knownBlockIndexes) |
||||
invalidBlocks := hashPool.indexesToHashes(invalidBlockIndexes) |
||||
invalidPoW := hashPool.indexesToHashes(invalidPoWIndexes) |
||||
blockPool = NewBlockPool(newTestChainManager(knownBlocks, invalidBlocks, invalidPoW)) |
||||
return |
||||
} |
||||
|
||||
func (self *testHashPool) indexesToHashes(indexes []int) (hashes [][]byte) { |
||||
for _, i := range indexes { |
||||
hash, found := self.intToHash[i] |
||||
if !found { |
||||
hash = newHash(i) |
||||
self.intToHash[i] = hash |
||||
self.hashToInt[string(hash)] = i |
||||
} |
||||
hashes = append(hashes, hash) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (self *testHashPool) hashesToIndexes(hashes [][]byte) (indexes []int) { |
||||
for _, hash := range hashes { |
||||
i, found := self.hashToInt[string(hash)] |
||||
if !found { |
||||
i = -1 |
||||
} |
||||
indexes = append(indexes, i) |
||||
} |
||||
return |
||||
} |
||||
|
||||
type protocolChecker struct { |
||||
blockHashesRequests []int |
||||
blocksRequests [][]int |
||||
invalidBlocks []error |
||||
hashPool *testHashPool |
||||
lock sync.Mutex |
||||
} |
||||
|
||||
// -1 is special: not found (a hash never seen)
|
||||
func (self *protocolChecker) requestBlockHashesCallBack() (requestBlockHashesCallBack func([]byte) error) { |
||||
requestBlockHashesCallBack = func(hash []byte) error { |
||||
indexes := self.hashPool.hashesToIndexes([][]byte{hash}) |
||||
self.lock.Lock() |
||||
defer self.lock.Unlock() |
||||
self.blockHashesRequests = append(self.blockHashesRequests, indexes[0]) |
||||
return nil |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (self *protocolChecker) requestBlocksCallBack() (requestBlocksCallBack func([][]byte) error) { |
||||
requestBlocksCallBack = func(hashes [][]byte) error { |
||||
indexes := self.hashPool.hashesToIndexes(hashes) |
||||
self.lock.Lock() |
||||
defer self.lock.Unlock() |
||||
self.blocksRequests = append(self.blocksRequests, indexes) |
||||
return nil |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (self *protocolChecker) invalidBlockCallBack() (invalidBlockCallBack func(error)) { |
||||
invalidBlockCallBack = func(err error) { |
||||
self.invalidBlocks = append(self.invalidBlocks, err) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func TestAddPeer(t *testing.T) { |
||||
ethlogger.AddLogSystem(sys) |
||||
knownBlockIndexes := []int{0, 1} |
||||
invalidBlockIndexes := []int{2, 3} |
||||
invalidPoWIndexes := []int{4, 5} |
||||
hashPool, blockPool := newTestBlockPool(knownBlockIndexes, invalidBlockIndexes, invalidPoWIndexes) |
||||
// TODO:
|
||||
// hashPool, blockPool, blockChainChecker = newTestBlockPool(knownBlockIndexes, invalidBlockIndexes, invalidPoWIndexes)
|
||||
peer0 := &protocolChecker{ |
||||
// blockHashesRequests: make([]int),
|
||||
// blocksRequests: make([][]int),
|
||||
// invalidBlocks: make([]error),
|
||||
hashPool: hashPool, |
||||
} |
||||
best := blockPool.AddPeer(ethutil.Big1, newHash(100), "0", |
||||
peer0.requestBlockHashesCallBack(), |
||||
peer0.requestBlocksCallBack(), |
||||
peer0.invalidBlockCallBack(), |
||||
) |
||||
if !best { |
||||
t.Errorf("peer not accepted as best") |
||||
} |
||||
blockPool.Stop() |
||||
|
||||
} |
@ -0,0 +1,71 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"fmt" |
||||
) |
||||
|
||||
const ( |
||||
ErrMsgTooLarge = iota |
||||
ErrDecode |
||||
ErrInvalidMsgCode |
||||
ErrProtocolVersionMismatch |
||||
ErrNetworkIdMismatch |
||||
ErrGenesisBlockMismatch |
||||
ErrNoStatusMsg |
||||
ErrExtraStatusMsg |
||||
ErrInvalidBlock |
||||
ErrInvalidPoW |
||||
ErrUnrequestedBlock |
||||
) |
||||
|
||||
var errorToString = map[int]string{ |
||||
ErrMsgTooLarge: "Message too long", |
||||
ErrDecode: "Invalid message", |
||||
ErrInvalidMsgCode: "Invalid message code", |
||||
ErrProtocolVersionMismatch: "Protocol version mismatch", |
||||
ErrNetworkIdMismatch: "NetworkId mismatch", |
||||
ErrGenesisBlockMismatch: "Genesis block mismatch", |
||||
ErrNoStatusMsg: "No status message", |
||||
ErrExtraStatusMsg: "Extra status message", |
||||
ErrInvalidBlock: "Invalid block", |
||||
ErrInvalidPoW: "Invalid PoW", |
||||
ErrUnrequestedBlock: "Unrequested block", |
||||
} |
||||
|
||||
type protocolError struct { |
||||
Code int |
||||
fatal bool |
||||
message string |
||||
format string |
||||
params []interface{} |
||||
// size int
|
||||
} |
||||
|
||||
func newProtocolError(code int, format string, params ...interface{}) *protocolError { |
||||
return &protocolError{Code: code, format: format, params: params} |
||||
} |
||||
|
||||
func ProtocolError(code int, format string, params ...interface{}) (err *protocolError) { |
||||
err = newProtocolError(code, format, params...) |
||||
// report(err)
|
||||
return |
||||
} |
||||
|
||||
func (self protocolError) Error() (message string) { |
||||
message = self.message |
||||
if message == "" { |
||||
message, ok := errorToString[self.Code] |
||||
if !ok { |
||||
panic("invalid error code") |
||||
} |
||||
if self.format != "" { |
||||
message += ": " + fmt.Sprintf(self.format, self.params...) |
||||
} |
||||
self.message = message |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (self *protocolError) Fatal() bool { |
||||
return self.fatal |
||||
} |
@ -0,0 +1,23 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
|
||||
"github.com/ethereum/go-ethereum/ethutil" |
||||
) |
||||
|
||||
func WritePeers(path string, addresses []string) { |
||||
if len(addresses) > 0 { |
||||
data, _ := json.MarshalIndent(addresses, "", " ") |
||||
ethutil.WriteFile(path, data) |
||||
} |
||||
} |
||||
|
||||
func ReadPeers(path string) (ips []string, err error) { |
||||
var data string |
||||
data, err = ethutil.ReadAllFile(path) |
||||
if err != nil { |
||||
json.Unmarshal([]byte(data), &ips) |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,319 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"math" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/ethutil" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
const ( |
||||
ProtocolVersion = 49 |
||||
NetworkId = 0 |
||||
ProtocolLength = uint64(8) |
||||
ProtocolMaxMsgSize = 10 * 1024 * 1024 |
||||
) |
||||
|
||||
// eth protocol message codes
|
||||
const ( |
||||
StatusMsg = iota |
||||
GetTxMsg // unused
|
||||
TxMsg |
||||
GetBlockHashesMsg |
||||
BlockHashesMsg |
||||
GetBlocksMsg |
||||
BlocksMsg |
||||
NewBlockMsg |
||||
) |
||||
|
||||
// ethProtocol represents the ethereum wire protocol
|
||||
// instance is running on each peer
|
||||
type ethProtocol struct { |
||||
txPool txPool |
||||
chainManager chainManager |
||||
blockPool blockPool |
||||
peer *p2p.Peer |
||||
id string |
||||
rw p2p.MsgReadWriter |
||||
} |
||||
|
||||
// backend is the interface the ethereum protocol backend should implement
|
||||
// used as an argument to EthProtocol
|
||||
type txPool interface { |
||||
AddTransactions([]*types.Transaction) |
||||
} |
||||
|
||||
type chainManager interface { |
||||
GetBlockHashesFromHash(hash []byte, amount uint64) (hashes [][]byte) |
||||
GetBlock(hash []byte) (block *types.Block) |
||||
Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) |
||||
} |
||||
|
||||
type blockPool interface { |
||||
AddBlockHashes(next func() ([]byte, bool), peerId string) |
||||
AddBlock(block *types.Block, peerId string) |
||||
AddPeer(td *big.Int, currentBlock []byte, peerId string, requestHashes func([]byte) error, requestBlocks func([][]byte) error, peerError func(int, string, ...interface{})) (best bool) |
||||
RemovePeer(peerId string) |
||||
} |
||||
|
||||
// message structs used for rlp decoding
|
||||
type newBlockMsgData struct { |
||||
Block *types.Block |
||||
TD *big.Int |
||||
} |
||||
|
||||
type getBlockHashesMsgData struct { |
||||
Hash []byte |
||||
Amount uint64 |
||||
} |
||||
|
||||
// main entrypoint, wrappers starting a server running the eth protocol
|
||||
// use this constructor to attach the protocol ("class") to server caps
|
||||
// the Dev p2p layer then runs the protocol instance on each peer
|
||||
func EthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool) p2p.Protocol { |
||||
return p2p.Protocol{ |
||||
Name: "eth", |
||||
Version: ProtocolVersion, |
||||
Length: ProtocolLength, |
||||
Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error { |
||||
return runEthProtocol(txPool, chainManager, blockPool, peer, rw) |
||||
}, |
||||
} |
||||
} |
||||
|
||||
// the main loop that handles incoming messages
|
||||
// note RemovePeer in the post-disconnect hook
|
||||
func runEthProtocol(txPool txPool, chainManager chainManager, blockPool blockPool, peer *p2p.Peer, rw p2p.MsgReadWriter) (err error) { |
||||
self := ðProtocol{ |
||||
txPool: txPool, |
||||
chainManager: chainManager, |
||||
blockPool: blockPool, |
||||
rw: rw, |
||||
peer: peer, |
||||
id: (string)(peer.Identity().Pubkey()), |
||||
} |
||||
err = self.handleStatus() |
||||
if err == nil { |
||||
for { |
||||
err = self.handle() |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
self.blockPool.RemovePeer(self.id) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (self *ethProtocol) handle() error { |
||||
msg, err := self.rw.ReadMsg() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if msg.Size > ProtocolMaxMsgSize { |
||||
return ProtocolError(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) |
||||
} |
||||
// make sure that the payload has been fully consumed
|
||||
defer msg.Discard() |
||||
|
||||
switch msg.Code { |
||||
|
||||
case StatusMsg: |
||||
return ProtocolError(ErrExtraStatusMsg, "") |
||||
|
||||
case TxMsg: |
||||
// TODO: rework using lazy RLP stream
|
||||
var txs []*types.Transaction |
||||
if err := msg.Decode(&txs); err != nil { |
||||
return ProtocolError(ErrDecode, "%v", err) |
||||
} |
||||
self.txPool.AddTransactions(txs) |
||||
|
||||
case GetBlockHashesMsg: |
||||
var request getBlockHashesMsgData |
||||
if err := msg.Decode(&request); err != nil { |
||||
return ProtocolError(ErrDecode, "%v", err) |
||||
} |
||||
hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) |
||||
return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) |
||||
|
||||
case BlockHashesMsg: |
||||
// TODO: redo using lazy decode , this way very inefficient on known chains
|
||||
msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) |
||||
var err error |
||||
iter := func() (hash []byte, ok bool) { |
||||
hash, err = msgStream.Bytes() |
||||
if err == nil { |
||||
ok = true |
||||
} |
||||
return |
||||
} |
||||
self.blockPool.AddBlockHashes(iter, self.id) |
||||
if err != nil && err != rlp.EOL { |
||||
return ProtocolError(ErrDecode, "%v", err) |
||||
} |
||||
|
||||
case GetBlocksMsg: |
||||
var blockHashes [][]byte |
||||
if err := msg.Decode(&blockHashes); err != nil { |
||||
return ProtocolError(ErrDecode, "%v", err) |
||||
} |
||||
max := int(math.Min(float64(len(blockHashes)), blockHashesBatchSize)) |
||||
var blocks []interface{} |
||||
for i, hash := range blockHashes { |
||||
if i >= max { |
||||
break |
||||
} |
||||
block := self.chainManager.GetBlock(hash) |
||||
if block != nil { |
||||
blocks = append(blocks, block.RlpData()) |
||||
} |
||||
} |
||||
return self.rw.EncodeMsg(BlocksMsg, blocks...) |
||||
|
||||
case BlocksMsg: |
||||
msgStream := rlp.NewListStream(msg.Payload, uint64(msg.Size)) |
||||
for { |
||||
var block *types.Block |
||||
if err := msgStream.Decode(&block); err != nil { |
||||
if err == rlp.EOL { |
||||
break |
||||
} else { |
||||
return ProtocolError(ErrDecode, "%v", err) |
||||
} |
||||
} |
||||
self.blockPool.AddBlock(block, self.id) |
||||
} |
||||
|
||||
case NewBlockMsg: |
||||
var request newBlockMsgData |
||||
if err := msg.Decode(&request); err != nil { |
||||
return ProtocolError(ErrDecode, "%v", err) |
||||
} |
||||
hash := request.Block.Hash() |
||||
// to simplify backend interface adding a new block
|
||||
// uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer
|
||||
// (or selected as new best peer)
|
||||
if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { |
||||
called := true |
||||
iter := func() (hash []byte, ok bool) { |
||||
if called { |
||||
called = false |
||||
return hash, true |
||||
} else { |
||||
return |
||||
} |
||||
} |
||||
self.blockPool.AddBlockHashes(iter, self.id) |
||||
self.blockPool.AddBlock(request.Block, self.id) |
||||
} |
||||
|
||||
default: |
||||
return ProtocolError(ErrInvalidMsgCode, "%v", msg.Code) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
type statusMsgData struct { |
||||
ProtocolVersion uint |
||||
NetworkId uint |
||||
TD *big.Int |
||||
CurrentBlock []byte |
||||
GenesisBlock []byte |
||||
} |
||||
|
||||
func (self *ethProtocol) statusMsg() p2p.Msg { |
||||
td, currentBlock, genesisBlock := self.chainManager.Status() |
||||
|
||||
return p2p.NewMsg(StatusMsg, |
||||
uint32(ProtocolVersion), |
||||
uint32(NetworkId), |
||||
td, |
||||
currentBlock, |
||||
genesisBlock, |
||||
) |
||||
} |
||||
|
||||
func (self *ethProtocol) handleStatus() error { |
||||
// send precanned status message
|
||||
if err := self.rw.WriteMsg(self.statusMsg()); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// read and handle remote status
|
||||
msg, err := self.rw.ReadMsg() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if msg.Code != StatusMsg { |
||||
return ProtocolError(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) |
||||
} |
||||
|
||||
if msg.Size > ProtocolMaxMsgSize { |
||||
return ProtocolError(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) |
||||
} |
||||
|
||||
var status statusMsgData |
||||
if err := msg.Decode(&status); err != nil { |
||||
return ProtocolError(ErrDecode, "%v", err) |
||||
} |
||||
|
||||
_, _, genesisBlock := self.chainManager.Status() |
||||
|
||||
if bytes.Compare(status.GenesisBlock, genesisBlock) != 0 { |
||||
return ProtocolError(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock, genesisBlock) |
||||
} |
||||
|
||||
if status.NetworkId != NetworkId { |
||||
return ProtocolError(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, NetworkId) |
||||
} |
||||
|
||||
if ProtocolVersion != status.ProtocolVersion { |
||||
return ProtocolError(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, ProtocolVersion) |
||||
} |
||||
|
||||
self.peer.Infof("Peer is [eth] capable (%d/%d). TD=%v H=%x\n", status.ProtocolVersion, status.NetworkId, status.TD, status.CurrentBlock[:4]) |
||||
|
||||
//self.blockPool.AddPeer(status.TD, status.CurrentBlock, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect)
|
||||
self.peer.Infoln("AddPeer(IGNORED)") |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (self *ethProtocol) requestBlockHashes(from []byte) error { |
||||
self.peer.Debugf("fetching hashes (%d) %x...\n", blockHashesBatchSize, from[0:4]) |
||||
return self.rw.EncodeMsg(GetBlockHashesMsg, from, blockHashesBatchSize) |
||||
} |
||||
|
||||
func (self *ethProtocol) requestBlocks(hashes [][]byte) error { |
||||
self.peer.Debugf("fetching %v blocks", len(hashes)) |
||||
return self.rw.EncodeMsg(GetBlocksMsg, ethutil.ByteSliceToInterface(hashes)) |
||||
} |
||||
|
||||
func (self *ethProtocol) protoError(code int, format string, params ...interface{}) (err *protocolError) { |
||||
err = ProtocolError(code, format, params...) |
||||
if err.Fatal() { |
||||
self.peer.Errorln(err) |
||||
} else { |
||||
self.peer.Debugln(err) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (self *ethProtocol) protoErrorDisconnect(code int, format string, params ...interface{}) { |
||||
err := ProtocolError(code, format, params...) |
||||
if err.Fatal() { |
||||
self.peer.Errorln(err) |
||||
// disconnect
|
||||
} else { |
||||
self.peer.Debugln(err) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,232 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"io" |
||||
"math/big" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
) |
||||
|
||||
type testMsgReadWriter struct { |
||||
in chan p2p.Msg |
||||
out chan p2p.Msg |
||||
} |
||||
|
||||
func (self *testMsgReadWriter) In(msg p2p.Msg) { |
||||
self.in <- msg |
||||
} |
||||
|
||||
func (self *testMsgReadWriter) Out(msg p2p.Msg) { |
||||
self.in <- msg |
||||
} |
||||
|
||||
func (self *testMsgReadWriter) WriteMsg(msg p2p.Msg) error { |
||||
self.out <- msg |
||||
return nil |
||||
} |
||||
|
||||
func (self *testMsgReadWriter) EncodeMsg(code uint64, data ...interface{}) error { |
||||
return self.WriteMsg(p2p.NewMsg(code, data)) |
||||
} |
||||
|
||||
func (self *testMsgReadWriter) ReadMsg() (p2p.Msg, error) { |
||||
msg, ok := <-self.in |
||||
if !ok { |
||||
return msg, io.EOF |
||||
} |
||||
return msg, nil |
||||
} |
||||
|
||||
func errorCheck(t *testing.T, expCode int, err error) { |
||||
perr, ok := err.(*protocolError) |
||||
if ok && perr != nil { |
||||
if code := perr.Code; code != expCode { |
||||
ok = false |
||||
} |
||||
} |
||||
if !ok { |
||||
t.Errorf("expected error code %v, got %v", ErrNoStatusMsg, err) |
||||
} |
||||
} |
||||
|
||||
type TestBackend struct { |
||||
getTransactions func() []*types.Transaction |
||||
addTransactions func(txs []*types.Transaction) |
||||
getBlockHashes func(hash []byte, amount uint32) (hashes [][]byte) |
||||
addBlockHashes func(next func() ([]byte, bool), peerId string) |
||||
getBlock func(hash []byte) *types.Block |
||||
addBlock func(block *types.Block, peerId string) (err error) |
||||
addPeer func(td *big.Int, currentBlock []byte, peerId string, requestHashes func([]byte) error, requestBlocks func([][]byte) error, invalidBlock func(error)) (best bool) |
||||
removePeer func(peerId string) |
||||
status func() (td *big.Int, currentBlock []byte, genesisBlock []byte) |
||||
} |
||||
|
||||
func (self *TestBackend) GetTransactions() (txs []*types.Transaction) { |
||||
if self.getTransactions != nil { |
||||
txs = self.getTransactions() |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (self *TestBackend) AddTransactions(txs []*types.Transaction) { |
||||
if self.addTransactions != nil { |
||||
self.addTransactions(txs) |
||||
} |
||||
} |
||||
|
||||
func (self *TestBackend) GetBlockHashes(hash []byte, amount uint32) (hashes [][]byte) { |
||||
if self.getBlockHashes != nil { |
||||
hashes = self.getBlockHashes(hash, amount) |
||||
} |
||||
return |
||||
} |
||||
|
||||
<<<<<<< HEAD |
||||
<<<<<<< HEAD |
||||
func (self *TestBackend) AddBlockHashes(next func() ([]byte, bool), peerId string) { |
||||
if self.addBlockHashes != nil { |
||||
self.addBlockHashes(next, peerId) |
||||
} |
||||
} |
||||
|
||||
======= |
||||
func (self *TestBackend) AddHash(hash []byte, peer *p2p.Peer) (more bool) { |
||||
if self.addHash != nil { |
||||
more = self.addHash(hash, peer) |
||||
======= |
||||
func (self *TestBackend) AddBlockHashes(next func() ([]byte, bool), peerId string) { |
||||
if self.addBlockHashes != nil { |
||||
self.addBlockHashes(next, peerId) |
||||
>>>>>>> eth protocol changes |
||||
} |
||||
} |
||||
<<<<<<< HEAD |
||||
>>>>>>> initial commit for eth-p2p integration |
||||
======= |
||||
|
||||
>>>>>>> eth protocol changes |
||||
func (self *TestBackend) GetBlock(hash []byte) (block *types.Block) { |
||||
if self.getBlock != nil { |
||||
block = self.getBlock(hash) |
||||
} |
||||
return |
||||
} |
||||
|
||||
<<<<<<< HEAD |
||||
<<<<<<< HEAD |
||||
func (self *TestBackend) AddBlock(block *types.Block, peerId string) (err error) { |
||||
if self.addBlock != nil { |
||||
err = self.addBlock(block, peerId) |
||||
======= |
||||
func (self *TestBackend) AddBlock(td *big.Int, block *types.Block, peer *p2p.Peer) (fetchHashes bool, err error) { |
||||
if self.addBlock != nil { |
||||
fetchHashes, err = self.addBlock(td, block, peer) |
||||
>>>>>>> initial commit for eth-p2p integration |
||||
======= |
||||
func (self *TestBackend) AddBlock(block *types.Block, peerId string) (err error) { |
||||
if self.addBlock != nil { |
||||
err = self.addBlock(block, peerId) |
||||
>>>>>>> eth protocol changes |
||||
} |
||||
return |
||||
} |
||||
|
||||
<<<<<<< HEAD |
||||
<<<<<<< HEAD |
||||
func (self *TestBackend) AddPeer(td *big.Int, currentBlock []byte, peerId string, requestBlockHashes func([]byte) error, requestBlocks func([][]byte) error, invalidBlock func(error)) (best bool) { |
||||
if self.addPeer != nil { |
||||
best = self.addPeer(td, currentBlock, peerId, requestBlockHashes, requestBlocks, invalidBlock) |
||||
======= |
||||
func (self *TestBackend) AddPeer(td *big.Int, currentBlock []byte, peer *p2p.Peer) (fetchHashes bool) { |
||||
if self.addPeer != nil { |
||||
fetchHashes = self.addPeer(td, currentBlock, peer) |
||||
>>>>>>> initial commit for eth-p2p integration |
||||
======= |
||||
func (self *TestBackend) AddPeer(td *big.Int, currentBlock []byte, peerId string, requestBlockHashes func([]byte) error, requestBlocks func([][]byte) error, invalidBlock func(error)) (best bool) { |
||||
if self.addPeer != nil { |
||||
best = self.addPeer(td, currentBlock, peerId, requestBlockHashes, requestBlocks, invalidBlock) |
||||
>>>>>>> eth protocol changes |
||||
} |
||||
return |
||||
} |
||||
|
||||
<<<<<<< HEAD |
||||
<<<<<<< HEAD |
||||
======= |
||||
>>>>>>> eth protocol changes |
||||
func (self *TestBackend) RemovePeer(peerId string) { |
||||
if self.removePeer != nil { |
||||
self.removePeer(peerId) |
||||
} |
||||
} |
||||
|
||||
<<<<<<< HEAD |
||||
======= |
||||
>>>>>>> initial commit for eth-p2p integration |
||||
======= |
||||
>>>>>>> eth protocol changes |
||||
func (self *TestBackend) Status() (td *big.Int, currentBlock []byte, genesisBlock []byte) { |
||||
if self.status != nil { |
||||
td, currentBlock, genesisBlock = self.status() |
||||
} |
||||
return |
||||
} |
||||
|
||||
<<<<<<< HEAD |
||||
<<<<<<< HEAD |
||||
======= |
||||
>>>>>>> eth protocol changes |
||||
// TODO: refactor this into p2p/client_identity
|
||||
type peerId struct { |
||||
pubkey []byte |
||||
} |
||||
|
||||
func (self *peerId) String() string { |
||||
return "test peer" |
||||
} |
||||
|
||||
func (self *peerId) Pubkey() (pubkey []byte) { |
||||
pubkey = self.pubkey |
||||
if len(pubkey) == 0 { |
||||
pubkey = crypto.GenerateNewKeyPair().PublicKey |
||||
self.pubkey = pubkey |
||||
} |
||||
return |
||||
} |
||||
|
||||
func testPeer() *p2p.Peer { |
||||
return p2p.NewPeer(&peerId{}, []p2p.Cap{}) |
||||
} |
||||
|
||||
func TestErrNoStatusMsg(t *testing.T) { |
||||
<<<<<<< HEAD |
||||
======= |
||||
func TestEth(t *testing.T) { |
||||
>>>>>>> initial commit for eth-p2p integration |
||||
======= |
||||
>>>>>>> eth protocol changes |
||||
quit := make(chan bool) |
||||
rw := &testMsgReadWriter{make(chan p2p.Msg, 10), make(chan p2p.Msg, 10)} |
||||
testBackend := &TestBackend{} |
||||
var err error |
||||
go func() { |
||||
<<<<<<< HEAD |
||||
<<<<<<< HEAD |
||||
err = runEthProtocol(testBackend, testPeer(), rw) |
||||
======= |
||||
err = runEthProtocol(testBackend, nil, rw) |
||||
>>>>>>> initial commit for eth-p2p integration |
||||
======= |
||||
err = runEthProtocol(testBackend, testPeer(), rw) |
||||
>>>>>>> eth protocol changes |
||||
close(quit) |
||||
}() |
||||
statusMsg := p2p.NewMsg(4) |
||||
rw.In(statusMsg) |
||||
<-quit |
||||
errorCheck(t, ErrNoStatusMsg, err) |
||||
// read(t, remote, []byte("hello, world"), nil)
|
||||
} |
@ -1,659 +0,0 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"container/list" |
||||
"encoding/json" |
||||
"fmt" |
||||
"math/big" |
||||
"math/rand" |
||||
"net" |
||||
"path" |
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/core" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/ethutil" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
"github.com/ethereum/go-ethereum/logger" |
||||
"github.com/ethereum/go-ethereum/rpc" |
||||
"github.com/ethereum/go-ethereum/state" |
||||
"github.com/ethereum/go-ethereum/wire" |
||||
) |
||||
|
||||
const ( |
||||
seedTextFileUri string = "http://www.ethereum.org/servers.poc3.txt" |
||||
seedNodeAddress = "poc-7.ethdev.com:30303" |
||||
) |
||||
|
||||
var loggerger = logger.NewLogger("SERV") |
||||
|
||||
func eachPeer(peers *list.List, callback func(*Peer, *list.Element)) { |
||||
// Loop thru the peers and close them (if we had them)
|
||||
for e := peers.Front(); e != nil; e = e.Next() { |
||||
callback(e.Value.(*Peer), e) |
||||
} |
||||
} |
||||
|
||||
const ( |
||||
processReapingTimeout = 60 // TODO increase
|
||||
) |
||||
|
||||
type Ethereum struct { |
||||
// Channel for shutting down the ethereum
|
||||
shutdownChan chan bool |
||||
quit chan bool |
||||
|
||||
// DB interface
|
||||
db ethutil.Database |
||||
// State manager for processing new blocks and managing the over all states
|
||||
blockManager *core.BlockManager |
||||
// The transaction pool. Transaction can be pushed on this pool
|
||||
// for later including in the blocks
|
||||
txPool *core.TxPool |
||||
// The canonical chain
|
||||
blockChain *core.ChainManager |
||||
// The block pool
|
||||
blockPool *BlockPool |
||||
// Eventer
|
||||
eventMux event.TypeMux |
||||
// Peers
|
||||
peers *list.List |
||||
// Nonce
|
||||
Nonce uint64 |
||||
|
||||
Addr net.Addr |
||||
Port string |
||||
|
||||
blacklist [][]byte |
||||
|
||||
peerMut sync.Mutex |
||||
|
||||
// Capabilities for outgoing peers
|
||||
serverCaps Caps |
||||
|
||||
nat NAT |
||||
|
||||
// Specifies the desired amount of maximum peers
|
||||
MaxPeers int |
||||
|
||||
Mining bool |
||||
|
||||
listening bool |
||||
|
||||
RpcServer *rpc.JsonRpcServer |
||||
|
||||
keyManager *crypto.KeyManager |
||||
|
||||
clientIdentity wire.ClientIdentity |
||||
|
||||
isUpToDate bool |
||||
|
||||
filterMu sync.RWMutex |
||||
filterId int |
||||
filters map[int]*core.Filter |
||||
} |
||||
|
||||
func New(db ethutil.Database, clientIdentity wire.ClientIdentity, keyManager *crypto.KeyManager, caps Caps, usePnp bool) (*Ethereum, error) { |
||||
var err error |
||||
var nat NAT |
||||
|
||||
if usePnp { |
||||
nat, err = Discover() |
||||
if err != nil { |
||||
loggerger.Debugln("UPnP failed", err) |
||||
} |
||||
} |
||||
|
||||
bootstrapDb(db) |
||||
|
||||
ethutil.Config.Db = db |
||||
|
||||
nonce, _ := ethutil.RandomUint64() |
||||
ethereum := &Ethereum{ |
||||
shutdownChan: make(chan bool), |
||||
quit: make(chan bool), |
||||
db: db, |
||||
peers: list.New(), |
||||
Nonce: nonce, |
||||
serverCaps: caps, |
||||
nat: nat, |
||||
keyManager: keyManager, |
||||
clientIdentity: clientIdentity, |
||||
isUpToDate: true, |
||||
filters: make(map[int]*core.Filter), |
||||
} |
||||
|
||||
ethereum.blockPool = NewBlockPool(ethereum) |
||||
ethereum.txPool = core.NewTxPool(ethereum) |
||||
ethereum.blockChain = core.NewChainManager(ethereum.EventMux()) |
||||
ethereum.blockManager = core.NewBlockManager(ethereum) |
||||
ethereum.blockChain.SetProcessor(ethereum.blockManager) |
||||
|
||||
// Start the tx pool
|
||||
ethereum.txPool.Start() |
||||
|
||||
return ethereum, nil |
||||
} |
||||
|
||||
func (s *Ethereum) KeyManager() *crypto.KeyManager { |
||||
return s.keyManager |
||||
} |
||||
|
||||
func (s *Ethereum) ClientIdentity() wire.ClientIdentity { |
||||
return s.clientIdentity |
||||
} |
||||
|
||||
func (s *Ethereum) ChainManager() *core.ChainManager { |
||||
return s.blockChain |
||||
} |
||||
|
||||
func (s *Ethereum) BlockManager() *core.BlockManager { |
||||
return s.blockManager |
||||
} |
||||
|
||||
func (s *Ethereum) TxPool() *core.TxPool { |
||||
return s.txPool |
||||
} |
||||
func (s *Ethereum) BlockPool() *BlockPool { |
||||
return s.blockPool |
||||
} |
||||
func (s *Ethereum) EventMux() *event.TypeMux { |
||||
return &s.eventMux |
||||
} |
||||
func (self *Ethereum) Db() ethutil.Database { |
||||
return self.db |
||||
} |
||||
|
||||
func (s *Ethereum) ServerCaps() Caps { |
||||
return s.serverCaps |
||||
} |
||||
func (s *Ethereum) IsMining() bool { |
||||
return s.Mining |
||||
} |
||||
func (s *Ethereum) PeerCount() int { |
||||
return s.peers.Len() |
||||
} |
||||
func (s *Ethereum) IsUpToDate() bool { |
||||
upToDate := true |
||||
eachPeer(s.peers, func(peer *Peer, e *list.Element) { |
||||
if atomic.LoadInt32(&peer.connected) == 1 { |
||||
if peer.catchingUp == true && peer.versionKnown { |
||||
upToDate = false |
||||
} |
||||
} |
||||
}) |
||||
return upToDate |
||||
} |
||||
func (s *Ethereum) PushPeer(peer *Peer) { |
||||
s.peers.PushBack(peer) |
||||
} |
||||
func (s *Ethereum) IsListening() bool { |
||||
return s.listening |
||||
} |
||||
|
||||
func (s *Ethereum) HighestTDPeer() (td *big.Int) { |
||||
td = big.NewInt(0) |
||||
|
||||
eachPeer(s.peers, func(p *Peer, v *list.Element) { |
||||
if p.td.Cmp(td) > 0 { |
||||
td = p.td |
||||
} |
||||
}) |
||||
|
||||
return |
||||
} |
||||
|
||||
func (self *Ethereum) BlacklistPeer(peer *Peer) { |
||||
self.blacklist = append(self.blacklist, peer.pubkey) |
||||
} |
||||
|
||||
func (s *Ethereum) AddPeer(conn net.Conn) { |
||||
peer := NewPeer(conn, s, true) |
||||
|
||||
if peer != nil { |
||||
if s.peers.Len() < s.MaxPeers { |
||||
peer.Start() |
||||
} else { |
||||
loggerger.Debugf("Max connected peers reached. Not adding incoming peer.") |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (s *Ethereum) ProcessPeerList(addrs []string) { |
||||
for _, addr := range addrs { |
||||
// TODO Probably requires some sanity checks
|
||||
s.ConnectToPeer(addr) |
||||
} |
||||
} |
||||
|
||||
func (s *Ethereum) ConnectToPeer(addr string) error { |
||||
if s.peers.Len() < s.MaxPeers { |
||||
var alreadyConnected bool |
||||
|
||||
ahost, aport, _ := net.SplitHostPort(addr) |
||||
var chost string |
||||
|
||||
ips, err := net.LookupIP(ahost) |
||||
|
||||
if err != nil { |
||||
return err |
||||
} else { |
||||
// If more then one ip is available try stripping away the ipv6 ones
|
||||
if len(ips) > 1 { |
||||
var ipsv4 []net.IP |
||||
// For now remove the ipv6 addresses
|
||||
for _, ip := range ips { |
||||
if strings.Contains(ip.String(), "::") { |
||||
continue |
||||
} else { |
||||
ipsv4 = append(ipsv4, ip) |
||||
} |
||||
} |
||||
if len(ipsv4) == 0 { |
||||
return fmt.Errorf("[SERV] No IPV4 addresses available for hostname") |
||||
} |
||||
|
||||
// Pick a random ipv4 address, simulating round-robin DNS.
|
||||
rand.Seed(time.Now().UTC().UnixNano()) |
||||
i := rand.Intn(len(ipsv4)) |
||||
chost = ipsv4[i].String() |
||||
} else { |
||||
if len(ips) == 0 { |
||||
return fmt.Errorf("[SERV] No IPs resolved for the given hostname") |
||||
return nil |
||||
} |
||||
chost = ips[0].String() |
||||
} |
||||
} |
||||
|
||||
eachPeer(s.peers, func(p *Peer, v *list.Element) { |
||||
if p.conn == nil { |
||||
return |
||||
} |
||||
phost, pport, _ := net.SplitHostPort(p.conn.RemoteAddr().String()) |
||||
|
||||
if phost == chost && pport == aport { |
||||
alreadyConnected = true |
||||
//loggerger.Debugf("Peer %s already added.\n", chost)
|
||||
return |
||||
} |
||||
}) |
||||
|
||||
if alreadyConnected { |
||||
return nil |
||||
} |
||||
|
||||
NewOutboundPeer(addr, s, s.serverCaps) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (s *Ethereum) OutboundPeers() []*Peer { |
||||
// Create a new peer slice with at least the length of the total peers
|
||||
outboundPeers := make([]*Peer, s.peers.Len()) |
||||
length := 0 |
||||
eachPeer(s.peers, func(p *Peer, e *list.Element) { |
||||
if !p.inbound && p.conn != nil { |
||||
outboundPeers[length] = p |
||||
length++ |
||||
} |
||||
}) |
||||
|
||||
return outboundPeers[:length] |
||||
} |
||||
|
||||
func (s *Ethereum) InboundPeers() []*Peer { |
||||
// Create a new peer slice with at least the length of the total peers
|
||||
inboundPeers := make([]*Peer, s.peers.Len()) |
||||
length := 0 |
||||
eachPeer(s.peers, func(p *Peer, e *list.Element) { |
||||
if p.inbound { |
||||
inboundPeers[length] = p |
||||
length++ |
||||
} |
||||
}) |
||||
|
||||
return inboundPeers[:length] |
||||
} |
||||
|
||||
func (s *Ethereum) InOutPeers() []*Peer { |
||||
// Reap the dead peers first
|
||||
s.reapPeers() |
||||
|
||||
// Create a new peer slice with at least the length of the total peers
|
||||
inboundPeers := make([]*Peer, s.peers.Len()) |
||||
length := 0 |
||||
eachPeer(s.peers, func(p *Peer, e *list.Element) { |
||||
// Only return peers with an actual ip
|
||||
if len(p.host) > 0 { |
||||
inboundPeers[length] = p |
||||
length++ |
||||
} |
||||
}) |
||||
|
||||
return inboundPeers[:length] |
||||
} |
||||
|
||||
func (s *Ethereum) Broadcast(msgType wire.MsgType, data []interface{}) { |
||||
msg := wire.NewMessage(msgType, data) |
||||
s.BroadcastMsg(msg) |
||||
} |
||||
|
||||
func (s *Ethereum) BroadcastMsg(msg *wire.Msg) { |
||||
eachPeer(s.peers, func(p *Peer, e *list.Element) { |
||||
p.QueueMessage(msg) |
||||
}) |
||||
} |
||||
|
||||
func (s *Ethereum) Peers() *list.List { |
||||
return s.peers |
||||
} |
||||
|
||||
func (s *Ethereum) reapPeers() { |
||||
eachPeer(s.peers, func(p *Peer, e *list.Element) { |
||||
if atomic.LoadInt32(&p.disconnect) == 1 || (p.inbound && (time.Now().Unix()-p.lastPong) > int64(5*time.Minute)) { |
||||
s.removePeerElement(e) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
func (s *Ethereum) removePeerElement(e *list.Element) { |
||||
s.peerMut.Lock() |
||||
defer s.peerMut.Unlock() |
||||
|
||||
s.peers.Remove(e) |
||||
|
||||
s.eventMux.Post(PeerListEvent{s.peers}) |
||||
} |
||||
|
||||
func (s *Ethereum) RemovePeer(p *Peer) { |
||||
eachPeer(s.peers, func(peer *Peer, e *list.Element) { |
||||
if peer == p { |
||||
s.removePeerElement(e) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
func (s *Ethereum) reapDeadPeerHandler() { |
||||
reapTimer := time.NewTicker(processReapingTimeout * time.Second) |
||||
|
||||
for { |
||||
select { |
||||
case <-reapTimer.C: |
||||
s.reapPeers() |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Start the ethereum
|
||||
func (s *Ethereum) Start(seed bool) { |
||||
s.blockPool.Start() |
||||
|
||||
// Bind to addr and port
|
||||
ln, err := net.Listen("tcp", ":"+s.Port) |
||||
if err != nil { |
||||
loggerger.Warnf("Port %s in use. Connection listening disabled. Acting as client", s.Port) |
||||
s.listening = false |
||||
} else { |
||||
s.listening = true |
||||
// Starting accepting connections
|
||||
loggerger.Infoln("Ready and accepting connections") |
||||
// Start the peer handler
|
||||
go s.peerHandler(ln) |
||||
} |
||||
|
||||
if s.nat != nil { |
||||
go s.upnpUpdateThread() |
||||
} |
||||
|
||||
// Start the reaping processes
|
||||
go s.reapDeadPeerHandler() |
||||
go s.update() |
||||
go s.filterLoop() |
||||
|
||||
if seed { |
||||
s.Seed() |
||||
} |
||||
s.ConnectToPeer("localhost:40404") |
||||
loggerger.Infoln("Server started") |
||||
} |
||||
|
||||
func (s *Ethereum) Seed() { |
||||
// Sorry Py person. I must blacklist. you perform badly
|
||||
s.blacklist = append(s.blacklist, ethutil.Hex2Bytes("64656330303561383532336435376331616537643864663236623336313863373537353163636634333530626263396330346237336262623931383064393031")) |
||||
ips := PastPeers() |
||||
if len(ips) > 0 { |
||||
for _, ip := range ips { |
||||
loggerger.Infoln("Connecting to previous peer ", ip) |
||||
s.ConnectToPeer(ip) |
||||
} |
||||
} else { |
||||
loggerger.Debugln("Retrieving seed nodes") |
||||
|
||||
// Eth-Go Bootstrapping
|
||||
ips, er := net.LookupIP("seed.bysh.me") |
||||
if er == nil { |
||||
peers := []string{} |
||||
for _, ip := range ips { |
||||
node := fmt.Sprintf("%s:%d", ip.String(), 30303) |
||||
loggerger.Debugln("Found DNS Go Peer:", node) |
||||
peers = append(peers, node) |
||||
} |
||||
s.ProcessPeerList(peers) |
||||
} |
||||
|
||||
// Official DNS Bootstrapping
|
||||
_, nodes, err := net.LookupSRV("eth", "tcp", "ethereum.org") |
||||
if err == nil { |
||||
peers := []string{} |
||||
// Iterate SRV nodes
|
||||
for _, n := range nodes { |
||||
target := n.Target |
||||
port := strconv.Itoa(int(n.Port)) |
||||
// Resolve target to ip (Go returns list, so may resolve to multiple ips?)
|
||||
addr, err := net.LookupHost(target) |
||||
if err == nil { |
||||
for _, a := range addr { |
||||
// Build string out of SRV port and Resolved IP
|
||||
peer := net.JoinHostPort(a, port) |
||||
loggerger.Debugln("Found DNS Bootstrap Peer:", peer) |
||||
peers = append(peers, peer) |
||||
} |
||||
} else { |
||||
loggerger.Debugln("Couldn't resolve :", target) |
||||
} |
||||
} |
||||
// Connect to Peer list
|
||||
s.ProcessPeerList(peers) |
||||
} |
||||
|
||||
s.ConnectToPeer(seedNodeAddress) |
||||
} |
||||
} |
||||
|
||||
func (s *Ethereum) peerHandler(listener net.Listener) { |
||||
for { |
||||
conn, err := listener.Accept() |
||||
if err != nil { |
||||
loggerger.Debugln(err) |
||||
|
||||
continue |
||||
} |
||||
|
||||
go s.AddPeer(conn) |
||||
} |
||||
} |
||||
|
||||
func (s *Ethereum) Stop() { |
||||
// Stop eventMux first, it will close all subscriptions.
|
||||
s.eventMux.Stop() |
||||
|
||||
// Close the database
|
||||
defer s.db.Close() |
||||
|
||||
var ips []string |
||||
eachPeer(s.peers, func(p *Peer, e *list.Element) { |
||||
ips = append(ips, p.conn.RemoteAddr().String()) |
||||
}) |
||||
|
||||
if len(ips) > 0 { |
||||
d, _ := json.MarshalIndent(ips, "", " ") |
||||
ethutil.WriteFile(path.Join(ethutil.Config.ExecPath, "known_peers.json"), d) |
||||
} |
||||
|
||||
eachPeer(s.peers, func(p *Peer, e *list.Element) { |
||||
p.Stop() |
||||
}) |
||||
|
||||
close(s.quit) |
||||
|
||||
if s.RpcServer != nil { |
||||
s.RpcServer.Stop() |
||||
} |
||||
s.txPool.Stop() |
||||
s.blockPool.Stop() |
||||
|
||||
loggerger.Infoln("Server stopped") |
||||
close(s.shutdownChan) |
||||
} |
||||
|
||||
// This function will wait for a shutdown and resumes main thread execution
|
||||
func (s *Ethereum) WaitForShutdown() { |
||||
<-s.shutdownChan |
||||
} |
||||
|
||||
func (s *Ethereum) upnpUpdateThread() { |
||||
// Go off immediately to prevent code duplication, thereafter we renew
|
||||
// lease every 15 minutes.
|
||||
timer := time.NewTimer(5 * time.Minute) |
||||
lport, _ := strconv.ParseInt(s.Port, 10, 16) |
||||
first := true |
||||
out: |
||||
for { |
||||
select { |
||||
case <-timer.C: |
||||
var err error |
||||
_, err = s.nat.AddPortMapping("TCP", int(lport), int(lport), "eth listen port", 20*60) |
||||
if err != nil { |
||||
loggerger.Debugln("can't add UPnP port mapping:", err) |
||||
break out |
||||
} |
||||
if first && err == nil { |
||||
_, err = s.nat.GetExternalAddress() |
||||
if err != nil { |
||||
loggerger.Debugln("UPnP can't get external address:", err) |
||||
continue out |
||||
} |
||||
first = false |
||||
} |
||||
timer.Reset(time.Minute * 15) |
||||
case <-s.quit: |
||||
break out |
||||
} |
||||
} |
||||
|
||||
timer.Stop() |
||||
|
||||
if err := s.nat.DeletePortMapping("TCP", int(lport), int(lport)); err != nil { |
||||
loggerger.Debugln("unable to remove UPnP port mapping:", err) |
||||
} else { |
||||
loggerger.Debugln("succesfully disestablished UPnP port mapping") |
||||
} |
||||
} |
||||
|
||||
func (self *Ethereum) update() { |
||||
upToDateTimer := time.NewTicker(1 * time.Second) |
||||
|
||||
out: |
||||
for { |
||||
select { |
||||
case <-upToDateTimer.C: |
||||
if self.IsUpToDate() && !self.isUpToDate { |
||||
self.eventMux.Post(ChainSyncEvent{false}) |
||||
self.isUpToDate = true |
||||
} else if !self.IsUpToDate() && self.isUpToDate { |
||||
self.eventMux.Post(ChainSyncEvent{true}) |
||||
self.isUpToDate = false |
||||
} |
||||
case <-self.quit: |
||||
break out |
||||
} |
||||
} |
||||
} |
||||
|
||||
// InstallFilter adds filter for blockchain events.
|
||||
// The filter's callbacks will run for matching blocks and messages.
|
||||
// The filter should not be modified after it has been installed.
|
||||
func (self *Ethereum) InstallFilter(filter *core.Filter) (id int) { |
||||
self.filterMu.Lock() |
||||
id = self.filterId |
||||
self.filters[id] = filter |
||||
self.filterId++ |
||||
self.filterMu.Unlock() |
||||
return id |
||||
} |
||||
|
||||
func (self *Ethereum) UninstallFilter(id int) { |
||||
self.filterMu.Lock() |
||||
delete(self.filters, id) |
||||
self.filterMu.Unlock() |
||||
} |
||||
|
||||
// GetFilter retrieves a filter installed using InstallFilter.
|
||||
// The filter may not be modified.
|
||||
func (self *Ethereum) GetFilter(id int) *core.Filter { |
||||
self.filterMu.RLock() |
||||
defer self.filterMu.RUnlock() |
||||
return self.filters[id] |
||||
} |
||||
|
||||
func (self *Ethereum) filterLoop() { |
||||
// Subscribe to events
|
||||
events := self.eventMux.Subscribe(core.NewBlockEvent{}, state.Messages(nil)) |
||||
for event := range events.Chan() { |
||||
switch event := event.(type) { |
||||
case core.NewBlockEvent: |
||||
self.filterMu.RLock() |
||||
for _, filter := range self.filters { |
||||
if filter.BlockCallback != nil { |
||||
filter.BlockCallback(event.Block) |
||||
} |
||||
} |
||||
self.filterMu.RUnlock() |
||||
|
||||
case state.Messages: |
||||
self.filterMu.RLock() |
||||
for _, filter := range self.filters { |
||||
if filter.MessageCallback != nil { |
||||
msgs := filter.FilterMessages(event) |
||||
if len(msgs) > 0 { |
||||
filter.MessageCallback(msgs) |
||||
} |
||||
} |
||||
} |
||||
self.filterMu.RUnlock() |
||||
} |
||||
} |
||||
} |
||||
|
||||
func bootstrapDb(db ethutil.Database) { |
||||
d, _ := db.Get([]byte("ProtocolVersion")) |
||||
protov := ethutil.NewValue(d).Uint() |
||||
|
||||
if protov == 0 { |
||||
db.Put([]byte("ProtocolVersion"), ethutil.NewValue(ProtocolVersion).Bytes()) |
||||
} |
||||
} |
||||
|
||||
func PastPeers() []string { |
||||
var ips []string |
||||
data, _ := ethutil.ReadAllFile(path.Join(ethutil.Config.ExecPath, "known_peers.json")) |
||||
json.Unmarshal([]byte(data), &ips) |
||||
|
||||
return ips |
||||
} |
@ -0,0 +1,94 @@ |
||||
// XXX This is the old filter system specifically for messages. This is till in used and could use some refactoring
|
||||
package filter |
||||
|
||||
import ( |
||||
"sync" |
||||
|
||||
"github.com/ethereum/go-ethereum/core" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
"github.com/ethereum/go-ethereum/state" |
||||
) |
||||
|
||||
type FilterManager struct { |
||||
eventMux *event.TypeMux |
||||
|
||||
filterMu sync.RWMutex |
||||
filterId int |
||||
filters map[int]*core.Filter |
||||
|
||||
quit chan struct{} |
||||
} |
||||
|
||||
func NewFilterManager(mux *event.TypeMux) *FilterManager { |
||||
return &FilterManager{ |
||||
eventMux: mux, |
||||
filters: make(map[int]*core.Filter), |
||||
} |
||||
} |
||||
|
||||
func (self *FilterManager) Start() { |
||||
go self.filterLoop() |
||||
} |
||||
|
||||
func (self *FilterManager) Stop() { |
||||
close(self.quit) |
||||
} |
||||
|
||||
func (self *FilterManager) InstallFilter(filter *core.Filter) (id int) { |
||||
self.filterMu.Lock() |
||||
id = self.filterId |
||||
self.filters[id] = filter |
||||
self.filterId++ |
||||
self.filterMu.Unlock() |
||||
return id |
||||
} |
||||
|
||||
func (self *FilterManager) UninstallFilter(id int) { |
||||
self.filterMu.Lock() |
||||
delete(self.filters, id) |
||||
self.filterMu.Unlock() |
||||
} |
||||
|
||||
// GetFilter retrieves a filter installed using InstallFilter.
|
||||
// The filter may not be modified.
|
||||
func (self *FilterManager) GetFilter(id int) *core.Filter { |
||||
self.filterMu.RLock() |
||||
defer self.filterMu.RUnlock() |
||||
return self.filters[id] |
||||
} |
||||
|
||||
func (self *FilterManager) filterLoop() { |
||||
// Subscribe to events
|
||||
events := self.eventMux.Subscribe(core.NewBlockEvent{}, state.Messages(nil)) |
||||
|
||||
out: |
||||
for { |
||||
select { |
||||
case <-self.quit: |
||||
break out |
||||
case event := <-events.Chan(): |
||||
switch event := event.(type) { |
||||
case core.NewBlockEvent: |
||||
self.filterMu.RLock() |
||||
for _, filter := range self.filters { |
||||
if filter.BlockCallback != nil { |
||||
filter.BlockCallback(event.Block) |
||||
} |
||||
} |
||||
self.filterMu.RUnlock() |
||||
|
||||
case state.Messages: |
||||
self.filterMu.RLock() |
||||
for _, filter := range self.filters { |
||||
if filter.MessageCallback != nil { |
||||
msgs := filter.FilterMessages(event) |
||||
if len(msgs) > 0 { |
||||
filter.MessageCallback(msgs) |
||||
} |
||||
} |
||||
} |
||||
self.filterMu.RUnlock() |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,11 +0,0 @@ |
||||
package eth |
||||
|
||||
import "container/list" |
||||
|
||||
type PeerListEvent struct { |
||||
Peers *list.List |
||||
} |
||||
|
||||
type ChainSyncEvent struct { |
||||
InSync bool |
||||
} |
@ -1,8 +0,0 @@ |
||||
#!/bin/bash |
||||
|
||||
set -e |
||||
|
||||
TEST_DEPS=$(go list -f '{{.Imports}} {{.TestImports}} {{.XTestImports}}' github.com/ethereum/go-ethereum/... | sed -e 's/\[//g' | sed -e 's/\]//g' | sed -e 's/C //g') |
||||
if [ "$TEST_DEPS" ]; then |
||||
go get -race $TEST_DEPS |
||||
fi |
@ -1,12 +0,0 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"net" |
||||
) |
||||
|
||||
// protocol is either "udp" or "tcp"
|
||||
type NAT interface { |
||||
GetExternalAddress() (addr net.IP, err error) |
||||
AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) |
||||
DeletePortMapping(protocol string, externalPort, internalPort int) (err error) |
||||
} |
@ -1,55 +0,0 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net" |
||||
|
||||
natpmp "github.com/jackpal/go-nat-pmp" |
||||
) |
||||
|
||||
// Adapt the NAT-PMP protocol to the NAT interface
|
||||
|
||||
// TODO:
|
||||
// + Register for changes to the external address.
|
||||
// + Re-register port mapping when router reboots.
|
||||
// + A mechanism for keeping a port mapping registered.
|
||||
|
||||
type natPMPClient struct { |
||||
client *natpmp.Client |
||||
} |
||||
|
||||
func NewNatPMP(gateway net.IP) (nat NAT) { |
||||
return &natPMPClient{natpmp.NewClient(gateway)} |
||||
} |
||||
|
||||
func (n *natPMPClient) GetExternalAddress() (addr net.IP, err error) { |
||||
response, err := n.client.GetExternalAddress() |
||||
if err != nil { |
||||
return |
||||
} |
||||
ip := response.ExternalIPAddress |
||||
addr = net.IPv4(ip[0], ip[1], ip[2], ip[3]) |
||||
return |
||||
} |
||||
|
||||
func (n *natPMPClient) AddPortMapping(protocol string, externalPort, internalPort int, |
||||
description string, timeout int) (mappedExternalPort int, err error) { |
||||
if timeout <= 0 { |
||||
err = fmt.Errorf("timeout must not be <= 0") |
||||
return |
||||
} |
||||
// Note order of port arguments is switched between our AddPortMapping and the client's AddPortMapping.
|
||||
response, err := n.client.AddPortMapping(protocol, internalPort, externalPort, timeout) |
||||
if err != nil { |
||||
return |
||||
} |
||||
mappedExternalPort = int(response.MappedExternalPort) |
||||
return |
||||
} |
||||
|
||||
func (n *natPMPClient) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) { |
||||
// To destroy a mapping, send an add-port with
|
||||
// an internalPort of the internal port to destroy, an external port of zero and a time of zero.
|
||||
_, err = n.client.AddPortMapping(protocol, internalPort, 0, 0) |
||||
return |
||||
} |
@ -1,338 +0,0 @@ |
||||
package eth |
||||
|
||||
// Just enough UPnP to be able to forward ports
|
||||
//
|
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/xml" |
||||
"errors" |
||||
"net" |
||||
"net/http" |
||||
"os" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
type upnpNAT struct { |
||||
serviceURL string |
||||
ourIP string |
||||
} |
||||
|
||||
func Discover() (nat NAT, err error) { |
||||
ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") |
||||
if err != nil { |
||||
return |
||||
} |
||||
conn, err := net.ListenPacket("udp4", ":0") |
||||
if err != nil { |
||||
return |
||||
} |
||||
socket := conn.(*net.UDPConn) |
||||
defer socket.Close() |
||||
|
||||
err = socket.SetDeadline(time.Now().Add(10 * time.Second)) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
st := "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" |
||||
buf := bytes.NewBufferString( |
||||
"M-SEARCH * HTTP/1.1\r\n" + |
||||
"HOST: 239.255.255.250:1900\r\n" + |
||||
st + |
||||
"MAN: \"ssdp:discover\"\r\n" + |
||||
"MX: 2\r\n\r\n") |
||||
message := buf.Bytes() |
||||
answerBytes := make([]byte, 1024) |
||||
for i := 0; i < 3; i++ { |
||||
_, err = socket.WriteToUDP(message, ssdp) |
||||
if err != nil { |
||||
return |
||||
} |
||||
var n int |
||||
n, _, err = socket.ReadFromUDP(answerBytes) |
||||
if err != nil { |
||||
continue |
||||
// socket.Close()
|
||||
// return
|
||||
} |
||||
answer := string(answerBytes[0:n]) |
||||
if strings.Index(answer, "\r\n"+st) < 0 { |
||||
continue |
||||
} |
||||
// HTTP header field names are case-insensitive.
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
||||
locString := "\r\nlocation: " |
||||
answer = strings.ToLower(answer) |
||||
locIndex := strings.Index(answer, locString) |
||||
if locIndex < 0 { |
||||
continue |
||||
} |
||||
loc := answer[locIndex+len(locString):] |
||||
endIndex := strings.Index(loc, "\r\n") |
||||
if endIndex < 0 { |
||||
continue |
||||
} |
||||
locURL := loc[0:endIndex] |
||||
var serviceURL string |
||||
serviceURL, err = getServiceURL(locURL) |
||||
if err != nil { |
||||
return |
||||
} |
||||
var ourIP string |
||||
ourIP, err = getOurIP() |
||||
if err != nil { |
||||
return |
||||
} |
||||
nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP} |
||||
return |
||||
} |
||||
err = errors.New("UPnP port discovery failed.") |
||||
return |
||||
} |
||||
|
||||
// service represents the Service type in an UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type service struct { |
||||
ServiceType string `xml:"serviceType"` |
||||
ControlURL string `xml:"controlURL"` |
||||
} |
||||
|
||||
// deviceList represents the deviceList type in an UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type deviceList struct { |
||||
XMLName xml.Name `xml:"deviceList"` |
||||
Device []device `xml:"device"` |
||||
} |
||||
|
||||
// serviceList represents the serviceList type in an UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type serviceList struct { |
||||
XMLName xml.Name `xml:"serviceList"` |
||||
Service []service `xml:"service"` |
||||
} |
||||
|
||||
// device represents the device type in an UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type device struct { |
||||
XMLName xml.Name `xml:"device"` |
||||
DeviceType string `xml:"deviceType"` |
||||
DeviceList deviceList `xml:"deviceList"` |
||||
ServiceList serviceList `xml:"serviceList"` |
||||
} |
||||
|
||||
// specVersion represents the specVersion in a UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type specVersion struct { |
||||
XMLName xml.Name `xml:"specVersion"` |
||||
Major int `xml:"major"` |
||||
Minor int `xml:"minor"` |
||||
} |
||||
|
||||
// root represents the Root document for a UPnP xml description.
|
||||
// Only the parts we care about are present and thus the xml may have more
|
||||
// fields than present in the structure.
|
||||
type root struct { |
||||
XMLName xml.Name `xml:"root"` |
||||
SpecVersion specVersion |
||||
Device device |
||||
} |
||||
|
||||
func getChildDevice(d *device, deviceType string) *device { |
||||
dl := d.DeviceList.Device |
||||
for i := 0; i < len(dl); i++ { |
||||
if dl[i].DeviceType == deviceType { |
||||
return &dl[i] |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func getChildService(d *device, serviceType string) *service { |
||||
sl := d.ServiceList.Service |
||||
for i := 0; i < len(sl); i++ { |
||||
if sl[i].ServiceType == serviceType { |
||||
return &sl[i] |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func getOurIP() (ip string, err error) { |
||||
hostname, err := os.Hostname() |
||||
if err != nil { |
||||
return |
||||
} |
||||
p, err := net.LookupIP(hostname) |
||||
if err != nil && len(p) > 0 { |
||||
return |
||||
} |
||||
return p[0].String(), nil |
||||
} |
||||
|
||||
func getServiceURL(rootURL string) (url string, err error) { |
||||
r, err := http.Get(rootURL) |
||||
if err != nil { |
||||
return |
||||
} |
||||
defer r.Body.Close() |
||||
if r.StatusCode >= 400 { |
||||
err = errors.New(string(r.StatusCode)) |
||||
return |
||||
} |
||||
var root root |
||||
err = xml.NewDecoder(r.Body).Decode(&root) |
||||
|
||||
if err != nil { |
||||
return |
||||
} |
||||
a := &root.Device |
||||
if a.DeviceType != "urn:schemas-upnp-org:device:InternetGatewayDevice:1" { |
||||
err = errors.New("No InternetGatewayDevice") |
||||
return |
||||
} |
||||
b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1") |
||||
if b == nil { |
||||
err = errors.New("No WANDevice") |
||||
return |
||||
} |
||||
c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1") |
||||
if c == nil { |
||||
err = errors.New("No WANConnectionDevice") |
||||
return |
||||
} |
||||
d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1") |
||||
if d == nil { |
||||
err = errors.New("No WANIPConnection") |
||||
return |
||||
} |
||||
url = combineURL(rootURL, d.ControlURL) |
||||
return |
||||
} |
||||
|
||||
func combineURL(rootURL, subURL string) string { |
||||
protocolEnd := "://" |
||||
protoEndIndex := strings.Index(rootURL, protocolEnd) |
||||
a := rootURL[protoEndIndex+len(protocolEnd):] |
||||
rootIndex := strings.Index(a, "/") |
||||
return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL |
||||
} |
||||
|
||||
func soapRequest(url, function, message string) (r *http.Response, err error) { |
||||
fullMessage := "<?xml version=\"1.0\" ?>" + |
||||
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" + |
||||
"<s:Body>" + message + "</s:Body></s:Envelope>" |
||||
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"") |
||||
req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3") |
||||
//req.Header.Set("Transfer-Encoding", "chunked")
|
||||
req.Header.Set("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#"+function+"\"") |
||||
req.Header.Set("Connection", "Close") |
||||
req.Header.Set("Cache-Control", "no-cache") |
||||
req.Header.Set("Pragma", "no-cache") |
||||
|
||||
// log.Stderr("soapRequest ", req)
|
||||
//fmt.Println(fullMessage)
|
||||
|
||||
r, err = http.DefaultClient.Do(req) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
if r.Body != nil { |
||||
defer r.Body.Close() |
||||
} |
||||
|
||||
if r.StatusCode >= 400 { |
||||
// log.Stderr(function, r.StatusCode)
|
||||
err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function) |
||||
r = nil |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
type statusInfo struct { |
||||
externalIpAddress string |
||||
} |
||||
|
||||
func (n *upnpNAT) getStatusInfo() (info statusInfo, err error) { |
||||
|
||||
message := "<u:GetStatusInfo xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" + |
||||
"</u:GetStatusInfo>" |
||||
|
||||
var response *http.Response |
||||
response, err = soapRequest(n.serviceURL, "GetStatusInfo", message) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
// TODO: Write a soap reply parser. It has to eat the Body and envelope tags...
|
||||
|
||||
response.Body.Close() |
||||
return |
||||
} |
||||
|
||||
func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) { |
||||
info, err := n.getStatusInfo() |
||||
if err != nil { |
||||
return |
||||
} |
||||
addr = net.ParseIP(info.externalIpAddress) |
||||
return |
||||
} |
||||
|
||||
func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) { |
||||
// A single concatenation would break ARM compilation.
|
||||
message := "<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" + |
||||
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) |
||||
message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" |
||||
message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" + |
||||
"<NewInternalClient>" + n.ourIP + "</NewInternalClient>" + |
||||
"<NewEnabled>1</NewEnabled><NewPortMappingDescription>" |
||||
message += description + |
||||
"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) + |
||||
"</NewLeaseDuration></u:AddPortMapping>" |
||||
|
||||
var response *http.Response |
||||
response, err = soapRequest(n.serviceURL, "AddPortMapping", message) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
// TODO: check response to see if the port was forwarded
|
||||
// log.Println(message, response)
|
||||
mappedExternalPort = externalPort |
||||
_ = response |
||||
return |
||||
} |
||||
|
||||
func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) { |
||||
|
||||
message := "<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" + |
||||
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) + |
||||
"</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" + |
||||
"</u:DeletePortMapping>" |
||||
|
||||
var response *http.Response |
||||
response, err = soapRequest(n.serviceURL, "DeletePortMapping", message) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
// TODO: check response to see if the port was deleted
|
||||
// log.Println(message, response)
|
||||
_ = response |
||||
return |
||||
} |
@ -1,881 +0,0 @@ |
||||
package eth |
||||
|
||||
import ( |
||||
"bytes" |
||||
"container/list" |
||||
"fmt" |
||||
"math" |
||||
"math/big" |
||||
"net" |
||||
"strconv" |
||||
"strings" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/ethutil" |
||||
"github.com/ethereum/go-ethereum/logger" |
||||
"github.com/ethereum/go-ethereum/wire" |
||||
) |
||||
|
||||
var peerlogger = logger.NewLogger("PEER") |
||||
|
||||
const ( |
||||
// The size of the output buffer for writing messages
|
||||
outputBufferSize = 50 |
||||
// Current protocol version
|
||||
ProtocolVersion = 49 |
||||
// Current P2P version
|
||||
P2PVersion = 2 |
||||
// Ethereum network version
|
||||
NetVersion = 0 |
||||
// Interval for ping/pong message
|
||||
pingPongTimer = 2 * time.Second |
||||
) |
||||
|
||||
type DiscReason byte |
||||
|
||||
const ( |
||||
// Values are given explicitly instead of by iota because these values are
|
||||
// defined by the wire protocol spec; it is easier for humans to ensure
|
||||
// correctness when values are explicit.
|
||||
DiscRequested DiscReason = iota |
||||
DiscReTcpSysErr |
||||
DiscBadProto |
||||
DiscBadPeer |
||||
DiscTooManyPeers |
||||
DiscConnDup |
||||
DiscGenesisErr |
||||
DiscProtoErr |
||||
DiscQuitting |
||||
) |
||||
|
||||
var discReasonToString = []string{ |
||||
"requested", |
||||
"TCP sys error", |
||||
"bad protocol", |
||||
"useless peer", |
||||
"too many peers", |
||||
"already connected", |
||||
"wrong genesis block", |
||||
"incompatible network", |
||||
"quitting", |
||||
} |
||||
|
||||
func (d DiscReason) String() string { |
||||
if len(discReasonToString) < int(d) { |
||||
return "Unknown" |
||||
} |
||||
|
||||
return discReasonToString[d] |
||||
} |
||||
|
||||
// Peer capabilities
|
||||
type Caps byte |
||||
|
||||
const ( |
||||
CapPeerDiscTy Caps = 1 << iota |
||||
CapTxTy |
||||
CapChainTy |
||||
|
||||
CapDefault = CapChainTy | CapTxTy | CapPeerDiscTy |
||||
) |
||||
|
||||
var capsToString = map[Caps]string{ |
||||
CapPeerDiscTy: "Peer discovery", |
||||
CapTxTy: "Transaction relaying", |
||||
CapChainTy: "Block chain relaying", |
||||
} |
||||
|
||||
func (c Caps) IsCap(cap Caps) bool { |
||||
return c&cap > 0 |
||||
} |
||||
|
||||
func (c Caps) String() string { |
||||
var caps []string |
||||
if c.IsCap(CapPeerDiscTy) { |
||||
caps = append(caps, capsToString[CapPeerDiscTy]) |
||||
} |
||||
if c.IsCap(CapChainTy) { |
||||
caps = append(caps, capsToString[CapChainTy]) |
||||
} |
||||
if c.IsCap(CapTxTy) { |
||||
caps = append(caps, capsToString[CapTxTy]) |
||||
} |
||||
|
||||
return strings.Join(caps, " | ") |
||||
} |
||||
|
||||
type Peer struct { |
||||
// Ethereum interface
|
||||
ethereum *Ethereum |
||||
// Net connection
|
||||
conn net.Conn |
||||
// Output queue which is used to communicate and handle messages
|
||||
outputQueue chan *wire.Msg |
||||
// Quit channel
|
||||
quit chan bool |
||||
// Determines whether it's an inbound or outbound peer
|
||||
inbound bool |
||||
// Flag for checking the peer's connectivity state
|
||||
connected int32 |
||||
disconnect int32 |
||||
// Last known message send
|
||||
lastSend time.Time |
||||
// Indicated whether a verack has been send or not
|
||||
// This flag is used by writeMessage to check if messages are allowed
|
||||
// to be send or not. If no version is known all messages are ignored.
|
||||
versionKnown bool |
||||
statusKnown bool |
||||
|
||||
// Last received pong message
|
||||
lastPong int64 |
||||
lastBlockReceived time.Time |
||||
doneFetchingHashes bool |
||||
lastHashAt time.Time |
||||
lastHashRequestedAt time.Time |
||||
|
||||
host []byte |
||||
port uint16 |
||||
caps Caps |
||||
td *big.Int |
||||
bestHash []byte |
||||
lastReceivedHash []byte |
||||
requestedHashes [][]byte |
||||
|
||||
// This peer's public key
|
||||
pubkey []byte |
||||
|
||||
// Indicated whether the node is catching up or not
|
||||
catchingUp bool |
||||
diverted bool |
||||
blocksRequested int |
||||
|
||||
version string |
||||
|
||||
// We use this to give some kind of pingtime to a node, not very accurate, could be improved.
|
||||
pingTime time.Duration |
||||
pingStartTime time.Time |
||||
|
||||
lastRequestedBlock *types.Block |
||||
|
||||
protocolCaps *ethutil.Value |
||||
} |
||||
|
||||
func NewPeer(conn net.Conn, ethereum *Ethereum, inbound bool) *Peer { |
||||
pubkey := ethereum.KeyManager().PublicKey()[1:] |
||||
|
||||
return &Peer{ |
||||
outputQueue: make(chan *wire.Msg, outputBufferSize), |
||||
quit: make(chan bool), |
||||
ethereum: ethereum, |
||||
conn: conn, |
||||
inbound: inbound, |
||||
disconnect: 0, |
||||
connected: 1, |
||||
port: 30303, |
||||
pubkey: pubkey, |
||||
blocksRequested: 10, |
||||
caps: ethereum.ServerCaps(), |
||||
version: ethereum.ClientIdentity().String(), |
||||
protocolCaps: ethutil.NewValue(nil), |
||||
td: big.NewInt(0), |
||||
doneFetchingHashes: true, |
||||
} |
||||
} |
||||
|
||||
func NewOutboundPeer(addr string, ethereum *Ethereum, caps Caps) *Peer { |
||||
p := &Peer{ |
||||
outputQueue: make(chan *wire.Msg, outputBufferSize), |
||||
quit: make(chan bool), |
||||
ethereum: ethereum, |
||||
inbound: false, |
||||
connected: 0, |
||||
disconnect: 0, |
||||
port: 30303, |
||||
caps: caps, |
||||
version: ethereum.ClientIdentity().String(), |
||||
protocolCaps: ethutil.NewValue(nil), |
||||
td: big.NewInt(0), |
||||
doneFetchingHashes: true, |
||||
} |
||||
|
||||
// Set up the connection in another goroutine so we don't block the main thread
|
||||
go func() { |
||||
conn, err := p.Connect(addr) |
||||
if err != nil { |
||||
//peerlogger.Debugln("Connection to peer failed. Giving up.", err)
|
||||
p.Stop() |
||||
return |
||||
} |
||||
p.conn = conn |
||||
|
||||
// Atomically set the connection state
|
||||
atomic.StoreInt32(&p.connected, 1) |
||||
atomic.StoreInt32(&p.disconnect, 0) |
||||
|
||||
p.Start() |
||||
}() |
||||
|
||||
return p |
||||
} |
||||
|
||||
func (self *Peer) Connect(addr string) (conn net.Conn, err error) { |
||||
const maxTries = 3 |
||||
for attempts := 0; attempts < maxTries; attempts++ { |
||||
conn, err = net.DialTimeout("tcp", addr, 10*time.Second) |
||||
if err != nil { |
||||
time.Sleep(time.Duration(attempts*20) * time.Second) |
||||
continue |
||||
} |
||||
|
||||
// Success
|
||||
return |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
// Getters
|
||||
func (p *Peer) PingTime() string { |
||||
return p.pingTime.String() |
||||
} |
||||
func (p *Peer) Inbound() bool { |
||||
return p.inbound |
||||
} |
||||
func (p *Peer) LastSend() time.Time { |
||||
return p.lastSend |
||||
} |
||||
func (p *Peer) LastPong() int64 { |
||||
return p.lastPong |
||||
} |
||||
func (p *Peer) Host() []byte { |
||||
return p.host |
||||
} |
||||
func (p *Peer) Port() uint16 { |
||||
return p.port |
||||
} |
||||
func (p *Peer) Version() string { |
||||
return p.version |
||||
} |
||||
func (p *Peer) Connected() *int32 { |
||||
return &p.connected |
||||
} |
||||
|
||||
// Setters
|
||||
func (p *Peer) SetVersion(version string) { |
||||
p.version = version |
||||
} |
||||
|
||||
// Outputs any RLP encoded data to the peer
|
||||
func (p *Peer) QueueMessage(msg *wire.Msg) { |
||||
if atomic.LoadInt32(&p.connected) != 1 { |
||||
return |
||||
} |
||||
p.outputQueue <- msg |
||||
} |
||||
|
||||
func (p *Peer) writeMessage(msg *wire.Msg) { |
||||
// Ignore the write if we're not connected
|
||||
if atomic.LoadInt32(&p.connected) != 1 { |
||||
return |
||||
} |
||||
|
||||
if !p.versionKnown { |
||||
switch msg.Type { |
||||
case wire.MsgHandshakeTy: // Ok
|
||||
default: // Anything but ack is allowed
|
||||
return |
||||
} |
||||
} else { |
||||
/* |
||||
if !p.statusKnown { |
||||
switch msg.Type { |
||||
case wire.MsgStatusTy: // Ok
|
||||
default: // Anything but ack is allowed
|
||||
return |
||||
} |
||||
} |
||||
*/ |
||||
} |
||||
|
||||
peerlogger.DebugDetailf("(%v) <= %v\n", p.conn.RemoteAddr(), formatMessage(msg)) |
||||
|
||||
err := wire.WriteMessage(p.conn, msg) |
||||
if err != nil { |
||||
peerlogger.Debugln(" Can't send message:", err) |
||||
// Stop the client if there was an error writing to it
|
||||
p.Stop() |
||||
return |
||||
} |
||||
} |
||||
|
||||
// Outbound message handler. Outbound messages are handled here
|
||||
func (p *Peer) HandleOutbound() { |
||||
// The ping timer. Makes sure that every 2 minutes a ping is send to the peer
|
||||
pingTimer := time.NewTicker(pingPongTimer) |
||||
serviceTimer := time.NewTicker(10 * time.Second) |
||||
|
||||
out: |
||||
for { |
||||
skip: |
||||
select { |
||||
// Main message queue. All outbound messages are processed through here
|
||||
case msg := <-p.outputQueue: |
||||
if !p.statusKnown { |
||||
switch msg.Type { |
||||
case wire.MsgTxTy, wire.MsgGetBlockHashesTy, wire.MsgBlockHashesTy, wire.MsgGetBlocksTy, wire.MsgBlockTy: |
||||
break skip |
||||
} |
||||
} |
||||
|
||||
switch msg.Type { |
||||
case wire.MsgGetBlockHashesTy: |
||||
p.lastHashRequestedAt = time.Now() |
||||
} |
||||
|
||||
p.writeMessage(msg) |
||||
p.lastSend = time.Now() |
||||
|
||||
// Ping timer
|
||||
case <-pingTimer.C: |
||||
p.writeMessage(wire.NewMessage(wire.MsgPingTy, "")) |
||||
p.pingStartTime = time.Now() |
||||
|
||||
// Service timer takes care of peer broadcasting, transaction
|
||||
// posting or block posting
|
||||
case <-serviceTimer.C: |
||||
p.QueueMessage(wire.NewMessage(wire.MsgGetPeersTy, "")) |
||||
|
||||
case <-p.quit: |
||||
// Break out of the for loop if a quit message is posted
|
||||
break out |
||||
} |
||||
} |
||||
|
||||
clean: |
||||
// This loop is for draining the output queue and anybody waiting for us
|
||||
for { |
||||
select { |
||||
case <-p.outputQueue: |
||||
// TODO
|
||||
default: |
||||
break clean |
||||
} |
||||
} |
||||
} |
||||
|
||||
func formatMessage(msg *wire.Msg) (ret string) { |
||||
ret = fmt.Sprintf("%v %v", msg.Type, msg.Data) |
||||
|
||||
/* |
||||
XXX Commented out because I need the log level here to determine |
||||
if i should or shouldn't generate this message |
||||
*/ |
||||
/* |
||||
switch msg.Type { |
||||
case wire.MsgPeersTy: |
||||
ret += fmt.Sprintf("(%d entries)", msg.Data.Len()) |
||||
case wire.MsgBlockTy: |
||||
b1, b2 := chain.NewBlockFromRlpValue(msg.Data.Get(0)), ethchain.NewBlockFromRlpValue(msg.Data.Get(msg.Data.Len()-1)) |
||||
ret += fmt.Sprintf("(%d entries) %x - %x", msg.Data.Len(), b1.Hash()[0:4], b2.Hash()[0:4]) |
||||
case wire.MsgBlockHashesTy: |
||||
h1, h2 := msg.Data.Get(0).Bytes(), msg.Data.Get(msg.Data.Len()-1).Bytes() |
||||
ret += fmt.Sprintf("(%d entries) %x - %x", msg.Data.Len(), h1, h2) |
||||
} |
||||
*/ |
||||
|
||||
return |
||||
} |
||||
|
||||
// Inbound handler. Inbound messages are received here and passed to the appropriate methods
|
||||
func (p *Peer) HandleInbound() { |
||||
for atomic.LoadInt32(&p.disconnect) == 0 { |
||||
|
||||
// HMM?
|
||||
time.Sleep(50 * time.Millisecond) |
||||
// Wait for a message from the peer
|
||||
msgs, err := wire.ReadMessages(p.conn) |
||||
if err != nil { |
||||
peerlogger.Debugln(err) |
||||
} |
||||
for _, msg := range msgs { |
||||
peerlogger.DebugDetailf("(%v) => %v\n", p.conn.RemoteAddr(), formatMessage(msg)) |
||||
|
||||
switch msg.Type { |
||||
case wire.MsgHandshakeTy: |
||||
// Version message
|
||||
p.handleHandshake(msg) |
||||
|
||||
//if p.caps.IsCap(CapPeerDiscTy) {
|
||||
p.QueueMessage(wire.NewMessage(wire.MsgGetPeersTy, "")) |
||||
//}
|
||||
|
||||
case wire.MsgDiscTy: |
||||
p.Stop() |
||||
peerlogger.Infoln("Disconnect peer: ", DiscReason(msg.Data.Get(0).Uint())) |
||||
case wire.MsgPingTy: |
||||
// Respond back with pong
|
||||
p.QueueMessage(wire.NewMessage(wire.MsgPongTy, "")) |
||||
case wire.MsgPongTy: |
||||
// If we received a pong back from a peer we set the
|
||||
// last pong so the peer handler knows this peer is still
|
||||
// active.
|
||||
p.lastPong = time.Now().Unix() |
||||
p.pingTime = time.Since(p.pingStartTime) |
||||
case wire.MsgTxTy: |
||||
// If the message was a transaction queue the transaction
|
||||
// in the TxPool where it will undergo validation and
|
||||
// processing when a new block is found
|
||||
for i := 0; i < msg.Data.Len(); i++ { |
||||
tx := types.NewTransactionFromValue(msg.Data.Get(i)) |
||||
err := p.ethereum.TxPool().Add(tx) |
||||
if err != nil { |
||||
peerlogger.Infoln(err) |
||||
} else { |
||||
peerlogger.Infof("tx OK (%x)\n", tx.Hash()[0:4]) |
||||
} |
||||
} |
||||
case wire.MsgGetPeersTy: |
||||
// Peer asked for list of connected peers
|
||||
//p.pushPeers()
|
||||
case wire.MsgPeersTy: |
||||
// Received a list of peers (probably because MsgGetPeersTy was send)
|
||||
data := msg.Data |
||||
// Create new list of possible peers for the ethereum to process
|
||||
peers := make([]string, data.Len()) |
||||
// Parse each possible peer
|
||||
for i := 0; i < data.Len(); i++ { |
||||
value := data.Get(i) |
||||
peers[i] = unpackAddr(value.Get(0), value.Get(1).Uint()) |
||||
} |
||||
|
||||
// Connect to the list of peers
|
||||
p.ethereum.ProcessPeerList(peers) |
||||
|
||||
case wire.MsgStatusTy: |
||||
// Handle peer's status msg
|
||||
p.handleStatus(msg) |
||||
} |
||||
|
||||
// TMP
|
||||
if p.statusKnown { |
||||
switch msg.Type { |
||||
|
||||
case wire.MsgGetBlockHashesTy: |
||||
if msg.Data.Len() < 2 { |
||||
peerlogger.Debugln("err: argument length invalid ", msg.Data.Len()) |
||||
} |
||||
|
||||
hash := msg.Data.Get(0).Bytes() |
||||
amount := msg.Data.Get(1).Uint() |
||||
|
||||
hashes := p.ethereum.ChainManager().GetChainHashesFromHash(hash, amount) |
||||
|
||||
p.QueueMessage(wire.NewMessage(wire.MsgBlockHashesTy, ethutil.ByteSliceToInterface(hashes))) |
||||
|
||||
case wire.MsgGetBlocksTy: |
||||
// Limit to max 300 blocks
|
||||
max := int(math.Min(float64(msg.Data.Len()), 300.0)) |
||||
var blocks []interface{} |
||||
|
||||
for i := 0; i < max; i++ { |
||||
hash := msg.Data.Get(i).Bytes() |
||||
block := p.ethereum.ChainManager().GetBlock(hash) |
||||
if block != nil { |
||||
blocks = append(blocks, block.Value().Raw()) |
||||
} |
||||
} |
||||
|
||||
p.QueueMessage(wire.NewMessage(wire.MsgBlockTy, blocks)) |
||||
|
||||
case wire.MsgBlockHashesTy: |
||||
p.catchingUp = true |
||||
|
||||
blockPool := p.ethereum.blockPool |
||||
|
||||
foundCommonHash := false |
||||
p.lastHashAt = time.Now() |
||||
|
||||
it := msg.Data.NewIterator() |
||||
for it.Next() { |
||||
hash := it.Value().Bytes() |
||||
p.lastReceivedHash = hash |
||||
|
||||
if blockPool.HasCommonHash(hash) { |
||||
foundCommonHash = true |
||||
|
||||
break |
||||
} |
||||
|
||||
blockPool.AddHash(hash, p) |
||||
} |
||||
|
||||
if !foundCommonHash { |
||||
p.FetchHashes() |
||||
} else { |
||||
peerlogger.Infof("Found common hash (%x...)\n", p.lastReceivedHash[0:4]) |
||||
p.doneFetchingHashes = true |
||||
} |
||||
|
||||
case wire.MsgBlockTy: |
||||
p.catchingUp = true |
||||
|
||||
blockPool := p.ethereum.blockPool |
||||
|
||||
it := msg.Data.NewIterator() |
||||
for it.Next() { |
||||
block := types.NewBlockFromRlpValue(it.Value()) |
||||
blockPool.Add(block, p) |
||||
|
||||
p.lastBlockReceived = time.Now() |
||||
} |
||||
case wire.MsgNewBlockTy: |
||||
var ( |
||||
blockPool = p.ethereum.blockPool |
||||
block = types.NewBlockFromRlpValue(msg.Data.Get(0)) |
||||
td = msg.Data.Get(1).BigInt() |
||||
) |
||||
|
||||
if td.Cmp(blockPool.td) > 0 { |
||||
p.ethereum.blockPool.AddNew(block, p) |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
} |
||||
|
||||
p.Stop() |
||||
} |
||||
|
||||
func (self *Peer) FetchBlocks(hashes [][]byte) { |
||||
if len(hashes) > 0 { |
||||
peerlogger.Debugf("Fetching blocks (%d)\n", len(hashes)) |
||||
|
||||
self.QueueMessage(wire.NewMessage(wire.MsgGetBlocksTy, ethutil.ByteSliceToInterface(hashes))) |
||||
} |
||||
} |
||||
|
||||
func (self *Peer) FetchHashes() bool { |
||||
blockPool := self.ethereum.blockPool |
||||
|
||||
return blockPool.FetchHashes(self) |
||||
} |
||||
|
||||
func (self *Peer) FetchingHashes() bool { |
||||
return !self.doneFetchingHashes |
||||
} |
||||
|
||||
// General update method
|
||||
func (self *Peer) update() { |
||||
serviceTimer := time.NewTicker(100 * time.Millisecond) |
||||
|
||||
out: |
||||
for { |
||||
select { |
||||
case <-serviceTimer.C: |
||||
if self.IsCap("eth") { |
||||
var ( |
||||
sinceBlock = time.Since(self.lastBlockReceived) |
||||
) |
||||
|
||||
if sinceBlock > 5*time.Second { |
||||
self.catchingUp = false |
||||
} |
||||
} |
||||
case <-self.quit: |
||||
break out |
||||
} |
||||
} |
||||
|
||||
serviceTimer.Stop() |
||||
} |
||||
|
||||
func (p *Peer) Start() { |
||||
peerHost, peerPort, _ := net.SplitHostPort(p.conn.LocalAddr().String()) |
||||
servHost, servPort, _ := net.SplitHostPort(p.conn.RemoteAddr().String()) |
||||
|
||||
if p.inbound { |
||||
p.host, p.port = packAddr(peerHost, peerPort) |
||||
} else { |
||||
p.host, p.port = packAddr(servHost, servPort) |
||||
} |
||||
|
||||
err := p.pushHandshake() |
||||
if err != nil { |
||||
peerlogger.Debugln("Peer can't send outbound version ack", err) |
||||
|
||||
p.Stop() |
||||
|
||||
return |
||||
} |
||||
|
||||
go p.HandleOutbound() |
||||
// Run the inbound handler in a new goroutine
|
||||
go p.HandleInbound() |
||||
// Run the general update handler
|
||||
go p.update() |
||||
|
||||
// Wait a few seconds for startup and then ask for an initial ping
|
||||
time.Sleep(2 * time.Second) |
||||
p.writeMessage(wire.NewMessage(wire.MsgPingTy, "")) |
||||
p.pingStartTime = time.Now() |
||||
|
||||
} |
||||
|
||||
func (p *Peer) Stop() { |
||||
p.StopWithReason(DiscRequested) |
||||
} |
||||
|
||||
func (p *Peer) StopWithReason(reason DiscReason) { |
||||
if atomic.AddInt32(&p.disconnect, 1) != 1 { |
||||
return |
||||
} |
||||
|
||||
// Pre-emptively remove the peer; don't wait for reaping. We already know it's dead if we are here
|
||||
p.ethereum.RemovePeer(p) |
||||
|
||||
close(p.quit) |
||||
if atomic.LoadInt32(&p.connected) != 0 { |
||||
p.writeMessage(wire.NewMessage(wire.MsgDiscTy, reason)) |
||||
p.conn.Close() |
||||
} |
||||
} |
||||
|
||||
func (p *Peer) peersMessage() *wire.Msg { |
||||
outPeers := make([]interface{}, len(p.ethereum.InOutPeers())) |
||||
// Serialise each peer
|
||||
for i, peer := range p.ethereum.InOutPeers() { |
||||
// Don't return localhost as valid peer
|
||||
if !net.ParseIP(peer.conn.RemoteAddr().String()).IsLoopback() { |
||||
outPeers[i] = peer.RlpData() |
||||
} |
||||
} |
||||
|
||||
// Return the message to the peer with the known list of connected clients
|
||||
return wire.NewMessage(wire.MsgPeersTy, outPeers) |
||||
} |
||||
|
||||
// Pushes the list of outbound peers to the client when requested
|
||||
func (p *Peer) pushPeers() { |
||||
p.QueueMessage(p.peersMessage()) |
||||
} |
||||
|
||||
func (self *Peer) pushStatus() { |
||||
msg := wire.NewMessage(wire.MsgStatusTy, []interface{}{ |
||||
uint32(ProtocolVersion), |
||||
uint32(NetVersion), |
||||
self.ethereum.ChainManager().TD, |
||||
self.ethereum.ChainManager().CurrentBlock.Hash(), |
||||
self.ethereum.ChainManager().Genesis().Hash(), |
||||
}) |
||||
|
||||
self.QueueMessage(msg) |
||||
} |
||||
|
||||
func (self *Peer) handleStatus(msg *wire.Msg) { |
||||
c := msg.Data |
||||
|
||||
var ( |
||||
//protoVersion = c.Get(0).Uint()
|
||||
netVersion = c.Get(1).Uint() |
||||
td = c.Get(2).BigInt() |
||||
bestHash = c.Get(3).Bytes() |
||||
genesis = c.Get(4).Bytes() |
||||
) |
||||
|
||||
if bytes.Compare(self.ethereum.ChainManager().Genesis().Hash(), genesis) != 0 { |
||||
loggerger.Warnf("Invalid genisis hash %x. Disabling [eth]\n", genesis) |
||||
return |
||||
} |
||||
|
||||
if netVersion != NetVersion { |
||||
loggerger.Warnf("Invalid network version %d. Disabling [eth]\n", netVersion) |
||||
return |
||||
} |
||||
|
||||
/* |
||||
if protoVersion != ProtocolVersion { |
||||
loggerger.Warnf("Invalid protocol version %d. Disabling [eth]\n", protoVersion) |
||||
return |
||||
} |
||||
*/ |
||||
|
||||
// Get the td and last hash
|
||||
self.td = td |
||||
self.bestHash = bestHash |
||||
self.lastReceivedHash = bestHash |
||||
|
||||
self.statusKnown = true |
||||
|
||||
// Compare the total TD with the blockchain TD. If remote is higher
|
||||
// fetch hashes from highest TD node.
|
||||
self.FetchHashes() |
||||
|
||||
loggerger.Infof("Peer is [eth] capable. (TD = %v ~ %x)", self.td, self.bestHash) |
||||
|
||||
} |
||||
|
||||
func (p *Peer) pushHandshake() error { |
||||
pubkey := p.ethereum.KeyManager().PublicKey() |
||||
msg := wire.NewMessage(wire.MsgHandshakeTy, []interface{}{ |
||||
P2PVersion, []byte(p.version), []interface{}{[]interface{}{"eth", ProtocolVersion}}, p.port, pubkey[1:], |
||||
}) |
||||
|
||||
p.QueueMessage(msg) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (p *Peer) handleHandshake(msg *wire.Msg) { |
||||
c := msg.Data |
||||
|
||||
var ( |
||||
p2pVersion = c.Get(0).Uint() |
||||
clientId = c.Get(1).Str() |
||||
caps = c.Get(2) |
||||
port = c.Get(3).Uint() |
||||
pub = c.Get(4).Bytes() |
||||
) |
||||
|
||||
// Check correctness of p2p protocol version
|
||||
if p2pVersion != P2PVersion { |
||||
peerlogger.Debugf("Invalid P2P version. Require protocol %d, received %d\n", P2PVersion, p2pVersion) |
||||
p.Stop() |
||||
return |
||||
} |
||||
|
||||
// Handle the pub key (validation, uniqueness)
|
||||
if len(pub) == 0 { |
||||
peerlogger.Warnln("Pubkey required, not supplied in handshake.") |
||||
p.Stop() |
||||
return |
||||
} |
||||
|
||||
// Self connect detection
|
||||
pubkey := p.ethereum.KeyManager().PublicKey() |
||||
if bytes.Compare(pubkey[1:], pub) == 0 { |
||||
p.Stop() |
||||
|
||||
return |
||||
} |
||||
|
||||
// Check for blacklisting
|
||||
for _, pk := range p.ethereum.blacklist { |
||||
if bytes.Compare(pk, pub) == 0 { |
||||
peerlogger.Debugf("Blacklisted peer tried to connect (%x...)\n", pubkey[0:4]) |
||||
p.StopWithReason(DiscBadPeer) |
||||
|
||||
return |
||||
} |
||||
} |
||||
|
||||
usedPub := 0 |
||||
// This peer is already added to the peerlist so we expect to find a double pubkey at least once
|
||||
eachPeer(p.ethereum.Peers(), func(peer *Peer, e *list.Element) { |
||||
if bytes.Compare(pub, peer.pubkey) == 0 { |
||||
usedPub++ |
||||
} |
||||
}) |
||||
|
||||
if usedPub > 0 { |
||||
peerlogger.Debugf("Pubkey %x found more then once. Already connected to client.", p.pubkey) |
||||
p.Stop() |
||||
return |
||||
} |
||||
p.pubkey = pub |
||||
|
||||
// If this is an inbound connection send an ack back
|
||||
if p.inbound { |
||||
p.port = uint16(port) |
||||
} |
||||
|
||||
p.SetVersion(clientId) |
||||
|
||||
p.versionKnown = true |
||||
|
||||
p.ethereum.PushPeer(p) |
||||
p.ethereum.eventMux.Post(PeerListEvent{p.ethereum.Peers()}) |
||||
|
||||
p.protocolCaps = caps |
||||
|
||||
it := caps.NewIterator() |
||||
var capsStrs []string |
||||
for it.Next() { |
||||
cap := it.Value().Get(0).Str() |
||||
ver := it.Value().Get(1).Uint() |
||||
switch cap { |
||||
case "eth": |
||||
if ver != ProtocolVersion { |
||||
loggerger.Warnf("Invalid protocol version %d. Disabling [eth]\n", ver) |
||||
continue |
||||
} |
||||
p.pushStatus() |
||||
} |
||||
|
||||
capsStrs = append(capsStrs, fmt.Sprintf("%s/%d", cap, ver)) |
||||
} |
||||
|
||||
peerlogger.Infof("Added peer (%s) %d / %d (%v)\n", p.conn.RemoteAddr(), p.ethereum.Peers().Len(), p.ethereum.MaxPeers, capsStrs) |
||||
|
||||
peerlogger.Debugln(p) |
||||
} |
||||
|
||||
func (self *Peer) IsCap(cap string) bool { |
||||
capsIt := self.protocolCaps.NewIterator() |
||||
for capsIt.Next() { |
||||
if capsIt.Value().Str() == cap { |
||||
return true |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
func (self *Peer) Caps() *ethutil.Value { |
||||
return self.protocolCaps |
||||
} |
||||
|
||||
func (p *Peer) String() string { |
||||
var strBoundType string |
||||
if p.inbound { |
||||
strBoundType = "inbound" |
||||
} else { |
||||
strBoundType = "outbound" |
||||
} |
||||
var strConnectType string |
||||
if atomic.LoadInt32(&p.disconnect) == 0 { |
||||
strConnectType = "connected" |
||||
} else { |
||||
strConnectType = "disconnected" |
||||
} |
||||
|
||||
return fmt.Sprintf("[%s] (%s) %v %s", strConnectType, strBoundType, p.conn.RemoteAddr(), p.version) |
||||
|
||||
} |
||||
|
||||
func (p *Peer) RlpData() []interface{} { |
||||
return []interface{}{p.host, p.port, p.pubkey} |
||||
} |
||||
|
||||
func packAddr(address, _port string) (host []byte, port uint16) { |
||||
p, _ := strconv.Atoi(_port) |
||||
port = uint16(p) |
||||
|
||||
h := net.ParseIP(address) |
||||
if ip := h.To4(); ip != nil { |
||||
host = []byte(ip) |
||||
} else { |
||||
host = []byte(h) |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
func unpackAddr(value *ethutil.Value, p uint64) string { |
||||
host, _ := net.IP(value.Bytes()).MarshalText() |
||||
prt := strconv.Itoa(int(p)) |
||||
|
||||
return net.JoinHostPort(string(host), prt) |
||||
} |
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue