Compare commits

...

63 Commits

Author SHA1 Message Date
021c3c2816 params: release Geth 1.6.1, Deripors of Ohratuu 2017-05-04 14:11:45 +03:00
ff2c966e7f Merge pull request #14418 from karalabe/rinkeby-flag
cmd, core, params: add --rinkeby flag for fast connectivity
2017-05-04 13:54:02 +03:00
14cc40a31a Hive-test fixes (#14419)
* core: Fix for consensus test gasLimit > 2^63-1 https://github.com/ethereum/tests/blob/develop/BlockchainTests/bcInvalidHeaderTest.json#L238

* core: fix testcase for uncle gasUsage > gasLimit : https://github.com/ethereum/tests/blob/develop/BlockchainTests/EIP150/bcUncleHeaderValiditiy.json#L986

* math/big: rename TTM63m1 -> MaxBig63, + go fmt

* core: documentation
2017-05-04 13:53:42 +03:00
881df0e629 Merge pull request #14413 from bas-vk/cli-chain-mngt
Migrate remaining flags/command to new style
2017-05-04 13:31:09 +03:00
1c2f6f5597 Merge pull request #14402 from karalabe/tiered-faucet
cmd/faucet, cmd/puppeth: support multi-tiered faucet
2017-05-04 13:07:41 +03:00
d51a9fd6b7 cmd, core, params: add --rinkeby flag for fast connectivity 2017-05-04 12:36:20 +03:00
464f30d301 cmd/faucet: fix period to days conversion 2017-05-04 11:43:18 +03:00
8a28408616 cmd/faucet, cmd/puppeth: support multi-tiered faucet 2017-05-04 11:42:43 +03:00
e1dc7ece62 Merge pull request #14414 from gluk256/77_release
build: wnode added to the build configuration
2017-05-03 16:45:39 +03:00
99127ff2e7 build: wnode added to the build configuration 2017-05-03 15:36:02 +02:00
81d6ec908a cmd/geth: migrate dumpconfig command/flags 2017-05-03 14:39:07 +02:00
38b4fc8069 cmd/geth: migrate bug command/flags 2017-05-03 14:33:08 +02:00
11ab73f6d8 cmd/geth: migrate metric command/flags 2017-05-03 14:33:08 +02:00
18e9cb1187 cmd/geth: reorganise misc commands/flags 2017-05-03 14:33:07 +02:00
502a2bd69f cmd/geth: reorganise console/attach commands/flags 2017-05-03 14:33:07 +02:00
8b517d7f00 cmd/geth: reorganise chain commands/flags 2017-05-03 14:33:03 +02:00
cad071009d Merge pull request #14412 from karalabe/init-both-chains
cmd/geth, cmd/utils: init/removedb on light/full dbs simultaneously
2017-05-03 14:32:24 +03:00
181a3309df cmd/geth, cmd/utils: init/removedb on light/full dbs simultaneously 2017-05-03 13:35:47 +03:00
02fa3e3179 Merge pull request #14411 from karalabe/clique-double-sign
consensus/clique: fix overflow on recent signer check around genesis
2017-05-03 11:45:36 +03:00
a8eafcdc0e Merge pull request #13885 from bas-vk/rpc_generic_pubsub
rpc: support subscriptions under custom namespaces
2017-05-03 11:41:07 +03:00
bcf2465b0b consensus/clique: fix overflow on recent signer check around genesis 2017-05-03 11:01:06 +03:00
c3dc01caf1 README: add config to genesis.json (#14373)
README: add config to genesis.json
2017-05-03 09:10:36 +02:00
cf4faa491a cmd/puppeth, vendor: update ssh, manage server keys (#14398) 2017-05-03 09:09:34 +02:00
59966255ad Merge pull request #14407 from karalabe/ethash-generation-race
consensus/ethash: fix a timestamp update race
2017-05-03 09:17:01 +03:00
f8acc0af7e consensus/ethash: fix a timestamp update race 2017-05-02 16:48:36 +03:00
02a29060d2 Merge pull request #14406 from karalabe/downloader-sensitive-code
eth/downloader: fix a potential issue against future refactors
2017-05-02 16:31:31 +03:00
0255ed6335 Merge pull request #14403 from fjl/console-number
console: avoid float64 when remarshaling parameters
2017-05-02 16:24:34 +03:00
96c2ab22e0 eth/downloader: fix a potential issue against future refactors 2017-05-02 16:14:35 +03:00
5494e6e7ab Merge pull request #14399 from bas-vk/rpc-cors
rpc: disable CORS if user has not specified a custom config
2017-05-02 15:53:47 +03:00
32db571681 console: avoid float64 when remarshaling parameters
With Go 1.7, encoding/json marshals float64 using scientific
notation ("10e+6"), but Go's int and *big.Int decoders don't accept such
numbers. This change disables use of float64 to avoid the problem.
2017-05-02 13:42:55 +02:00
a6af56fa4d rpc: disable CORS if user has not specified custom config 2017-05-02 11:14:40 +02:00
5884606ec3 Merge pull request #14388 from bas-vk/cli-account-mngt
cmd/geth: reorganise account/wallet command/flags
2017-05-02 10:05:20 +03:00
f6c0f76cc5 cmd/geth: reorganise account/wallet command/flags 2017-04-28 14:01:54 +02:00
f9be9a2302 whisper: switching to v5 + minor refactoring (#14387) 2017-04-28 11:57:15 +02:00
95f0bd0acf whisper: message format refactoring (#14335)
* whisper: salt removed from AES encryption
* whisper: padding format updated
* whisper: padding test added
* whisper: padding refactored, tests fixed
* whisper: padding test updated
* whisper: wnode bugfix
* whisper: send/receive protocol updated
* whisper: minor update
* whisper: bugfix in test
* whisper: updated parameter names and comments
* whisper: functions renamed
* whisper: minor refactoring
2017-04-26 21:05:48 +02:00
8dce4c283d Merge pull request #14379 from farazdagi/fix/deadlock-in-node-wait
node: fixes deadlock on Wait()
2017-04-25 18:47:55 +03:00
fff16169c6 Merge pull request #14377 from karalabe/unify-network-ids
cmd, eth, les, mobile: make networkid uint64 everywhere
2017-04-25 18:30:56 +03:00
5f7eb78918 node: fixes deadlock on Wait() 2017-04-25 18:04:02 +03:00
e61035c5a3 cmd, eth, les, mobile: make networkid uint64 everywhere 2017-04-25 14:53:50 +03:00
37e3f561f1 rpc: support subscriptions under custom namespaces 2017-04-25 11:13:22 +02:00
ba3bcd16a6 Merge pull request #14350 from fjl/trie-iterator-skip-2
eth: add debug_storageRangeAt
2017-04-25 11:10:20 +03:00
207bd7d2cd eth: add debug_storageRangeAt 2017-04-25 02:14:32 +02:00
4047ccad2f trie: add start key to NodeIterator constructors
The 'step' method is split into two parts, 'peek' and 'push'. peek
returns the next state but doesn't make it current.

The end of iteration was previously tracked by setting 'trie' to nil.
End of iteration is now tracked using the 'iteratorEnd' error, which is
slightly cleaner and requires less code.
2017-04-25 02:14:31 +02:00
a13e920af0 trie: clean up iterator constructors
Make it so each iterator has exactly one public constructor:

- NodeIterators can be created through a method.
- Iterators can be created through NewIterator on any NodeIterator.
2017-04-25 02:14:31 +02:00
f958d7d482 trie: rework and document key encoding
'encode' and 'decode' are meaningless because the code deals with three
encodings. Document the encodings and give a name to each one.
2017-04-25 02:14:31 +02:00
7cc6abeef6 Merge pull request #14372 from bas-vk/bootnodegenkey
cmd/bootnode: stop after generating/writing nodekey
2017-04-24 19:31:23 +03:00
54253aae4c internal/ethapi: return empty arrays instead of null (#14374)
* internal/ethapi: return empty arrays instead of null

* internal/ethapi: minor comments to avoid future regressions
2017-04-24 15:00:30 +03:00
09aabaea9f Merge pull request #14364 from fjl/core-remove-split-stat-ty
core, light: delete SplitStatTy, ChainSplitEvent (unused)
2017-04-24 11:43:22 +03:00
ecec454e92 cmd/bootnode: stop after generating/writing nodekey 2017-04-24 10:40:20 +02:00
7b2fc0643f core, light: delete SplitStatTy, ChainSplitEvent (unused) 2017-04-21 18:56:00 +02:00
d2fda73ad7 Merge pull request #14339 from karalabe/faucet-block-banned-users
cmd/faucet: further user validations and bot protection
2017-04-20 17:42:36 +03:00
5aa21d8b32 Merge pull request #14357 from karalabe/nousb-flag
cmd, node: add --nousb and node.Config.NoUSB to disable hw wallets
2017-04-20 17:40:57 +03:00
9fc90b6747 Merge pull request #14358 from karalabe/wrong-genesis-description
core: make genesis incompatibility error more explicit
2017-04-20 15:17:59 +03:00
edef84da2b core: make genesis incompatibility error more explicit 2017-04-20 14:14:13 +03:00
6430e672c9 cmd, node: add --nosub and node.Config.NoUSB to disable hw wallets 2017-04-20 14:01:51 +03:00
a31d268b76 trie: remove Key in MissingNodeError
The key was constructed from nibbles, which isn't possible for all
nodes. Remove the only use of Key in LightTrie by always retrying with
the original key that was looked up.
2017-04-18 14:52:11 +02:00
e353f9c088 Merge pull request #13886 from bas-vk/rpc_blocknum_parse
rpc: improve BlockNumber unmarshal parsing
2017-04-18 14:55:53 +03:00
af48a331bf cmd: integrate invisible recaptcha into puppeth 2017-04-16 20:53:27 +03:00
80e74fc1e0 cmd/faucet: fix websocket double close/reopen 2017-04-16 20:39:42 +03:00
03dffe3efd cmd/faucet: add optional recaptcha validation support 2017-04-16 19:49:40 +03:00
cb3f5f8b93 cmd/faucet: double check user against the GH website 2017-04-16 18:49:06 +03:00
c7a4d9cf8a VERSION, params: begin 1.6.1 release cycle 2017-04-14 13:43:10 +03:00
7d0ac94809 rpc: improve BlockNumber unmarshal parsing 2017-04-13 13:20:19 +02:00
112 changed files with 2651 additions and 1331 deletions

View File

@ -161,6 +161,12 @@ and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`)
```json ```json
{ {
"config": {
"chainId": 0,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"alloc" : {}, "alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000", "coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000", "difficulty" : "0x20000",

View File

@ -1 +1 @@
1.6.0 1.6.1

View File

@ -77,6 +77,7 @@ var (
executablePath("puppeth"), executablePath("puppeth"),
executablePath("rlpdump"), executablePath("rlpdump"),
executablePath("swarm"), executablePath("swarm"),
executablePath("wnode"),
} }
// A debian package is created for all executables listed here. // A debian package is created for all executables listed here.
@ -109,6 +110,10 @@ var (
Name: "swarm", Name: "swarm",
Description: "Ethereum Swarm daemon and tools", Description: "Ethereum Swarm daemon and tools",
}, },
{
Name: "wnode",
Description: "Ethereum Whisper diagnostic tool",
},
} }
// Distros for which packages are created. // Distros for which packages are created.

View File

@ -68,6 +68,7 @@ func main() {
if err = crypto.SaveECDSA(*genKey, nodeKey); err != nil { if err = crypto.SaveECDSA(*genKey, nodeKey); err != nil {
utils.Fatalf("%v", err) utils.Fatalf("%v", err)
} }
return
case *nodeKeyFile == "" && *nodeKeyHex == "": case *nodeKeyFile == "" && *nodeKeyHex == "":
utils.Fatalf("Use -nodekey or -nodekeyhex to specify a private key") utils.Fatalf("Use -nodekey or -nodekeyhex to specify a private key")
case *nodeKeyFile != "" && *nodeKeyHex != "": case *nodeKeyFile != "" && *nodeKeyHex != "":

View File

@ -27,10 +27,13 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"math"
"math/big" "math/big"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -60,12 +63,13 @@ var (
apiPortFlag = flag.Int("apiport", 8080, "Listener port for the HTTP API connection") apiPortFlag = flag.Int("apiport", 8080, "Listener port for the HTTP API connection")
ethPortFlag = flag.Int("ethport", 30303, "Listener port for the devp2p connection") ethPortFlag = flag.Int("ethport", 30303, "Listener port for the devp2p connection")
bootFlag = flag.String("bootnodes", "", "Comma separated bootnode enode URLs to seed with") bootFlag = flag.String("bootnodes", "", "Comma separated bootnode enode URLs to seed with")
netFlag = flag.Int("network", 0, "Network ID to use for the Ethereum protocol") netFlag = flag.Uint64("network", 0, "Network ID to use for the Ethereum protocol")
statsFlag = flag.String("ethstats", "", "Ethstats network monitoring auth string") statsFlag = flag.String("ethstats", "", "Ethstats network monitoring auth string")
netnameFlag = flag.String("faucet.name", "", "Network name to assign to the faucet") netnameFlag = flag.String("faucet.name", "", "Network name to assign to the faucet")
payoutFlag = flag.Int("faucet.amount", 1, "Number of Ethers to pay out per user request") payoutFlag = flag.Int("faucet.amount", 1, "Number of Ethers to pay out per user request")
minutesFlag = flag.Int("faucet.minutes", 1440, "Number of minutes to wait between funding rounds") minutesFlag = flag.Int("faucet.minutes", 1440, "Number of minutes to wait between funding rounds")
tiersFlag = flag.Int("faucet.tiers", 3, "Number of funding tiers to enable (x3 time, x2.5 funds)")
accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with") accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with")
accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds") accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds")
@ -73,6 +77,9 @@ var (
githubUser = flag.String("github.user", "", "GitHub user to authenticate with for Gist access") githubUser = flag.String("github.user", "", "GitHub user to authenticate with for Gist access")
githubToken = flag.String("github.token", "", "GitHub personal token to access Gists with") githubToken = flag.String("github.token", "", "GitHub personal token to access Gists with")
captchaToken = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side")
captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server side")
logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet") logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet")
) )
@ -85,21 +92,47 @@ func main() {
flag.Parse() flag.Parse()
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*logFlag), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*logFlag), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
// Construct the payout tiers
amounts := make([]string, *tiersFlag)
periods := make([]string, *tiersFlag)
for i := 0; i < *tiersFlag; i++ {
// Calculate the amount for the next tier and format it
amount := float64(*payoutFlag) * math.Pow(2.5, float64(i))
amounts[i] = fmt.Sprintf("%s Ethers", strconv.FormatFloat(amount, 'f', -1, 64))
if amount == 1 {
amounts[i] = strings.TrimSuffix(amounts[i], "s")
}
// Calcualte the period for th enext tier and format it
period := *minutesFlag * int(math.Pow(3, float64(i)))
periods[i] = fmt.Sprintf("%d mins", period)
if period%60 == 0 {
period /= 60
periods[i] = fmt.Sprintf("%d hours", period)
if period%24 == 0 {
period /= 24
periods[i] = fmt.Sprintf("%d days", period)
}
}
if period == 1 {
periods[i] = strings.TrimSuffix(periods[i], "s")
}
}
// Load up and render the faucet website // Load up and render the faucet website
tmpl, err := Asset("faucet.html") tmpl, err := Asset("faucet.html")
if err != nil { if err != nil {
log.Crit("Failed to load the faucet template", "err", err) log.Crit("Failed to load the faucet template", "err", err)
} }
period := fmt.Sprintf("%d minute(s)", *minutesFlag)
if *minutesFlag%60 == 0 {
period = fmt.Sprintf("%d hour(s)", *minutesFlag/60)
}
website := new(bytes.Buffer) website := new(bytes.Buffer)
template.Must(template.New("").Parse(string(tmpl))).Execute(website, map[string]interface{}{ err = template.Must(template.New("").Parse(string(tmpl))).Execute(website, map[string]interface{}{
"Network": *netnameFlag, "Network": *netnameFlag,
"Amount": *payoutFlag, "Amounts": amounts,
"Period": period, "Periods": periods,
"Recaptcha": *captchaToken,
}) })
if err != nil {
log.Crit("Failed to render the faucet template", "err", err)
}
// Load and parse the genesis block requested by the user // Load and parse the genesis block requested by the user
blob, err := ioutil.ReadFile(*genesisFlag) blob, err := ioutil.ReadFile(*genesisFlag)
if err != nil { if err != nil {
@ -167,14 +200,14 @@ type faucet struct {
price *big.Int // Current gas price to issue funds with price *big.Int // Current gas price to issue funds with
conns []*websocket.Conn // Currently live websocket connections conns []*websocket.Conn // Currently live websocket connections
history map[string]time.Time // History of users and their funding requests timeouts map[string]time.Time // History of users and their funding timeouts
reqs []*request // Currently pending funding requests reqs []*request // Currently pending funding requests
update chan struct{} // Channel to signal request updates update chan struct{} // Channel to signal request updates
lock sync.RWMutex // Lock protecting the faucet's internals lock sync.RWMutex // Lock protecting the faucet's internals
} }
func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network int, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) {
// Assemble the raw devp2p protocol stack // Assemble the raw devp2p protocol stack
stack, err := node.New(&node.Config{ stack, err := node.New(&node.Config{
Name: "geth", Name: "geth",
@ -236,7 +269,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network i
index: index, index: index,
keystore: ks, keystore: ks,
account: ks.Accounts()[0], account: ks.Accounts()[0],
history: make(map[string]time.Time), timeouts: make(map[string]time.Time),
update: make(chan struct{}, 1), update: make(chan struct{}, 1),
}, nil }, nil
} }
@ -290,14 +323,23 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
"peers": f.stack.Server().PeerCount(), "peers": f.stack.Server().PeerCount(),
"requests": f.reqs, "requests": f.reqs,
}) })
header, _ := f.client.HeaderByNumber(context.Background(), nil) // Send the initial block to the client
websocket.JSON.Send(conn, header) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
header, err := f.client.HeaderByNumber(ctx, nil)
cancel()
if err != nil {
log.Error("Failed to retrieve latest header", "err", err)
} else {
websocket.JSON.Send(conn, header)
}
// Keep reading requests from the websocket until the connection breaks // Keep reading requests from the websocket until the connection breaks
for { for {
// Fetch the next funding request and validate against github // Fetch the next funding request and validate against github
var msg struct { var msg struct {
URL string `json:"url"` URL string `json:"url"`
Tier uint `json:"tier"`
Captcha string `json:"captcha"`
} }
if err := websocket.JSON.Receive(conn, &msg); err != nil { if err := websocket.JSON.Receive(conn, &msg); err != nil {
return return
@ -306,8 +348,39 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
websocket.JSON.Send(conn, map[string]string{"error": "URL doesn't link to GitHub Gists"}) websocket.JSON.Send(conn, map[string]string{"error": "URL doesn't link to GitHub Gists"})
continue continue
} }
log.Info("Faucet funds requested", "gist", msg.URL) if msg.Tier >= uint(*tiersFlag) {
websocket.JSON.Send(conn, map[string]string{"error": "Invalid funding tier requested"})
continue
}
log.Info("Faucet funds requested", "gist", msg.URL, "tier", msg.Tier)
// If captcha verifications are enabled, make sure we're not dealing with a robot
if *captchaToken != "" {
form := url.Values{}
form.Add("secret", *captchaSecret)
form.Add("response", msg.Captcha)
res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form)
if err != nil {
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
continue
}
var result struct {
Success bool `json:"success"`
Errors json.RawMessage `json:"error-codes"`
}
err = json.NewDecoder(res.Body).Decode(&result)
res.Body.Close()
if err != nil {
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
continue
}
if !result.Success {
log.Warn("Captcha verification failed", "err", string(result.Errors))
websocket.JSON.Send(conn, map[string]string{"error": "Beep-bop, you're a robot!"})
continue
}
}
// Retrieve the gist from the GitHub Gist APIs // Retrieve the gist from the GitHub Gist APIs
parts := strings.Split(msg.URL, "/") parts := strings.Split(msg.URL, "/")
req, _ := http.NewRequest("GET", "https://api.github.com/gists/"+parts[len(parts)-1], nil) req, _ := http.NewRequest("GET", "https://api.github.com/gists/"+parts[len(parts)-1], nil)
@ -334,7 +407,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
continue continue
} }
if gist.Owner.Login == "" { if gist.Owner.Login == "" {
websocket.JSON.Send(conn, map[string]string{"error": "Nice try ;)"}) websocket.JSON.Send(conn, map[string]string{"error": "Anonymous Gists not allowed"})
continue continue
} }
// Iterate over all the files and look for Ethereum addresses // Iterate over all the files and look for Ethereum addresses
@ -348,15 +421,30 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
websocket.JSON.Send(conn, map[string]string{"error": "No Ethereum address found to fund"}) websocket.JSON.Send(conn, map[string]string{"error": "No Ethereum address found to fund"})
continue continue
} }
// Validate the user's existence since the API is unhelpful here
if res, err = http.Head("https://github.com/" + gist.Owner.Login); err != nil {
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
continue
}
res.Body.Close()
if res.StatusCode != 200 {
websocket.JSON.Send(conn, map[string]string{"error": "Invalid user... boom!"})
continue
}
// Ensure the user didn't request funds too recently // Ensure the user didn't request funds too recently
f.lock.Lock() f.lock.Lock()
var ( var (
fund bool fund bool
elapsed time.Duration timeout time.Time
) )
if elapsed = time.Since(f.history[gist.Owner.Login]); elapsed > time.Duration(*minutesFlag)*time.Minute { if timeout = f.timeouts[gist.Owner.Login]; time.Now().After(timeout) {
// User wasn't funded recently, create the funding transaction // User wasn't funded recently, create the funding transaction
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether), big.NewInt(21000), f.price, nil) amount := new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether)
amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil))
amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil))
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, big.NewInt(21000), f.price, nil)
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId) signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId)
if err != nil { if err != nil {
websocket.JSON.Send(conn, map[string]string{"error": err.Error()}) websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
@ -375,14 +463,14 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
Time: time.Now(), Time: time.Now(),
Tx: signed, Tx: signed,
}) })
f.history[gist.Owner.Login] = time.Now() f.timeouts[gist.Owner.Login] = time.Now().Add(time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute)
fund = true fund = true
} }
f.lock.Unlock() f.lock.Unlock()
// Send an error if too frequent funding, othewise a success // Send an error if too frequent funding, othewise a success
if !fund { if !fund {
websocket.JSON.Send(conn, map[string]string{"error": fmt.Sprintf("User already funded %s ago", common.PrettyDuration(elapsed))}) websocket.JSON.Send(conn, map[string]string{"error": fmt.Sprintf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))})
continue continue
} }
websocket.JSON.Send(conn, map[string]string{"success": fmt.Sprintf("Funding request accepted for %s into %s", gist.Owner.Login, address.Hex())}) websocket.JSON.Send(conn, map[string]string{"success": fmt.Sprintf("Funding request accepted for %s into %s", gist.Owner.Login, address.Hex())})

View File

@ -51,9 +51,13 @@
<div class="input-group"> <div class="input-group">
<input id="gist" type="text" class="form-control" placeholder="GitHub Gist URL containing your Ethereum address..."> <input id="gist" type="text" class="form-control" placeholder="GitHub Gist URL containing your Ethereum address...">
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-default" type="button" onclick="submit()">Give me Ether!</button> <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Give me Ether <i class="fa fa-caret-down" aria-hidden="true"></i></button>
<ul class="dropdown-menu dropdown-menu-right">{{range $idx, $amount := .Amounts}}
<li><a style="text-align: center;" onclick="tier={{$idx}}; {{if $.Recaptcha}}grecaptcha.execute(){{else}}submit({{$idx}}){{end}}">{{$amount}} / {{index $.Periods $idx}}</a></li>{{end}}
</ul>
</span> </span>
</div> </div>{{if .Recaptcha}}
<div class="g-recaptcha" data-sitekey="{{.Recaptcha}}" data-callback="submit" data-size="invisible"></div>{{end}}
</div> </div>
</div> </div>
<div class="row" style="margin-top: 32px;"> <div class="row" style="margin-top: 32px;">
@ -76,8 +80,9 @@
<div class="row" style="margin-top: 32px;"> <div class="row" style="margin-top: 32px;">
<div class="col-lg-12"> <div class="col-lg-12">
<h3>How does this work?</h3> <h3>How does this work?</h3>
<p>This Ether faucet is running on the {{.Network}} network. To prevent malicious actors from exhausting all available funds or accumulating enough Ether to mount long running spam attacks, requests are tied to GitHub accounts. Anyone having a GitHub account may request funds within the permitted limit of <strong>{{.Amount}} Ether(s) / {{.Period}}</strong>.</p> <p>This Ether faucet is running on the {{.Network}} network. To prevent malicious actors from exhausting all available funds or accumulating enough Ether to mount long running spam attacks, requests are tied to GitHub accounts. Anyone having a GitHub account may request funds within the permitted limits.</p>
<p>To request funds, simply create a <a href="https://gist.github.com/" target="_about:blank">GitHub Gist</a> with your Ethereum address pasted into the contents (the file name doesn't matter), copy paste the gists URL into the above input box and fire away! You can track the current pending requests below the input field to see how much you have to wait until your turn comes.</p> <p>To request funds, simply create a <a href="https://gist.github.com/" target="_about:blank">GitHub Gist</a> with your Ethereum address pasted into the contents (the file name doesn't matter), copy paste the gists URL into the above input box and fire away! You can track the current pending requests below the input field to see how much you have to wait until your turn comes.</p>
{{if .Recaptcha}}<em>The faucet is running invisible reCaptcha protection against bots.</em>{{end}}
</div> </div>
</div> </div>
</div> </div>
@ -87,10 +92,12 @@
// Global variables to hold the current status of the faucet // Global variables to hold the current status of the faucet
var attempt = 0; var attempt = 0;
var server; var server;
var tier = 0;
// Define the function that submits a gist url to the server // Define the function that submits a gist url to the server
var submit = function() { var submit = function({{if .Recaptcha}}captcha{{end}}) {
server.send(JSON.stringify({url: $("#gist")[0].value})); server.send(JSON.stringify({url: $("#gist")[0].value, tier: tier{{if .Recaptcha}}, captcha: captcha{{end}}}));{{if .Recaptcha}}
grecaptcha.reset();{{end}}
}; };
// Define a method to reconnect upon server loss // Define a method to reconnect upon server loss
var reconnect = function() { var reconnect = function() {
@ -134,10 +141,10 @@
} }
} }
server.onclose = function() { setTimeout(reconnect, 3000); }; server.onclose = function() { setTimeout(reconnect, 3000); };
server.onerror = function() { setTimeout(reconnect, 3000); };
} }
// Establish a websocket connection to the API server // Establish a websocket connection to the API server
reconnect(); reconnect();
</script> </script>{{if .Recaptcha}}
<script src="https://www.google.com/recaptcha/api.js" async defer></script>{{end}}
</body> </body>
</html> </html>

File diff suppressed because one or more lines are too long

View File

@ -32,40 +32,32 @@ import (
var ( var (
walletCommand = cli.Command{ walletCommand = cli.Command{
Name: "wallet", Name: "wallet",
Usage: "Manage Ethereum presale wallets", Usage: "Import Ethereum presale wallets",
ArgsUsage: "", Action: utils.MigrateFlags(importWallet),
Category: "ACCOUNT COMMANDS", Category: "ACCOUNT COMMANDS",
Description: ` Flags: []cli.Flag{
geth wallet import /path/to/my/presale.wallet utils.DataDirFlag,
utils.KeyStoreDirFlag,
will prompt for your password and imports your ether presale account. utils.PasswordFileFlag,
It can be used non-interactively with the --password option taking a utils.LightKDFFlag,
passwordfile as argument containing the wallet password in plaintext.
`,
Subcommands: []cli.Command{
{
Action: importWallet,
Name: "import",
Usage: "Import Ethereum presale wallet",
ArgsUsage: "<keyFile>",
Description: `
TODO: Please write this
`,
},
}, },
Description: `
geth wallet [options] /path/to/my/presale.wallet
will prompt for your password and imports your ether presale account.
It can be used non-interactively with the --password option taking a
passwordfile as argument containing the wallet password in plaintext.
`,
} }
accountCommand = cli.Command{ accountCommand = cli.Command{
Action: accountList,
Name: "account", Name: "account",
Usage: "Manage accounts", Usage: "Manage accounts",
ArgsUsage: "",
Category: "ACCOUNT COMMANDS", Category: "ACCOUNT COMMANDS",
Description: ` Description: `
Manage accounts lets you create new accounts, list all existing accounts,
import a private key into a new account.
' help' shows a list of subcommands or help for one subcommand. Manage accounts, list all existing accounts, import a private key into a new
account, create a new account or update an existing account.
It supports interactive mode, when you are prompted for password as well as It supports interactive mode, when you are prompted for password as well as
non-interactive mode where passwords are supplied via a given password file. non-interactive mode where passwords are supplied via a given password file.
@ -80,36 +72,34 @@ Note that exporting your key in unencrypted format is NOT supported.
Keys are stored under <DATADIR>/keystore. Keys are stored under <DATADIR>/keystore.
It is safe to transfer the entire directory or the individual keys therein It is safe to transfer the entire directory or the individual keys therein
between ethereum nodes by simply copying. between ethereum nodes by simply copying.
Make sure you backup your keys regularly.
In order to use your account to send transactions, you need to unlock them using Make sure you backup your keys regularly.`,
the '--unlock' option. The argument is a space separated list of addresses or
indexes. If used non-interactively with a passwordfile, the file should contain
the respective passwords one per line. If you unlock n accounts and the password
file contains less than n entries, then the last password is meant to apply to
all remaining accounts.
And finally. DO NOT FORGET YOUR PASSWORD.
`,
Subcommands: []cli.Command{ Subcommands: []cli.Command{
{ {
Action: accountList,
Name: "list", Name: "list",
Usage: "Print account addresses", Usage: "Print summary of existing accounts",
ArgsUsage: " ", Action: utils.MigrateFlags(accountList),
Flags: []cli.Flag{
utils.DataDirFlag,
utils.KeyStoreDirFlag,
},
Description: ` Description: `
TODO: Please write this Print a short summary of all accounts`,
`,
}, },
{ {
Action: accountCreate,
Name: "new", Name: "new",
Usage: "Create a new account", Usage: "Create a new account",
ArgsUsage: " ", Action: utils.MigrateFlags(accountCreate),
Flags: []cli.Flag{
utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.PasswordFileFlag,
utils.LightKDFFlag,
},
Description: ` Description: `
geth account new geth account new
Creates a new account. Prints the address. Creates a new account and prints the address.
The account is saved in encrypted format, you are prompted for a passphrase. The account is saved in encrypted format, you are prompted for a passphrase.
@ -117,17 +107,20 @@ You must remember this passphrase to unlock your account in the future.
For non-interactive use the passphrase can be specified with the --password flag: For non-interactive use the passphrase can be specified with the --password flag:
geth --password <passwordfile> account new
Note, this is meant to be used for testing only, it is a bad idea to save your Note, this is meant to be used for testing only, it is a bad idea to save your
password to file or expose in any other way. password to file or expose in any other way.
`, `,
}, },
{ {
Action: accountUpdate,
Name: "update", Name: "update",
Usage: "Update an existing account", Usage: "Update an existing account",
Action: utils.MigrateFlags(accountUpdate),
ArgsUsage: "<address>", ArgsUsage: "<address>",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.LightKDFFlag,
},
Description: ` Description: `
geth account update <address> geth account update <address>
@ -141,16 +134,22 @@ format to the newest format or change the password for an account.
For non-interactive use the passphrase can be specified with the --password flag: For non-interactive use the passphrase can be specified with the --password flag:
geth --password <passwordfile> account update <address> geth account update [options] <address>
Since only one password can be given, only format update can be performed, Since only one password can be given, only format update can be performed,
changing your password is only possible interactively. changing your password is only possible interactively.
`, `,
}, },
{ {
Action: accountImport,
Name: "import", Name: "import",
Usage: "Import a private key into a new account", Usage: "Import a private key into a new account",
Action: utils.MigrateFlags(accountImport),
Flags: []cli.Flag{
utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.PasswordFileFlag,
utils.LightKDFFlag,
},
ArgsUsage: "<keyFile>", ArgsUsage: "<keyFile>",
Description: ` Description: `
geth account import <keyfile> geth account import <keyfile>
@ -166,7 +165,7 @@ You must remember this passphrase to unlock your account in the future.
For non-interactive use the passphrase can be specified with the -password flag: For non-interactive use the passphrase can be specified with the -password flag:
geth --password <passwordfile> account import <keyfile> geth account import [options] <keyfile>
Note: Note:
As you can directly copy your encrypted accounts to another ethereum instance, As you can directly copy your encrypted accounts to another ethereum instance,
@ -298,11 +297,13 @@ func accountUpdate(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx) stack, _ := makeConfigNode(ctx)
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
account, oldPassword := unlockAccount(ctx, ks, ctx.Args().First(), 0, nil) for _, addr := range ctx.Args() {
account, oldPassword := unlockAccount(ctx, ks, addr, 0, nil)
newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
if err := ks.Update(account, oldPassword, newPassword); err != nil { if err := ks.Update(account, oldPassword, newPassword); err != nil {
utils.Fatalf("Could not update the account: %v", err) utils.Fatalf("Could not update the account: %v", err)
} }
}
return nil return nil
} }

View File

@ -43,13 +43,13 @@ func tmpDatadirWithKeystore(t *testing.T) string {
} }
func TestAccountListEmpty(t *testing.T) { func TestAccountListEmpty(t *testing.T) {
geth := runGeth(t, "account") geth := runGeth(t, "account", "list")
geth.expectExit() geth.expectExit()
} }
func TestAccountList(t *testing.T) { func TestAccountList(t *testing.T) {
datadir := tmpDatadirWithKeystore(t) datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t, "--datadir", datadir, "account") geth := runGeth(t, "account", "list", "--datadir", datadir)
defer geth.expectExit() defer geth.expectExit()
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
geth.expect(` geth.expect(`
@ -67,7 +67,7 @@ Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/k
} }
func TestAccountNew(t *testing.T) { func TestAccountNew(t *testing.T) {
geth := runGeth(t, "--lightkdf", "account", "new") geth := runGeth(t, "account", "new", "--lightkdf")
defer geth.expectExit() defer geth.expectExit()
geth.expect(` geth.expect(`
Your new account is locked with a password. Please give a password. Do not forget this password. Your new account is locked with a password. Please give a password. Do not forget this password.
@ -79,7 +79,7 @@ Repeat passphrase: {{.InputLine "foobar"}}
} }
func TestAccountNewBadRepeat(t *testing.T) { func TestAccountNewBadRepeat(t *testing.T) {
geth := runGeth(t, "--lightkdf", "account", "new") geth := runGeth(t, "account", "new", "--lightkdf")
defer geth.expectExit() defer geth.expectExit()
geth.expect(` geth.expect(`
Your new account is locked with a password. Please give a password. Do not forget this password. Your new account is locked with a password. Please give a password. Do not forget this password.
@ -92,9 +92,9 @@ Fatal: Passphrases do not match
func TestAccountUpdate(t *testing.T) { func TestAccountUpdate(t *testing.T) {
datadir := tmpDatadirWithKeystore(t) datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t, geth := runGeth(t, "account", "update",
"--datadir", datadir, "--lightkdf", "--datadir", datadir, "--lightkdf",
"account", "update", "f466859ead1932d743d622cb74fc058882e8648a") "f466859ead1932d743d622cb74fc058882e8648a")
defer geth.expectExit() defer geth.expectExit()
geth.expect(` geth.expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
@ -107,7 +107,7 @@ Repeat passphrase: {{.InputLine "foobar2"}}
} }
func TestWalletImport(t *testing.T) { func TestWalletImport(t *testing.T) {
geth := runGeth(t, "--lightkdf", "wallet", "import", "testdata/guswallet.json") geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json")
defer geth.expectExit() defer geth.expectExit()
geth.expect(` geth.expect(`
!! Unsupported terminal, password will be echoed. !! Unsupported terminal, password will be echoed.
@ -122,7 +122,7 @@ Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f}
} }
func TestWalletImportBadPassword(t *testing.T) { func TestWalletImportBadPassword(t *testing.T) {
geth := runGeth(t, "--lightkdf", "wallet", "import", "testdata/guswallet.json") geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json")
defer geth.expectExit() defer geth.expectExit()
geth.expect(` geth.expect(`
!! Unsupported terminal, password will be echoed. !! Unsupported terminal, password will be echoed.

View File

@ -29,11 +29,12 @@ import (
"github.com/ethereum/go-ethereum/cmd/internal/browser" "github.com/ethereum/go-ethereum/cmd/internal/browser"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/cmd/utils"
cli "gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
) )
var bugCommand = cli.Command{ var bugCommand = cli.Command{
Action: reportBug, Action: utils.MigrateFlags(reportBug),
Name: "bug", Name: "bug",
Usage: "opens a window to report a bug on the geth repo", Usage: "opens a window to report a bug on the geth repo",
ArgsUsage: " ", ArgsUsage: " ",

View File

@ -40,80 +40,98 @@ import (
var ( var (
initCommand = cli.Command{ initCommand = cli.Command{
Action: initGenesis, Action: utils.MigrateFlags(initGenesis),
Name: "init", Name: "init",
Usage: "Bootstrap and initialize a new genesis block", Usage: "Bootstrap and initialize a new genesis block",
ArgsUsage: "<genesisPath>", ArgsUsage: "<genesisPath>",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.LightModeFlag,
},
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
The init command initializes a new genesis block and definition for the network. The init command initializes a new genesis block and definition for the network.
This is a destructive action and changes the network in which you will be This is a destructive action and changes the network in which you will be
participating. participating.
`,
It expects the genesis file as argument.`,
} }
importCommand = cli.Command{ importCommand = cli.Command{
Action: importChain, Action: utils.MigrateFlags(importChain),
Name: "import", Name: "import",
Usage: "Import a blockchain file", Usage: "Import a blockchain file",
ArgsUsage: "<filename> (<filename 2> ... <filename N>) ", ArgsUsage: "<filename> (<filename 2> ... <filename N>) ",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.CacheFlag,
utils.LightModeFlag,
},
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
The import command imports blocks from an RLP-encoded form. The form can be one file The import command imports blocks from an RLP-encoded form. The form can be one file
with several RLP-encoded blocks, or several files can be used. with several RLP-encoded blocks, or several files can be used.
If only one file is used, import error will result in failure. If several files are used, If only one file is used, import error will result in failure. If several files are used,
processing will proceed even if an individual RLP-file import failure occurs. processing will proceed even if an individual RLP-file import failure occurs.`,
`,
} }
exportCommand = cli.Command{ exportCommand = cli.Command{
Action: exportChain, Action: utils.MigrateFlags(exportChain),
Name: "export", Name: "export",
Usage: "Export blockchain into file", Usage: "Export blockchain into file",
ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]", ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.CacheFlag,
utils.LightModeFlag,
},
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
Requires a first argument of the file to write to. Requires a first argument of the file to write to.
Optional second and third arguments control the first and Optional second and third arguments control the first and
last block to write. In this mode, the file will be appended last block to write. In this mode, the file will be appended
if already existing. if already existing.`,
`,
} }
removedbCommand = cli.Command{ removedbCommand = cli.Command{
Action: removeDB, Action: utils.MigrateFlags(removeDB),
Name: "removedb", Name: "removedb",
Usage: "Remove blockchain and state databases", Usage: "Remove blockchain and state databases",
ArgsUsage: " ", ArgsUsage: " ",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.LightModeFlag,
},
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
TODO: Please write this Remove blockchain and state databases`,
`,
} }
dumpCommand = cli.Command{ dumpCommand = cli.Command{
Action: dump, Action: utils.MigrateFlags(dump),
Name: "dump", Name: "dump",
Usage: "Dump a specific block from storage", Usage: "Dump a specific block from storage",
ArgsUsage: "[<blockHash> | <blockNum>]...", ArgsUsage: "[<blockHash> | <blockNum>]...",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.CacheFlag,
utils.LightModeFlag,
},
Category: "BLOCKCHAIN COMMANDS", Category: "BLOCKCHAIN COMMANDS",
Description: ` Description: `
The arguments are interpreted as block numbers or hashes. The arguments are interpreted as block numbers or hashes.
Use "ethereum dump 0" to dump the genesis block. Use "ethereum dump 0" to dump the genesis block.`,
`,
} }
) )
// initGenesis will initialise the given JSON format genesis file and writes it as // initGenesis will initialise the given JSON format genesis file and writes it as
// the zero'd block (i.e. genesis) or will fail hard if it can't succeed. // the zero'd block (i.e. genesis) or will fail hard if it can't succeed.
func initGenesis(ctx *cli.Context) error { func initGenesis(ctx *cli.Context) error {
// Make sure we have a valid genesis JSON
genesisPath := ctx.Args().First() genesisPath := ctx.Args().First()
if len(genesisPath) == 0 { if len(genesisPath) == 0 {
utils.Fatalf("must supply path to genesis JSON file") utils.Fatalf("Must supply path to genesis JSON file")
} }
stack := makeFullNode(ctx)
chaindb := utils.MakeChainDatabase(ctx, stack)
file, err := os.Open(genesisPath) file, err := os.Open(genesisPath)
if err != nil { if err != nil {
utils.Fatalf("failed to read genesis file: %v", err) utils.Fatalf("Failed to read genesis file: %v", err)
} }
defer file.Close() defer file.Close()
@ -121,12 +139,19 @@ func initGenesis(ctx *cli.Context) error {
if err := json.NewDecoder(file).Decode(genesis); err != nil { if err := json.NewDecoder(file).Decode(genesis); err != nil {
utils.Fatalf("invalid genesis file: %v", err) utils.Fatalf("invalid genesis file: %v", err)
} }
// Open an initialise both full and light databases
stack := makeFullNode(ctx)
for _, name := range []string{"chaindata", "lightchaindata"} {
chaindb, err := stack.OpenDatabase(name, 0, 0)
if err != nil {
utils.Fatalf("Failed to open database: %v", err)
}
_, hash, err := core.SetupGenesisBlock(chaindb, genesis) _, hash, err := core.SetupGenesisBlock(chaindb, genesis)
if err != nil { if err != nil {
utils.Fatalf("failed to write genesis block: %v", err) utils.Fatalf("Failed to write genesis block: %v", err)
}
log.Info("Successfully wrote genesis state", "database", name, "hash", hash)
} }
log.Info("Successfully wrote genesis state", "hash", hash)
return nil return nil
} }
@ -245,24 +270,29 @@ func exportChain(ctx *cli.Context) error {
func removeDB(ctx *cli.Context) error { func removeDB(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx) stack, _ := makeConfigNode(ctx)
dbdir := stack.ResolvePath(utils.ChainDbName(ctx))
if !common.FileExist(dbdir) {
fmt.Println(dbdir, "does not exist")
return nil
}
for _, name := range []string{"chaindata", "lightchaindata"} {
// Ensure the database exists in the first place
logger := log.New("database", name)
dbdir := stack.ResolvePath(name)
if !common.FileExist(dbdir) {
logger.Info("Database doesn't exist, skipping", "path", dbdir)
continue
}
// Confirm removal and execute
fmt.Println(dbdir) fmt.Println(dbdir)
confirm, err := console.Stdin.PromptConfirm("Remove this database?") confirm, err := console.Stdin.PromptConfirm("Remove this database?")
switch { switch {
case err != nil: case err != nil:
utils.Fatalf("%v", err) utils.Fatalf("%v", err)
case !confirm: case !confirm:
fmt.Println("Operation aborted") logger.Warn("Database deletion aborted")
default: default:
fmt.Println("Removing...")
start := time.Now() start := time.Now()
os.RemoveAll(dbdir) os.RemoveAll(dbdir)
fmt.Printf("Removed in %v\n", time.Since(start)) logger.Info("Database successfully deleted", "elapsed", common.PrettyDuration(time.Since(start)))
}
} }
return nil return nil
} }

View File

@ -38,10 +38,11 @@ import (
var ( var (
dumpConfigCommand = cli.Command{ dumpConfigCommand = cli.Command{
Action: dumpConfig, Action: utils.MigrateFlags(dumpConfig),
Name: "dumpconfig", Name: "dumpconfig",
Usage: "Show configuration values", Usage: "Show configuration values",
ArgsUsage: "", ArgsUsage: "",
Flags: append(nodeFlags, rpcFlags...),
Category: "MISCELLANEOUS COMMANDS", Category: "MISCELLANEOUS COMMANDS",
Description: `The dumpconfig command shows configuration values.`, Description: `The dumpconfig command shows configuration values.`,
} }

View File

@ -28,42 +28,47 @@ import (
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
) )
var (
consoleFlags = []cli.Flag{utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag}
)
var ( var (
consoleCommand = cli.Command{ consoleCommand = cli.Command{
Action: localConsole, Action: utils.MigrateFlags(localConsole),
Name: "console", Name: "console",
Usage: "Start an interactive JavaScript environment", Usage: "Start an interactive JavaScript environment",
ArgsUsage: "", // TODO: Write this! Flags: append(append(nodeFlags, rpcFlags...), consoleFlags...),
Category: "CONSOLE COMMANDS", Category: "CONSOLE COMMANDS",
Description: ` Description: `
The Geth console is an interactive shell for the JavaScript runtime environment The Geth console is an interactive shell for the JavaScript runtime environment
which exposes a node admin interface as well as the Ðapp JavaScript API. which exposes a node admin interface as well as the Ðapp JavaScript API.
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.`,
`,
} }
attachCommand = cli.Command{ attachCommand = cli.Command{
Action: remoteConsole, Action: utils.MigrateFlags(remoteConsole),
Name: "attach", Name: "attach",
Usage: "Start an interactive JavaScript environment (connect to node)", Usage: "Start an interactive JavaScript environment (connect to node)",
ArgsUsage: "", // TODO: Write this! ArgsUsage: "[endpoint]",
Flags: append(consoleFlags, utils.DataDirFlag),
Category: "CONSOLE COMMANDS", Category: "CONSOLE COMMANDS",
Description: ` Description: `
The Geth console is an interactive shell for the JavaScript runtime environment The Geth console is an interactive shell for the JavaScript runtime environment
which exposes a node admin interface as well as the Ðapp JavaScript API. which exposes a node admin interface as well as the Ðapp JavaScript API.
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.
This command allows to open a console on a running geth node. This command allows to open a console on a running geth node.`,
`,
} }
javascriptCommand = cli.Command{ javascriptCommand = cli.Command{
Action: ephemeralConsole, Action: utils.MigrateFlags(ephemeralConsole),
Name: "js", Name: "js",
Usage: "Execute the specified JavaScript files", Usage: "Execute the specified JavaScript files",
ArgsUsage: "", // TODO: Write this! ArgsUsage: "<jsfile> [jsfile...]",
Flags: append(nodeFlags, consoleFlags...),
Category: "CONSOLE COMMANDS", Category: "CONSOLE COMMANDS",
Description: ` Description: `
The JavaScript VM exposes a node admin interface as well as the Ðapp The JavaScript VM exposes a node admin interface as well as the Ðapp
JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console`,
`,
} }
) )
@ -81,11 +86,12 @@ func localConsole(ctx *cli.Context) error {
utils.Fatalf("Failed to attach to the inproc geth: %v", err) utils.Fatalf("Failed to attach to the inproc geth: %v", err)
} }
config := console.Config{ config := console.Config{
DataDir: node.DataDir(), DataDir: utils.MakeDataDir(ctx),
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name), DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
Client: client, Client: client,
Preload: utils.MakeConsolePreloads(ctx), Preload: utils.MakeConsolePreloads(ctx),
} }
console, err := console.New(config) console, err := console.New(config)
if err != nil { if err != nil {
utils.Fatalf("Failed to start the JavaScript console: %v", err) utils.Fatalf("Failed to start the JavaScript console: %v", err)
@ -118,17 +124,18 @@ func remoteConsole(ctx *cli.Context) error {
Client: client, Client: client,
Preload: utils.MakeConsolePreloads(ctx), Preload: utils.MakeConsolePreloads(ctx),
} }
console, err := console.New(config) console, err := console.New(config)
if err != nil { if err != nil {
utils.Fatalf("Failed to start the JavaScript console: %v", err) utils.Fatalf("Failed to start the JavaScript console: %v", err)
} }
defer console.Stop(false) defer console.Stop(false)
// If only a short execution was requested, evaluate and return
if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" { if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
console.Evaluate(script) console.Evaluate(script)
return nil return nil
} }
// Otherwise print the welcome screen and enter interactive mode // Otherwise print the welcome screen and enter interactive mode
console.Welcome() console.Welcome()
console.Interactive() console.Interactive()
@ -151,7 +158,7 @@ func dialRPC(endpoint string) (*rpc.Client, error) {
} }
// ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript // ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
// console to it, and each of the files specified as arguments and tears the // console to it, executes each of the files specified as arguments and tears
// everything down. // everything down.
func ephemeralConsole(ctx *cli.Context) error { func ephemeralConsole(ctx *cli.Context) error {
// Create and start the node based on the CLI flags // Create and start the node based on the CLI flags
@ -165,11 +172,12 @@ func ephemeralConsole(ctx *cli.Context) error {
utils.Fatalf("Failed to attach to the inproc geth: %v", err) utils.Fatalf("Failed to attach to the inproc geth: %v", err)
} }
config := console.Config{ config := console.Config{
DataDir: node.DataDir(), DataDir: utils.MakeDataDir(ctx),
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name), DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
Client: client, Client: client,
Preload: utils.MakeConsolePreloads(ctx), Preload: utils.MakeConsolePreloads(ctx),
} }
console, err := console.New(config) console, err := console.New(config)
if err != nil { if err != nil {
utils.Fatalf("Failed to start the JavaScript console: %v", err) utils.Fatalf("Failed to start the JavaScript console: %v", err)

View File

@ -49,6 +49,73 @@ var (
relOracle = common.HexToAddress("0xfa7b9770ca4cb04296cac84f37736d4041251cdf") relOracle = common.HexToAddress("0xfa7b9770ca4cb04296cac84f37736d4041251cdf")
// The app that holds all commands and flags. // The app that holds all commands and flags.
app = utils.NewApp(gitCommit, "the go-ethereum command line interface") app = utils.NewApp(gitCommit, "the go-ethereum command line interface")
// flags that configure the node
nodeFlags = []cli.Flag{
utils.IdentityFlag,
utils.UnlockedAccountFlag,
utils.PasswordFileFlag,
utils.BootnodesFlag,
utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.NoUSBFlag,
utils.EthashCacheDirFlag,
utils.EthashCachesInMemoryFlag,
utils.EthashCachesOnDiskFlag,
utils.EthashDatasetDirFlag,
utils.EthashDatasetsInMemoryFlag,
utils.EthashDatasetsOnDiskFlag,
utils.FastSyncFlag,
utils.LightModeFlag,
utils.SyncModeFlag,
utils.LightServFlag,
utils.LightPeersFlag,
utils.LightKDFFlag,
utils.CacheFlag,
utils.TrieCacheGenFlag,
utils.ListenPortFlag,
utils.MaxPeersFlag,
utils.MaxPendingPeersFlag,
utils.EtherbaseFlag,
utils.GasPriceFlag,
utils.MinerThreadsFlag,
utils.MiningEnabledFlag,
utils.TargetGasLimitFlag,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
utils.NetrestrictFlag,
utils.NodeKeyFileFlag,
utils.NodeKeyHexFlag,
utils.WhisperEnabledFlag,
utils.DevModeFlag,
utils.TestnetFlag,
utils.RinkebyFlag,
utils.VMEnableDebugFlag,
utils.NetworkIdFlag,
utils.RPCCORSDomainFlag,
utils.EthStatsURLFlag,
utils.MetricsEnabledFlag,
utils.FakePoWFlag,
utils.NoCompactionFlag,
utils.GpoBlocksFlag,
utils.GpoPercentileFlag,
utils.ExtraDataFlag,
configFileFlag,
}
rpcFlags = []cli.Flag{
utils.RPCEnabledFlag,
utils.RPCListenAddrFlag,
utils.RPCPortFlag,
utils.RPCApiFlag,
utils.WSEnabledFlag,
utils.WSListenAddrFlag,
utils.WSPortFlag,
utils.WSApiFlag,
utils.WSAllowedOriginsFlag,
utils.IPCDisabledFlag,
utils.IPCPathFlag,
}
) )
func init() { func init() {
@ -81,70 +148,9 @@ func init() {
dumpConfigCommand, dumpConfigCommand,
} }
app.Flags = []cli.Flag{ app.Flags = append(app.Flags, nodeFlags...)
utils.IdentityFlag, app.Flags = append(app.Flags, rpcFlags...)
utils.UnlockedAccountFlag, app.Flags = append(app.Flags, consoleFlags...)
utils.PasswordFileFlag,
utils.BootnodesFlag,
utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.EthashCacheDirFlag,
utils.EthashCachesInMemoryFlag,
utils.EthashCachesOnDiskFlag,
utils.EthashDatasetDirFlag,
utils.EthashDatasetsInMemoryFlag,
utils.EthashDatasetsOnDiskFlag,
utils.FastSyncFlag,
utils.LightModeFlag,
utils.SyncModeFlag,
utils.LightServFlag,
utils.LightPeersFlag,
utils.LightKDFFlag,
utils.CacheFlag,
utils.TrieCacheGenFlag,
utils.JSpathFlag,
utils.ListenPortFlag,
utils.MaxPeersFlag,
utils.MaxPendingPeersFlag,
utils.EtherbaseFlag,
utils.GasPriceFlag,
utils.MinerThreadsFlag,
utils.MiningEnabledFlag,
utils.TargetGasLimitFlag,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
utils.NetrestrictFlag,
utils.NodeKeyFileFlag,
utils.NodeKeyHexFlag,
utils.RPCEnabledFlag,
utils.RPCListenAddrFlag,
utils.RPCPortFlag,
utils.RPCApiFlag,
utils.WSEnabledFlag,
utils.WSListenAddrFlag,
utils.WSPortFlag,
utils.WSApiFlag,
utils.WSAllowedOriginsFlag,
utils.IPCDisabledFlag,
utils.IPCPathFlag,
utils.ExecFlag,
utils.PreloadJSFlag,
utils.WhisperEnabledFlag,
utils.DevModeFlag,
utils.TestNetFlag,
utils.VMEnableDebugFlag,
utils.NetworkIdFlag,
utils.RPCCORSDomainFlag,
utils.EthStatsURLFlag,
utils.MetricsEnabledFlag,
utils.FakePoWFlag,
utils.NoCompactionFlag,
utils.GpoBlocksFlag,
utils.GpoPercentileFlag,
utils.ExtraDataFlag,
configFileFlag,
}
app.Flags = append(app.Flags, debug.Flags...) app.Flags = append(app.Flags, debug.Flags...)
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {

View File

@ -34,7 +34,7 @@ import (
var ( var (
makedagCommand = cli.Command{ makedagCommand = cli.Command{
Action: makedag, Action: utils.MigrateFlags(makedag),
Name: "makedag", Name: "makedag",
Usage: "Generate ethash DAG (for testing)", Usage: "Generate ethash DAG (for testing)",
ArgsUsage: "<blockNum> <outputDir>", ArgsUsage: "<blockNum> <outputDir>",
@ -47,7 +47,7 @@ Regular users do not need to execute it.
`, `,
} }
versionCommand = cli.Command{ versionCommand = cli.Command{
Action: version, Action: utils.MigrateFlags(version),
Name: "version", Name: "version",
Usage: "Print version numbers", Usage: "Print version numbers",
ArgsUsage: " ", ArgsUsage: " ",
@ -57,7 +57,7 @@ The output of this command is supposed to be machine-readable.
`, `,
} }
licenseCommand = cli.Command{ licenseCommand = cli.Command{
Action: license, Action: utils.MigrateFlags(license),
Name: "license", Name: "license",
Usage: "Display license information", Usage: "Display license information",
ArgsUsage: " ", ArgsUsage: " ",
@ -103,7 +103,7 @@ func version(ctx *cli.Context) error {
} }
fmt.Println("Architecture:", runtime.GOARCH) fmt.Println("Architecture:", runtime.GOARCH)
fmt.Println("Protocol Versions:", eth.ProtocolVersions) fmt.Println("Protocol Versions:", eth.ProtocolVersions)
fmt.Println("Network Id:", ctx.GlobalInt(utils.NetworkIdFlag.Name)) fmt.Println("Network Id:", eth.DefaultConfig.NetworkId)
fmt.Println("Go Version:", runtime.Version()) fmt.Println("Go Version:", runtime.Version())
fmt.Println("Operating System:", runtime.GOOS) fmt.Println("Operating System:", runtime.GOOS)
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))

View File

@ -49,7 +49,7 @@ var (
Usage: "Refresh interval in seconds", Usage: "Refresh interval in seconds",
} }
monitorCommand = cli.Command{ monitorCommand = cli.Command{
Action: monitor, Action: utils.MigrateFlags(monitor), // keep track of migration progress
Name: "monitor", Name: "monitor",
Usage: "Monitor and visualize node metrics", Usage: "Monitor and visualize node metrics",
ArgsUsage: " ", ArgsUsage: " ",

View File

@ -67,8 +67,10 @@ var AppHelpFlagGroups = []flagGroup{
configFileFlag, configFileFlag,
utils.DataDirFlag, utils.DataDirFlag,
utils.KeyStoreDirFlag, utils.KeyStoreDirFlag,
utils.NoUSBFlag,
utils.NetworkIdFlag, utils.NetworkIdFlag,
utils.TestNetFlag, utils.TestnetFlag,
utils.RinkebyFlag,
utils.DevModeFlag, utils.DevModeFlag,
utils.SyncModeFlag, utils.SyncModeFlag,
utils.EthStatsURLFlag, utils.EthStatsURLFlag,

View File

@ -51,9 +51,10 @@ ADD account.pass /account.pass
EXPOSE 8080 EXPOSE 8080
CMD [ \ CMD [ \
"/faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", \ "/faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \
"--ethport", "{{.EthPort}}", "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", \ "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \
"--github.user", "{{.GitHubUser}}", "--github.token", "{{.GitHubToken}}", "--account.json", "/account.json", "--account.pass", "/account.pass" \ "--github.user", "{{.GitHubUser}}", "--github.token", "{{.GitHubToken}}", "--account.json", "/account.json", "--account.pass", "/account.pass" \
{{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}} \
]` ]`
// faucetComposefile is the docker-compose.yml file required to deploy and maintain // faucetComposefile is the docker-compose.yml file required to deploy and maintain
@ -74,8 +75,11 @@ services:
- ETH_NAME={{.EthName}} - ETH_NAME={{.EthName}}
- FAUCET_AMOUNT={{.FaucetAmount}} - FAUCET_AMOUNT={{.FaucetAmount}}
- FAUCET_MINUTES={{.FaucetMinutes}} - FAUCET_MINUTES={{.FaucetMinutes}}
- FAUCET_TIERS={{.FaucetTiers}}
- GITHUB_USER={{.GitHubUser}} - GITHUB_USER={{.GitHubUser}}
- GITHUB_TOKEN={{.GitHubToken}}{{if .VHost}} - GITHUB_TOKEN={{.GitHubToken}}
- CAPTCHA_TOKEN={{.CaptchaToken}}
- CAPTCHA_SECRET={{.CaptchaSecret}}{{if .VHost}}
- VIRTUAL_HOST={{.VHost}} - VIRTUAL_HOST={{.VHost}}
- VIRTUAL_PORT=8080{{end}} - VIRTUAL_PORT=8080{{end}}
restart: always restart: always
@ -97,9 +101,12 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
"EthPort": config.node.portFull, "EthPort": config.node.portFull,
"GitHubUser": config.githubUser, "GitHubUser": config.githubUser,
"GitHubToken": config.githubToken, "GitHubToken": config.githubToken,
"CaptchaToken": config.captchaToken,
"CaptchaSecret": config.captchaSecret,
"FaucetName": strings.Title(network), "FaucetName": strings.Title(network),
"FaucetAmount": config.amount, "FaucetAmount": config.amount,
"FaucetMinutes": config.minutes, "FaucetMinutes": config.minutes,
"FaucetTiers": config.tiers,
}) })
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
@ -113,8 +120,11 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
"EthName": config.node.ethstats[:strings.Index(config.node.ethstats, ":")], "EthName": config.node.ethstats[:strings.Index(config.node.ethstats, ":")],
"GitHubUser": config.githubUser, "GitHubUser": config.githubUser,
"GitHubToken": config.githubToken, "GitHubToken": config.githubToken,
"CaptchaToken": config.captchaToken,
"CaptchaSecret": config.captchaSecret,
"FaucetAmount": config.amount, "FaucetAmount": config.amount,
"FaucetMinutes": config.minutes, "FaucetMinutes": config.minutes,
"FaucetTiers": config.tiers,
}) })
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
@ -140,13 +150,16 @@ type faucetInfos struct {
port int port int
amount int amount int
minutes int minutes int
tiers int
githubUser string githubUser string
githubToken string githubToken string
captchaToken string
captchaSecret string
} }
// String implements the stringer interface. // String implements the stringer interface.
func (info *faucetInfos) String() string { func (info *faucetInfos) String() string {
return fmt.Sprintf("host=%s, api=%d, eth=%d, amount=%d, minutes=%d, github=%s, ethstats=%s", info.host, info.port, info.node.portFull, info.amount, info.minutes, info.githubUser, info.node.ethstats) return fmt.Sprintf("host=%s, api=%d, eth=%d, amount=%d, minutes=%d, tiers=%d, github=%s, captcha=%v, ethstats=%s", info.host, info.port, info.node.portFull, info.amount, info.minutes, info.tiers, info.githubUser, info.captchaToken != "", info.node.ethstats)
} }
// checkFaucet does a health-check against an faucet server to verify whether // checkFaucet does a health-check against an faucet server to verify whether
@ -177,6 +190,7 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
} }
amount, _ := strconv.Atoi(infos.envvars["FAUCET_AMOUNT"]) amount, _ := strconv.Atoi(infos.envvars["FAUCET_AMOUNT"])
minutes, _ := strconv.Atoi(infos.envvars["FAUCET_MINUTES"]) minutes, _ := strconv.Atoi(infos.envvars["FAUCET_MINUTES"])
tiers, _ := strconv.Atoi(infos.envvars["FAUCET_TIERS"])
// Retrieve the funding account informations // Retrieve the funding account informations
var out []byte var out []byte
@ -204,7 +218,10 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
port: port, port: port,
amount: amount, amount: amount,
minutes: minutes, minutes: minutes,
tiers: tiers,
githubUser: infos.envvars["GITHUB_USER"], githubUser: infos.envvars["GITHUB_USER"],
githubToken: infos.envvars["GITHUB_TOKEN"], githubToken: infos.envvars["GITHUB_TOKEN"],
captchaToken: infos.envvars["CAPTCHA_TOKEN"],
captchaSecret: infos.envvars["CAPTCHA_SECRET"],
}, nil }, nil
} }

View File

@ -17,6 +17,8 @@
package main package main
import ( import (
"bufio"
"bytes"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -37,18 +39,26 @@ import (
type sshClient struct { type sshClient struct {
server string // Server name or IP without port number server string // Server name or IP without port number
address string // IP address of the remote server address string // IP address of the remote server
pubkey []byte // RSA public key to authenticate the server
client *ssh.Client client *ssh.Client
logger log.Logger logger log.Logger
} }
// dial establishes an SSH connection to a remote node using the current user and // dial establishes an SSH connection to a remote node using the current user and
// the user's configured private RSA key. // the user's configured private RSA key. If that fails, password authentication
func dial(server string) (*sshClient, error) { // is fallen back to. The caller may override the login user via user@server:port.
func dial(server string, pubkey []byte) (*sshClient, error) {
// Figure out a label for the server and a logger // Figure out a label for the server and a logger
label := server label := server
if strings.Contains(label, ":") { if strings.Contains(label, ":") {
label = label[:strings.Index(label, ":")] label = label[:strings.Index(label, ":")]
} }
login := ""
if strings.Contains(server, "@") {
login = label[:strings.Index(label, "@")]
label = label[strings.Index(label, "@")+1:]
server = server[strings.Index(server, "@")+1:]
}
logger := log.New("server", label) logger := log.New("server", label)
logger.Debug("Attempting to establish SSH connection") logger.Debug("Attempting to establish SSH connection")
@ -56,6 +66,9 @@ func dial(server string) (*sshClient, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if login == "" {
login = user.Username
}
// Configure the supported authentication methods (private key and password) // Configure the supported authentication methods (private key and password)
var auths []ssh.AuthMethod var auths []ssh.AuthMethod
@ -71,7 +84,7 @@ func dial(server string) (*sshClient, error) {
} }
} }
auths = append(auths, ssh.PasswordCallback(func() (string, error) { auths = append(auths, ssh.PasswordCallback(func() (string, error) {
fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", user.Username, server) fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", login, server)
blob, err := terminal.ReadPassword(int(syscall.Stdin)) blob, err := terminal.ReadPassword(int(syscall.Stdin))
fmt.Println() fmt.Println()
@ -86,11 +99,36 @@ func dial(server string) (*sshClient, error) {
return nil, errors.New("no IPs associated with domain") return nil, errors.New("no IPs associated with domain")
} }
// Try to dial in to the remote server // Try to dial in to the remote server
logger.Trace("Dialing remote SSH server", "user", user.Username, "key", path) logger.Trace("Dialing remote SSH server", "user", login)
if !strings.Contains(server, ":") { if !strings.Contains(server, ":") {
server += ":22" server += ":22"
} }
client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: user.Username, Auth: auths}) keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// If no public key is known for SSH, ask the user to confirm
if pubkey == nil {
fmt.Printf("The authenticity of host '%s (%s)' can't be established.\n", hostname, remote)
fmt.Printf("SSH key fingerprint is %s [MD5]\n", ssh.FingerprintLegacyMD5(key))
fmt.Printf("Are you sure you want to continue connecting (yes/no)? ")
text, err := bufio.NewReader(os.Stdin).ReadString('\n')
switch {
case err != nil:
return err
case strings.TrimSpace(text) == "yes":
pubkey = key.Marshal()
return nil
default:
return fmt.Errorf("unknown auth choice: %v", text)
}
}
// If a public key exists for this SSH server, check that it matches
if bytes.Compare(pubkey, key.Marshal()) == 0 {
return nil
}
// We have a mismatch, forbid connecting
return errors.New("ssh key mismatch, readd the machine to update")
}
client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: login, Auth: auths, HostKeyCallback: keycheck})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -98,6 +136,7 @@ func dial(server string) (*sshClient, error) {
c := &sshClient{ c := &sshClient{
server: label, server: label,
address: addr[0], address: addr[0],
pubkey: pubkey,
client: client, client: client,
logger: logger, logger: logger,
} }

View File

@ -44,14 +44,24 @@ type config struct {
bootLight []string // Bootnodes to always connect to by light nodes bootLight []string // Bootnodes to always connect to by light nodes
ethstats string // Ethstats settings to cache for node deploys ethstats string // Ethstats settings to cache for node deploys
Servers []string `json:"servers,omitempty"` Servers map[string][]byte `json:"servers,omitempty"`
}
// servers retrieves an alphabetically sorted list of servers.
func (c config) servers() []string {
servers := make([]string, 0, len(c.Servers))
for server := range c.Servers {
servers = append(servers, server)
}
sort.Strings(servers)
return servers
} }
// flush dumps the contents of config to disk. // flush dumps the contents of config to disk.
func (c config) flush() { func (c config) flush() {
os.MkdirAll(filepath.Dir(c.path), 0755) os.MkdirAll(filepath.Dir(c.path), 0755)
sort.Strings(c.Servers)
out, _ := json.MarshalIndent(c, "", " ") out, _ := json.MarshalIndent(c, "", " ")
if err := ioutil.WriteFile(c.path, out, 0644); err != nil { if err := ioutil.WriteFile(c.path, out, 0644); err != nil {
log.Warn("Failed to save puppeth configs", "file", c.path, "err", err) log.Warn("Failed to save puppeth configs", "file", c.path, "err", err)

View File

@ -44,6 +44,7 @@ func (w *wizard) deployFaucet() {
host: client.server, host: client.server,
amount: 1, amount: 1,
minutes: 1440, minutes: 1440,
tiers: 3,
} }
} }
infos.node.genesis, _ = json.MarshalIndent(w.conf.genesis, "", " ") infos.node.genesis, _ = json.MarshalIndent(w.conf.genesis, "", " ")
@ -68,10 +69,17 @@ func (w *wizard) deployFaucet() {
fmt.Printf("How many minutes to enforce between requests? (default = %d)\n", infos.minutes) fmt.Printf("How many minutes to enforce between requests? (default = %d)\n", infos.minutes)
infos.minutes = w.readDefaultInt(infos.minutes) infos.minutes = w.readDefaultInt(infos.minutes)
fmt.Println()
fmt.Printf("How many funding tiers to feature (x2.5 amounts, x3 timeout)? (default = %d)\n", infos.tiers)
infos.tiers = w.readDefaultInt(infos.tiers)
if infos.tiers == 0 {
log.Error("At least one funding tier must be set")
return
}
// Accessing GitHub gists requires API authorization, retrieve it // Accessing GitHub gists requires API authorization, retrieve it
if infos.githubUser != "" { if infos.githubUser != "" {
fmt.Println() fmt.Println()
fmt.Printf("Reused previous (%s) GitHub API authorization (y/n)? (default = yes)\n", infos.githubUser) fmt.Printf("Reuse previous (%s) GitHub API authorization (y/n)? (default = yes)\n", infos.githubUser)
if w.readDefaultString("y") != "y" { if w.readDefaultString("y") != "y" {
infos.githubUser, infos.githubToken = "", "" infos.githubUser, infos.githubToken = "", ""
} }
@ -109,6 +117,29 @@ func (w *wizard) deployFaucet() {
return return
} }
} }
// Accessing the reCaptcha service requires API authorizations, request it
if infos.captchaToken != "" {
fmt.Println()
fmt.Println("Reuse previous reCaptcha API authorization (y/n)? (default = yes)")
if w.readDefaultString("y") != "y" {
infos.captchaToken, infos.captchaSecret = "", ""
}
}
if infos.captchaToken == "" {
// No previous authorization (or old one discarded)
fmt.Println()
fmt.Println("Enable reCaptcha protection against robots (y/n)? (default = no)")
if w.readDefaultString("n") == "y" {
// Captcha protection explicitly requested, read the site and secret keys
fmt.Println()
fmt.Printf("What is the reCaptcha site key to authenticate human users?\n")
infos.captchaToken = w.readString()
fmt.Println()
fmt.Printf("What is the reCaptcha secret key to verify authentications? (won't be echoed)\n")
infos.captchaSecret = w.readPassword()
}
}
// Figure out where the user wants to store the persistent data // Figure out where the user wants to store the persistent data
fmt.Println() fmt.Println()
if infos.node.datadir == "" { if infos.node.datadir == "" {

View File

@ -120,7 +120,7 @@ func (w *wizard) makeGenesis() {
// Query the user for some custom extras // Query the user for some custom extras
fmt.Println() fmt.Println()
fmt.Println("Specify your chain/network ID if you want an explicit one (default = random)") fmt.Println("Specify your chain/network ID if you want an explicit one (default = random)")
genesis.Config.ChainId = big.NewInt(int64(w.readDefaultInt(rand.Intn(65536)))) genesis.Config.ChainId = new(big.Int).SetUint64(uint64(w.readDefaultInt(rand.Intn(65536))))
fmt.Println() fmt.Println()
fmt.Println("Anything fun to embed into the genesis block? (max 32 bytes)") fmt.Println("Anything fun to embed into the genesis block? (max 32 bytes)")

View File

@ -32,6 +32,9 @@ import (
func makeWizard(network string) *wizard { func makeWizard(network string) *wizard {
return &wizard{ return &wizard{
network: network, network: network,
conf: config{
Servers: make(map[string][]byte),
},
servers: make(map[string]*sshClient), servers: make(map[string]*sshClient),
services: make(map[string][]string), services: make(map[string][]string),
in: bufio.NewReader(os.Stdin), in: bufio.NewReader(os.Stdin),
@ -77,9 +80,9 @@ func (w *wizard) run() {
} else if err := json.Unmarshal(blob, &w.conf); err != nil { } else if err := json.Unmarshal(blob, &w.conf); err != nil {
log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err) log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err)
} else { } else {
for _, server := range w.conf.Servers { for server, pubkey := range w.conf.Servers {
log.Info("Dialing previously configured server", "server", server) log.Info("Dialing previously configured server", "server", server)
client, err := dial(server) client, err := dial(server, pubkey)
if err != nil { if err != nil {
log.Error("Previous server unreachable", "server", server, "err", err) log.Error("Previous server unreachable", "server", server, "err", err)
} }

View File

@ -41,14 +41,14 @@ func (w *wizard) networkStats(tips bool) {
stats.SetHeader([]string{"Server", "IP", "Status", "Service", "Details"}) stats.SetHeader([]string{"Server", "IP", "Status", "Service", "Details"})
stats.SetColWidth(128) stats.SetColWidth(128)
for _, server := range w.conf.Servers { for server, pubkey := range w.conf.Servers {
client := w.servers[server] client := w.servers[server]
logger := log.New("server", server) logger := log.New("server", server)
logger.Info("Starting remote server health-check") logger.Info("Starting remote server health-check")
// If the server is not connected, try to connect again // If the server is not connected, try to connect again
if client == nil { if client == nil {
conn, err := dial(server) conn, err := dial(server, pubkey)
if err != nil { if err != nil {
logger.Error("Failed to establish remote connection", "err", err) logger.Error("Failed to establish remote connection", "err", err)
stats.Append([]string{server, "", err.Error(), "", ""}) stats.Append([]string{server, "", err.Error(), "", ""})

View File

@ -28,7 +28,9 @@ import (
func (w *wizard) manageServers() { func (w *wizard) manageServers() {
// List all the servers we can disconnect, along with an entry to connect a new one // List all the servers we can disconnect, along with an entry to connect a new one
fmt.Println() fmt.Println()
for i, server := range w.conf.Servers {
servers := w.conf.servers()
for i, server := range servers {
fmt.Printf(" %d. Disconnect %s\n", i+1, server) fmt.Printf(" %d. Disconnect %s\n", i+1, server)
} }
fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1) fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1)
@ -40,14 +42,14 @@ func (w *wizard) manageServers() {
} }
// If the user selected an existing server, drop it // If the user selected an existing server, drop it
if choice <= len(w.conf.Servers) { if choice <= len(w.conf.Servers) {
server := w.conf.Servers[choice-1] server := servers[choice-1]
client := w.servers[server] client := w.servers[server]
delete(w.servers, server) delete(w.servers, server)
if client != nil { if client != nil {
client.Close() client.Close()
} }
w.conf.Servers = append(w.conf.Servers[:choice-1], w.conf.Servers[choice:]...) delete(w.conf.Servers, server)
w.conf.flush() w.conf.flush()
log.Info("Disconnected existing server", "server", server) log.Info("Disconnected existing server", "server", server)
@ -73,14 +75,14 @@ func (w *wizard) makeServer() string {
// Read and fial the server to ensure docker is present // Read and fial the server to ensure docker is present
input := w.readString() input := w.readString()
client, err := dial(input) client, err := dial(input, nil)
if err != nil { if err != nil {
log.Error("Server not ready for puppeth", "err", err) log.Error("Server not ready for puppeth", "err", err)
return "" return ""
} }
// All checks passed, start tracking the server // All checks passed, start tracking the server
w.servers[input] = client w.servers[input] = client
w.conf.Servers = append(w.conf.Servers, input) w.conf.Servers[input] = client.pubkey
w.conf.flush() w.conf.flush()
return input return input
@ -93,7 +95,9 @@ func (w *wizard) selectServer() string {
// List the available server to the user and wait for a choice // List the available server to the user and wait for a choice
fmt.Println() fmt.Println()
fmt.Println("Which server do you want to interact with?") fmt.Println("Which server do you want to interact with?")
for i, server := range w.conf.Servers {
servers := w.conf.servers()
for i, server := range servers {
fmt.Printf(" %d. %s\n", i+1, server) fmt.Printf(" %d. %s\n", i+1, server)
} }
fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1) fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1)
@ -105,7 +109,7 @@ func (w *wizard) selectServer() string {
} }
// If the user requested connecting to a new server, go for it // If the user requested connecting to a new server, go for it
if choice <= len(w.conf.Servers) { if choice <= len(w.conf.Servers) {
return w.conf.Servers[choice-1] return servers[choice-1]
} }
return w.makeServer() return w.makeServer()
} }

View File

@ -52,7 +52,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv2" whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
) )
@ -115,6 +115,10 @@ var (
Name: "keystore", Name: "keystore",
Usage: "Directory for the keystore (default = inside the datadir)", Usage: "Directory for the keystore (default = inside the datadir)",
} }
NoUSBFlag = cli.BoolFlag{
Name: "nousb",
Usage: "Disables monitoring for and managine USB hardware wallets",
}
EthashCacheDirFlag = DirectoryFlag{ EthashCacheDirFlag = DirectoryFlag{
Name: "ethash.cachedir", Name: "ethash.cachedir",
Usage: "Directory to store the ethash verification caches (default = inside the datadir)", Usage: "Directory to store the ethash verification caches (default = inside the datadir)",
@ -144,15 +148,19 @@ var (
Usage: "Number of recent ethash mining DAGs to keep on disk (1+GB each)", Usage: "Number of recent ethash mining DAGs to keep on disk (1+GB each)",
Value: eth.DefaultConfig.EthashDatasetsOnDisk, Value: eth.DefaultConfig.EthashDatasetsOnDisk,
} }
NetworkIdFlag = cli.IntFlag{ NetworkIdFlag = cli.Uint64Flag{
Name: "networkid", Name: "networkid",
Usage: "Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten)", Usage: "Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten, 4=Rinkeby)",
Value: eth.DefaultConfig.NetworkId, Value: eth.DefaultConfig.NetworkId,
} }
TestNetFlag = cli.BoolFlag{ TestnetFlag = cli.BoolFlag{
Name: "testnet", Name: "testnet",
Usage: "Ropsten network: pre-configured proof-of-work test network", Usage: "Ropsten network: pre-configured proof-of-work test network",
} }
RinkebyFlag = cli.BoolFlag{
Name: "rinkeby",
Usage: "Rinkeby network: pre-configured proof-of-authority test network",
}
DevModeFlag = cli.BoolFlag{ DevModeFlag = cli.BoolFlag{
Name: "dev", Name: "dev",
Usage: "Developer mode: pre-configured private network with several debugging flags", Usage: "Developer mode: pre-configured private network with several debugging flags",
@ -327,7 +335,7 @@ var (
} }
ExecFlag = cli.StringFlag{ ExecFlag = cli.StringFlag{
Name: "exec", Name: "exec",
Usage: "Execute JavaScript statement (only in combination with console/attach)", Usage: "Execute JavaScript statement",
} }
PreloadJSFlag = cli.StringFlag{ PreloadJSFlag = cli.StringFlag{
Name: "preload", Name: "preload",
@ -411,10 +419,12 @@ var (
// the a subdirectory of the specified datadir will be used. // the a subdirectory of the specified datadir will be used.
func MakeDataDir(ctx *cli.Context) string { func MakeDataDir(ctx *cli.Context) string {
if path := ctx.GlobalString(DataDirFlag.Name); path != "" { if path := ctx.GlobalString(DataDirFlag.Name); path != "" {
// TODO: choose a different location outside of the regular datadir. if ctx.GlobalBool(TestnetFlag.Name) {
if ctx.GlobalBool(TestNetFlag.Name) {
return filepath.Join(path, "testnet") return filepath.Join(path, "testnet")
} }
if ctx.GlobalBool(RinkebyFlag.Name) {
return filepath.Join(path, "rinkeby")
}
return path return path
} }
Fatalf("Cannot determine default data directory, please set manually (--datadir)") Fatalf("Cannot determine default data directory, please set manually (--datadir)")
@ -458,10 +468,13 @@ func setNodeUserIdent(ctx *cli.Context, cfg *node.Config) {
// flags, reverting to pre-configured ones if none have been specified. // flags, reverting to pre-configured ones if none have been specified.
func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
urls := params.MainnetBootnodes urls := params.MainnetBootnodes
if ctx.GlobalIsSet(BootnodesFlag.Name) { switch {
case ctx.GlobalIsSet(BootnodesFlag.Name):
urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",") urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",")
} else if ctx.GlobalBool(TestNetFlag.Name) { case ctx.GlobalBool(TestnetFlag.Name):
urls = params.TestnetBootnodes urls = params.TestnetBootnodes
case ctx.GlobalBool(RinkebyFlag.Name):
urls = params.RinkebyBootnodes
} }
cfg.BootstrapNodes = make([]*discover.Node, 0, len(urls)) cfg.BootstrapNodes = make([]*discover.Node, 0, len(urls))
@ -479,9 +492,12 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
// flags, reverting to pre-configured ones if none have been specified. // flags, reverting to pre-configured ones if none have been specified.
func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) {
urls := params.DiscoveryV5Bootnodes urls := params.DiscoveryV5Bootnodes
if ctx.GlobalIsSet(BootnodesFlag.Name) { switch {
case ctx.GlobalIsSet(BootnodesFlag.Name):
urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",") urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",")
} else if cfg.BootstrapNodesV5 == nil { case ctx.GlobalBool(RinkebyFlag.Name):
urls = params.RinkebyV5Bootnodes
case cfg.BootstrapNodesV5 != nil:
return // already set, don't apply defaults. return // already set, don't apply defaults.
} }
@ -643,7 +659,7 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
} }
} }
// MakePasswordList reads password lines from the file specified by --password. // MakePasswordList reads password lines from the file specified by the global --password flag.
func MakePasswordList(ctx *cli.Context) []string { func MakePasswordList(ctx *cli.Context) []string {
path := ctx.GlobalString(PasswordFileFlag.Name) path := ctx.GlobalString(PasswordFileFlag.Name)
if path == "" { if path == "" {
@ -719,8 +735,10 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
cfg.DataDir = ctx.GlobalString(DataDirFlag.Name) cfg.DataDir = ctx.GlobalString(DataDirFlag.Name)
case ctx.GlobalBool(DevModeFlag.Name): case ctx.GlobalBool(DevModeFlag.Name):
cfg.DataDir = filepath.Join(os.TempDir(), "ethereum_dev_mode") cfg.DataDir = filepath.Join(os.TempDir(), "ethereum_dev_mode")
case ctx.GlobalBool(TestNetFlag.Name): case ctx.GlobalBool(TestnetFlag.Name):
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "testnet") cfg.DataDir = filepath.Join(node.DefaultDataDir(), "testnet")
case ctx.GlobalBool(RinkebyFlag.Name):
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby")
} }
if ctx.GlobalIsSet(KeyStoreDirFlag.Name) { if ctx.GlobalIsSet(KeyStoreDirFlag.Name) {
@ -729,6 +747,9 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalIsSet(LightKDFFlag.Name) { if ctx.GlobalIsSet(LightKDFFlag.Name) {
cfg.UseLightweightKDF = ctx.GlobalBool(LightKDFFlag.Name) cfg.UseLightweightKDF = ctx.GlobalBool(LightKDFFlag.Name)
} }
if ctx.GlobalIsSet(NoUSBFlag.Name) {
cfg.NoUSB = ctx.GlobalBool(NoUSBFlag.Name)
}
} }
func setGPO(ctx *cli.Context, cfg *gasprice.Config) { func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
@ -776,7 +797,7 @@ func checkExclusive(ctx *cli.Context, flags ...cli.Flag) {
// SetEthConfig applies eth-related command line flags to the config. // SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
// Avoid conflicting network flags // Avoid conflicting network flags
checkExclusive(ctx, DevModeFlag, TestNetFlag) checkExclusive(ctx, DevModeFlag, TestnetFlag, RinkebyFlag)
checkExclusive(ctx, FastSyncFlag, LightModeFlag, SyncModeFlag) checkExclusive(ctx, FastSyncFlag, LightModeFlag, SyncModeFlag)
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
@ -799,7 +820,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
cfg.LightPeers = ctx.GlobalInt(LightPeersFlag.Name) cfg.LightPeers = ctx.GlobalInt(LightPeersFlag.Name)
} }
if ctx.GlobalIsSet(NetworkIdFlag.Name) { if ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = ctx.GlobalInt(NetworkIdFlag.Name) cfg.NetworkId = ctx.GlobalUint64(NetworkIdFlag.Name)
} }
// Ethereum needs to know maxPeers to calculate the light server peer ratio. // Ethereum needs to know maxPeers to calculate the light server peer ratio.
@ -828,13 +849,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
cfg.EnablePreimageRecording = ctx.GlobalBool(VMEnableDebugFlag.Name) cfg.EnablePreimageRecording = ctx.GlobalBool(VMEnableDebugFlag.Name)
} }
// Override any default configs for --dev and --testnet. // Override any default configs for hard coded networks.
switch { switch {
case ctx.GlobalBool(TestNetFlag.Name): case ctx.GlobalBool(TestnetFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) { if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 3 cfg.NetworkId = 3
} }
cfg.Genesis = core.DefaultTestnetGenesisBlock() cfg.Genesis = core.DefaultTestnetGenesisBlock()
case ctx.GlobalBool(RinkebyFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 4
}
cfg.Genesis = core.DefaultRinkebyGenesisBlock()
case ctx.GlobalBool(DevModeFlag.Name): case ctx.GlobalBool(DevModeFlag.Name):
cfg.Genesis = core.DevGenesisBlock() cfg.Genesis = core.DevGenesisBlock()
if !ctx.GlobalIsSet(GasPriceFlag.Name) { if !ctx.GlobalIsSet(GasPriceFlag.Name) {
@ -901,22 +927,16 @@ func SetupNetwork(ctx *cli.Context) {
params.TargetGasLimit = new(big.Int).SetUint64(ctx.GlobalUint64(TargetGasLimitFlag.Name)) params.TargetGasLimit = new(big.Int).SetUint64(ctx.GlobalUint64(TargetGasLimitFlag.Name))
} }
func ChainDbName(ctx *cli.Context) string {
if ctx.GlobalBool(LightModeFlag.Name) {
return "lightchaindata"
} else {
return "chaindata"
}
}
// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails.
func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
var ( var (
cache = ctx.GlobalInt(CacheFlag.Name) cache = ctx.GlobalInt(CacheFlag.Name)
handles = makeDatabaseHandles() handles = makeDatabaseHandles()
name = ChainDbName(ctx)
) )
name := "chaindata"
if ctx.GlobalBool(LightModeFlag.Name) {
name = "lightchaindata"
}
chainDb, err := stack.OpenDatabase(name, cache, handles) chainDb, err := stack.OpenDatabase(name, cache, handles)
if err != nil { if err != nil {
Fatalf("Could not open database: %v", err) Fatalf("Could not open database: %v", err)
@ -927,8 +947,10 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
func MakeGenesis(ctx *cli.Context) *core.Genesis { func MakeGenesis(ctx *cli.Context) *core.Genesis {
var genesis *core.Genesis var genesis *core.Genesis
switch { switch {
case ctx.GlobalBool(TestNetFlag.Name): case ctx.GlobalBool(TestnetFlag.Name):
genesis = core.DefaultTestnetGenesisBlock() genesis = core.DefaultTestnetGenesisBlock()
case ctx.GlobalBool(RinkebyFlag.Name):
genesis = core.DefaultRinkebyGenesisBlock()
case ctx.GlobalBool(DevModeFlag.Name): case ctx.GlobalBool(DevModeFlag.Name):
genesis = core.DevGenesisBlock() genesis = core.DevGenesisBlock()
} }
@ -972,3 +994,27 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
} }
return preloads return preloads
} }
// MigrateFlags sets the global flag from a local flag when it's set.
// This is a temporary function used for migrating old command/flags to the
// new format.
//
// e.g. geth account new --keystore /tmp/mykeystore --lightkdf
//
// is equivalent after calling this method with:
//
// geth --keystore /tmp/mykeystore --lightkdf account new
//
// This allows the use of the existing configuration functionality.
// When all flags are migrated this function can be removed and the existing
// configuration functionality must be changed that is uses local flags
func MigrateFlags(action func(ctx *cli.Context) error) func(*cli.Context) error {
return func(ctx *cli.Context) error {
for _, name := range ctx.FlagNames() {
if ctx.IsSet(name) {
ctx.GlobalSet(name, ctx.String(name))
}
}
return action(ctx)
}
}

View File

@ -65,7 +65,7 @@ var (
pub *ecdsa.PublicKey pub *ecdsa.PublicKey
asymKey *ecdsa.PrivateKey asymKey *ecdsa.PrivateKey
nodeid *ecdsa.PrivateKey nodeid *ecdsa.PrivateKey
topic []byte topic whisper.TopicType
asymKeyID string asymKeyID string
filterID string filterID string
symPass string symPass string
@ -84,7 +84,7 @@ var (
testMode = flag.Bool("test", false, "use of predefined parameters for diagnostics") testMode = flag.Bool("test", false, "use of predefined parameters for diagnostics")
echoMode = flag.Bool("echo", false, "echo mode: prints some arguments for diagnostics") echoMode = flag.Bool("echo", false, "echo mode: prints some arguments for diagnostics")
argVerbosity = flag.Int("verbosity", int(log.LvlWarn), "log verbosity level") argVerbosity = flag.Int("verbosity", int(log.LvlError), "log verbosity level")
argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds") argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds")
argWorkTime = flag.Uint("work", 5, "work time in seconds") argWorkTime = flag.Uint("work", 5, "work time in seconds")
argMaxSize = flag.Int("maxsize", whisper.DefaultMaxMessageLength, "max size of message") argMaxSize = flag.Int("maxsize", whisper.DefaultMaxMessageLength, "max size of message")
@ -129,7 +129,7 @@ func processArgs() {
if err != nil { if err != nil {
utils.Fatalf("Failed to parse the topic: %s", err) utils.Fatalf("Failed to parse the topic: %s", err)
} }
topic = x topic = whisper.BytesToTopic(x)
} }
if *asymmetricMode && len(*argPub) > 0 { if *asymmetricMode && len(*argPub) > 0 {
@ -183,7 +183,7 @@ func initialize() {
if *testMode { if *testMode {
symPass = "wwww" // ascii code: 0x77777777 symPass = "wwww" // ascii code: 0x77777777
msPassword = "mail server test password" msPassword = "wwww"
} }
if *bootstrapMode { if *bootstrapMode {
@ -307,7 +307,11 @@ func configureNode() {
if *asymmetricMode { if *asymmetricMode {
if len(*argPub) == 0 { if len(*argPub) == 0 {
s := scanLine("Please enter the peer's public key: ") s := scanLine("Please enter the peer's public key: ")
pub = crypto.ToECDSAPub(common.FromHex(s)) b := common.FromHex(s)
if b == nil {
utils.Fatalf("Error: can not convert hexadecimal string")
}
pub = crypto.ToECDSAPub(b)
if !isKeyValid(pub) { if !isKeyValid(pub) {
utils.Fatalf("Error: invalid public key") utils.Fatalf("Error: invalid public key")
} }
@ -326,7 +330,7 @@ func configureNode() {
if !*asymmetricMode && !*forwarderMode { if !*asymmetricMode && !*forwarderMode {
if len(symPass) == 0 { if len(symPass) == 0 {
symPass, err = console.Stdin.PromptPassword("Please enter the password: ") symPass, err = console.Stdin.PromptPassword("Please enter the password for symmetric encryption: ")
if err != nil { if err != nil {
utils.Fatalf("Failed to read passphrase: %v", err) utils.Fatalf("Failed to read passphrase: %v", err)
} }
@ -343,6 +347,8 @@ func configureNode() {
if len(*argTopic) == 0 { if len(*argTopic) == 0 {
generateTopic([]byte(symPass)) generateTopic([]byte(symPass))
} }
fmt.Printf("Filter is configured for the topic: %x \n", topic)
} }
if *mailServerMode { if *mailServerMode {
@ -354,18 +360,17 @@ func configureNode() {
filter := whisper.Filter{ filter := whisper.Filter{
KeySym: symKey, KeySym: symKey,
KeyAsym: asymKey, KeyAsym: asymKey,
Topics: [][]byte{topic}, Topics: [][]byte{topic[:]},
AllowP2P: p2pAccept, AllowP2P: p2pAccept,
} }
filterID, err = shh.Subscribe(&filter) filterID, err = shh.Subscribe(&filter)
if err != nil { if err != nil {
utils.Fatalf("Failed to install filter: %s", err) utils.Fatalf("Failed to install filter: %s", err)
} }
fmt.Printf("Filter is configured for the topic: %x \n", topic)
} }
func generateTopic(password []byte) { func generateTopic(password []byte) {
x := pbkdf2.Key(password, password, 8196, 128, sha512.New) x := pbkdf2.Key(password, password, 4096, 128, sha512.New)
for i := 0; i < len(x); i++ { for i := 0; i < len(x); i++ {
topic[i%whisper.TopicLength] ^= x[i] topic[i%whisper.TopicLength] ^= x[i]
} }
@ -485,16 +490,15 @@ func sendMsg(payload []byte) common.Hash {
Dst: pub, Dst: pub,
KeySym: symKey, KeySym: symKey,
Payload: payload, Payload: payload,
Topic: whisper.BytesToTopic(topic), Topic: topic,
TTL: uint32(*argTTL), TTL: uint32(*argTTL),
PoW: *argPoW, PoW: *argPoW,
WorkTime: uint32(*argWorkTime), WorkTime: uint32(*argWorkTime),
} }
msg := whisper.NewSentMessage(&params) msg, err := whisper.NewSentMessage(&params)
if msg == nil { if err != nil {
fmt.Printf("failed to create new message (OS level error)") utils.Fatalf("failed to create new message: %s", err)
os.Exit(0)
} }
envelope, err := msg.Wrap(&params) envelope, err := msg.Wrap(&params)
if err != nil { if err != nil {
@ -624,9 +628,9 @@ func requestExpiredMessagesLoop() {
params.Src = nodeid params.Src = nodeid
params.WorkTime = 5 params.WorkTime = 5
msg := whisper.NewSentMessage(&params) msg, err := whisper.NewSentMessage(&params)
if msg == nil { if err != nil {
utils.Fatalf("failed to create new message (OS level error)") utils.Fatalf("failed to create new message: %s", err)
} }
env, err := msg.Wrap(&params) env, err := msg.Wrap(&params)
if err != nil { if err != nil {

View File

@ -27,6 +27,8 @@ var (
tt256 = BigPow(2, 256) tt256 = BigPow(2, 256)
tt256m1 = new(big.Int).Sub(tt256, big.NewInt(1)) tt256m1 = new(big.Int).Sub(tt256, big.NewInt(1))
MaxBig256 = new(big.Int).Set(tt256m1) MaxBig256 = new(big.Int).Set(tt256m1)
tt63 = BigPow(2, 63)
MaxBig63 = new(big.Int).Sub(tt63, big.NewInt(1))
) )
const ( const (

View File

@ -599,7 +599,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch
for seen, recent := range snap.Recents { for seen, recent := range snap.Recents {
if recent == signer { if recent == signer {
// Signer is among recents, only wait if the current block doens't shift it out // Signer is among recents, only wait if the current block doens't shift it out
if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit { if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
log.Info("Signed recently, must wait for others") log.Info("Signed recently, must wait for others")
<-stop <-stop
return nil, nil return nil, nil

View File

@ -243,6 +243,15 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
if expected.Cmp(header.Difficulty) != 0 { if expected.Cmp(header.Difficulty) != 0 {
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected) return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
} }
// Verify that the gas limit is <= 2^63-1
if header.GasLimit.Cmp(math.MaxBig63) > 0 {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, math.MaxBig63)
}
// Verify that the gasUsed is <= gasLimit
if header.GasUsed.Cmp(header.GasLimit) > 0 {
return fmt.Errorf("invalid gasUsed: have %v, gasLimit %v", header.GasUsed, header.GasLimit)
}
// Verify that the gas limit remains within allowed bounds // Verify that the gas limit remains within allowed bounds
diff := new(big.Int).Set(parent.GasLimit) diff := new(big.Int).Set(parent.GasLimit)
diff = diff.Sub(diff, header.GasLimit) diff = diff.Sub(diff, header.GasLimit)

View File

@ -467,8 +467,9 @@ func (ethash *Ethash) cache(block uint64) []uint32 {
future = &cache{epoch: epoch + 1} future = &cache{epoch: epoch + 1}
ethash.fcache = future ethash.fcache = future
} }
} // New current cache, set its initial timestamp
current.used = time.Now() current.used = time.Now()
}
ethash.lock.Unlock() ethash.lock.Unlock()
// Wait for generation finish, bump the timestamp and finalize the cache // Wait for generation finish, bump the timestamp and finalize the cache
@ -529,8 +530,9 @@ func (ethash *Ethash) dataset(block uint64) []uint32 {
future = &dataset{epoch: epoch + 1} future = &dataset{epoch: epoch + 1}
ethash.fdataset = future ethash.fdataset = future
} }
} // New current dataset, set its initial timestamp
current.used = time.Now() current.used = time.Now()
}
ethash.lock.Unlock() ethash.lock.Unlock()
// Wait for generation finish, bump the timestamp and finalize the cache // Wait for generation finish, bump the timestamp and finalize the cache

View File

@ -20,6 +20,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"strings"
"time" "time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -240,17 +241,19 @@ func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) {
throwJSException(err.Error()) throwJSException(err.Error())
} }
var ( var (
rawReq = []byte(reqVal.String()) rawReq = reqVal.String()
dec = json.NewDecoder(strings.NewReader(rawReq))
reqs []jsonrpcCall reqs []jsonrpcCall
batch bool batch bool
) )
dec.UseNumber() // avoid float64s
if rawReq[0] == '[' { if rawReq[0] == '[' {
batch = true batch = true
json.Unmarshal(rawReq, &reqs) dec.Decode(&reqs)
} else { } else {
batch = false batch = false
reqs = make([]jsonrpcCall, 1) reqs = make([]jsonrpcCall, 1)
json.Unmarshal(rawReq, &reqs[0]) dec.Decode(&reqs[0])
} }
// Execute the requests. // Execute the requests.

View File

@ -628,7 +628,6 @@ type WriteStatus byte
const ( const (
NonStatTy WriteStatus = iota NonStatTy WriteStatus = iota
CanonStatTy CanonStatTy
SplitStatTy
SideStatTy SideStatTy
) )
@ -1029,9 +1028,6 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
blockInsertTimer.UpdateSince(bstart) blockInsertTimer.UpdateSince(bstart)
events = append(events, ChainSideEvent{block}) events = append(events, ChainSideEvent{block})
case SplitStatTy:
events = append(events, ChainSplitEvent{block, logs})
} }
stats.processed++ stats.processed++
stats.usedGas += usedGas.Uint64() stats.usedGas += usedGas.Uint64()
@ -1226,8 +1222,9 @@ func (self *BlockChain) postChainEvents(events []interface{}, logs []*types.Log)
self.eventMux.Post(logs) self.eventMux.Post(logs)
for _, event := range events { for _, event := range events {
if event, ok := event.(ChainEvent); ok { if event, ok := event.(ChainEvent); ok {
// We need some control over the mining operation. Acquiring locks and waiting for the miner to create new block takes too long // We need some control over the mining operation. Acquiring locks and waiting
// and in most cases isn't even necessary. // for the miner to create new block takes too long and in most cases isn't
// even necessary.
if self.LastBlockHash() == event.Hash { if self.LastBlockHash() == event.Hash {
self.eventMux.Post(ChainHeadEvent{event.Block}) self.eventMux.Post(ChainHeadEvent{event.Block})
} }

View File

@ -46,12 +46,6 @@ type RemovedTransactionEvent struct{ Txs types.Transactions }
// RemovedLogEvent is posted when a reorg happens // RemovedLogEvent is posted when a reorg happens
type RemovedLogsEvent struct{ Logs []*types.Log } type RemovedLogsEvent struct{ Logs []*types.Log }
// ChainSplit is posted when a new head is detected
type ChainSplitEvent struct {
Block *types.Block
Logs []*types.Log
}
type ChainEvent struct { type ChainEvent struct {
Block *types.Block Block *types.Block
Hash common.Hash Hash common.Hash

View File

@ -86,7 +86,7 @@ type GenesisMismatchError struct {
} }
func (e *GenesisMismatchError) Error() string { func (e *GenesisMismatchError) Error() string {
return fmt.Sprintf("wrong genesis block in database (have %x, new %x)", e.Stored[:8], e.New[:8]) return fmt.Sprintf("database already contains an incompatible genesis block (have %x, new %x)", e.Stored[:8], e.New[:8])
} }
// SetupGenesisBlock writes or updates the genesis block in db. // SetupGenesisBlock writes or updates the genesis block in db.
@ -278,6 +278,18 @@ func DefaultTestnetGenesisBlock() *Genesis {
} }
} }
// DefaultRinkebyGenesisBlock returns the Rinkeby network genesis block.
func DefaultRinkebyGenesisBlock() *Genesis {
return &Genesis{
Config: params.RinkebyChainConfig,
Timestamp: 1492009146,
ExtraData: hexutil.MustDecode("0x52657370656374206d7920617574686f7269746168207e452e436172746d616e42eb768f2244c8811c63729a21a3569731535f067ffc57839b00206d1ad20c69a1981b489f772031b279182d99e65703f0076e4812653aab85fca0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
GasLimit: 4700000,
Difficulty: big.NewInt(1),
Alloc: decodePrealloc(rinkebyAllocData),
}
}
// DevGenesisBlock returns the 'geth --dev' genesis block. // DevGenesisBlock returns the 'geth --dev' genesis block.
func DevGenesisBlock() *Genesis { func DevGenesisBlock() *Genesis {
return &Genesis{ return &Genesis{

File diff suppressed because one or more lines are too long

View File

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
) )
type DumpAccount struct { type DumpAccount struct {
@ -44,7 +45,7 @@ func (self *StateDB) RawDump() Dump {
Accounts: make(map[string]DumpAccount), Accounts: make(map[string]DumpAccount),
} }
it := self.trie.Iterator() it := trie.NewIterator(self.trie.NodeIterator(nil))
for it.Next() { for it.Next() {
addr := self.trie.GetKey(it.Key) addr := self.trie.GetKey(it.Key)
var data Account var data Account
@ -61,7 +62,7 @@ func (self *StateDB) RawDump() Dump {
Code: common.Bytes2Hex(obj.Code(self.db)), Code: common.Bytes2Hex(obj.Code(self.db)),
Storage: make(map[string]string), Storage: make(map[string]string),
} }
storageIt := obj.getTrie(self.db).Iterator() storageIt := trie.NewIterator(obj.getTrie(self.db).NodeIterator(nil))
for storageIt.Next() { for storageIt.Next() {
account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value)
} }

View File

@ -75,7 +75,7 @@ func (it *NodeIterator) step() error {
} }
// Initialize the iterator if we've just started // Initialize the iterator if we've just started
if it.stateIt == nil { if it.stateIt == nil {
it.stateIt = it.state.trie.NodeIterator() it.stateIt = it.state.trie.NodeIterator(nil)
} }
// If we had data nodes previously, we surely have at least state nodes // If we had data nodes previously, we surely have at least state nodes
if it.dataIt != nil { if it.dataIt != nil {
@ -118,7 +118,7 @@ func (it *NodeIterator) step() error {
if err != nil { if err != nil {
return err return err
} }
it.dataIt = trie.NewNodeIterator(dataTrie) it.dataIt = dataTrie.NodeIterator(nil)
if !it.dataIt.Next(true) { if !it.dataIt.Next(true) {
it.dataIt = nil it.dataIt = nil
} }

View File

@ -201,7 +201,7 @@ func (self *stateObject) setState(key, value common.Hash) {
} }
// updateTrie writes cached storage modifications into the object's storage trie. // updateTrie writes cached storage modifications into the object's storage trie.
func (self *stateObject) updateTrie(db trie.Database) { func (self *stateObject) updateTrie(db trie.Database) *trie.SecureTrie {
tr := self.getTrie(db) tr := self.getTrie(db)
for key, value := range self.dirtyStorage { for key, value := range self.dirtyStorage {
delete(self.dirtyStorage, key) delete(self.dirtyStorage, key)
@ -213,6 +213,7 @@ func (self *stateObject) updateTrie(db trie.Database) {
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00")) v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
tr.Update(key[:], v) tr.Update(key[:], v)
} }
return tr
} }
// UpdateRoot sets the trie root to the current root hash of // UpdateRoot sets the trie root to the current root hash of
@ -280,7 +281,11 @@ func (c *stateObject) ReturnGas(gas *big.Int) {}
func (self *stateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *stateObject { func (self *stateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *stateObject {
stateObject := newObject(db, self.address, self.data, onDirty) stateObject := newObject(db, self.address, self.data, onDirty)
stateObject.trie = self.trie if self.trie != nil {
// A shallow copy makes the two tries independent.
cpy := *self.trie
stateObject.trie = &cpy
}
stateObject.code = self.code stateObject.code = self.code
stateObject.dirtyStorage = self.dirtyStorage.Copy() stateObject.dirtyStorage = self.dirtyStorage.Copy()
stateObject.cachedStorage = self.dirtyStorage.Copy() stateObject.cachedStorage = self.dirtyStorage.Copy()

View File

@ -296,6 +296,17 @@ func (self *StateDB) GetState(a common.Address, b common.Hash) common.Hash {
return common.Hash{} return common.Hash{}
} }
// StorageTrie returns the storage trie of an account.
// The return value is a copy and is nil for non-existent accounts.
func (self *StateDB) StorageTrie(a common.Address) *trie.SecureTrie {
stateObject := self.getStateObject(a)
if stateObject == nil {
return nil
}
cpy := stateObject.deepCopy(self, nil)
return cpy.updateTrie(self.db)
}
func (self *StateDB) HasSuicided(addr common.Address) bool { func (self *StateDB) HasSuicided(addr common.Address) bool {
stateObject := self.getStateObject(addr) stateObject := self.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -481,7 +492,7 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common
cb(h, value) cb(h, value)
} }
it := so.getTrie(db.db).Iterator() it := trie.NewIterator(so.getTrie(db.db).NodeIterator(nil))
for it.Next() { for it.Next() {
// ignore cached values // ignore cached values
key := common.BytesToHash(db.trie.GetKey(it.Key)) key := common.BytesToHash(db.trie.GetKey(it.Key))

View File

@ -20,7 +20,6 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -41,6 +40,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
) )
const defaultTraceTimeout = 5 * time.Second const defaultTraceTimeout = 5 * time.Second
@ -526,47 +526,17 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common.
if tx == nil { if tx == nil {
return nil, fmt.Errorf("transaction %x not found", txHash) return nil, fmt.Errorf("transaction %x not found", txHash)
} }
block := api.eth.BlockChain().GetBlockByHash(blockHash) msg, context, statedb, err := api.computeTxEnv(blockHash, int(txIndex))
if block == nil {
return nil, fmt.Errorf("block %x not found", blockHash)
}
// Create the state database to mutate and eventually trace
parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return nil, fmt.Errorf("block parent %x not found", block.ParentHash())
}
stateDb, err := api.eth.BlockChain().StateAt(parent.Root())
if err != nil { if err != nil {
return nil, err return nil, err
} }
signer := types.MakeSigner(api.config, block.Number()) // Run the transaction with tracing enabled.
// Mutate the state and trace the selected transaction vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{Debug: true, Tracer: tracer})
for idx, tx := range block.Transactions() {
// Assemble the transaction call message
msg, err := tx.AsMessage(signer)
if err != nil {
return nil, fmt.Errorf("sender retrieval failed: %v", err)
}
context := core.NewEVMContext(msg, block.Header(), api.eth.BlockChain(), nil)
// Mutate the state if we haven't reached the tracing transaction yet
if uint64(idx) < txIndex {
vmenv := vm.NewEVM(context, stateDb, api.config, vm.Config{})
_, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
return nil, fmt.Errorf("mutation failed: %v", err)
}
stateDb.DeleteSuicides()
continue
}
vmenv := vm.NewEVM(context, stateDb, api.config, vm.Config{Debug: true, Tracer: tracer})
ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil { if err != nil {
return nil, fmt.Errorf("tracing failed: %v", err) return nil, fmt.Errorf("tracing failed: %v", err)
} }
switch tracer := tracer.(type) { switch tracer := tracer.(type) {
case *vm.StructLogger: case *vm.StructLogger:
return &ethapi.ExecutionResult{ return &ethapi.ExecutionResult{
@ -576,9 +546,47 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common.
}, nil }, nil
case *ethapi.JavascriptTracer: case *ethapi.JavascriptTracer:
return tracer.GetResult() return tracer.GetResult()
default:
panic(fmt.Sprintf("bad tracer type %T", tracer))
} }
}
// computeTxEnv returns the execution environment of a certain transaction.
func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int) (core.Message, vm.Context, *state.StateDB, error) {
// Create the parent state.
block := api.eth.BlockChain().GetBlockByHash(blockHash)
if block == nil {
return nil, vm.Context{}, nil, fmt.Errorf("block %x not found", blockHash)
} }
return nil, errors.New("database inconsistency") parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return nil, vm.Context{}, nil, fmt.Errorf("block parent %x not found", block.ParentHash())
}
statedb, err := api.eth.BlockChain().StateAt(parent.Root())
if err != nil {
return nil, vm.Context{}, nil, err
}
txs := block.Transactions()
// Recompute transactions up to the target index.
signer := types.MakeSigner(api.config, block.Number())
for idx, tx := range txs {
// Assemble the transaction call message
msg, _ := tx.AsMessage(signer)
context := core.NewEVMContext(msg, block.Header(), api.eth.BlockChain(), nil)
if idx == txIndex {
return msg, context, statedb, nil
}
vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{})
gp := new(core.GasPool).AddGas(tx.Gas())
_, _, err := core.ApplyMessage(vmenv, msg, gp)
if err != nil {
return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err)
}
statedb.DeleteSuicides()
}
return nil, vm.Context{}, nil, fmt.Errorf("tx index %d out of range for block %x", txIndex, blockHash)
} }
// Preimage is a debug API function that returns the preimage for a sha3 hash, if known. // Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
@ -592,3 +600,48 @@ func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hex
func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]core.BadBlockArgs, error) { func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]core.BadBlockArgs, error) {
return api.eth.BlockChain().BadBlocks() return api.eth.BlockChain().BadBlocks()
} }
// StorageRangeResult is the result of a debug_storageRangeAt API call.
type StorageRangeResult struct {
Storage storageMap `json:"storage"`
NextKey *common.Hash `json:"nextKey"` // nil if Storage includes the last key in the trie.
}
type storageMap map[common.Hash]storageEntry
type storageEntry struct {
Key *common.Hash `json:"key"`
Value common.Hash `json:"value"`
}
// StorageRangeAt returns the storage at the given block height and transaction index.
func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) {
_, _, statedb, err := api.computeTxEnv(blockHash, txIndex)
if err != nil {
return StorageRangeResult{}, err
}
st := statedb.StorageTrie(contractAddress)
if st == nil {
return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress)
}
return storageRangeAt(st, keyStart, maxResult), nil
}
func storageRangeAt(st *trie.SecureTrie, start []byte, maxResult int) StorageRangeResult {
it := trie.NewIterator(st.NodeIterator(start))
result := StorageRangeResult{Storage: storageMap{}}
for i := 0; i < maxResult && it.Next(); i++ {
e := storageEntry{Value: common.BytesToHash(it.Value)}
if preimage := st.GetKey(it.Key); preimage != nil {
preimage := common.BytesToHash(preimage)
e.Key = &preimage
}
result.Storage[common.BytesToHash(it.Key)] = e
}
// Add the 'next key' so clients can continue downloading.
if it.Next() {
next := common.BytesToHash(it.Key)
result.NextKey = &next
}
return result
}

88
eth/api_test.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library 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.
//
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package eth
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/ethdb"
)
var dumper = spew.ConfigState{Indent: " "}
func TestStorageRangeAt(t *testing.T) {
// Create a state where account 0x010000... has a few storage entries.
var (
db, _ = ethdb.NewMemDatabase()
state, _ = state.New(common.Hash{}, db)
addr = common.Address{0x01}
keys = []common.Hash{ // hashes of Keys of storage
common.HexToHash("340dd630ad21bf010b4e676dbfa9ba9a02175262d1fa356232cfde6cb5b47ef2"),
common.HexToHash("426fcb404ab2d5d8e61a3d918108006bbb0a9be65e92235bb10eefbdb6dcd053"),
common.HexToHash("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5"),
common.HexToHash("5723d2c3a83af9b735e3b7f21531e5623d183a9095a56604ead41f3582fdfb75"),
}
storage = storageMap{
keys[0]: {Key: &common.Hash{0x02}, Value: common.Hash{0x01}},
keys[1]: {Key: &common.Hash{0x04}, Value: common.Hash{0x02}},
keys[2]: {Key: &common.Hash{0x01}, Value: common.Hash{0x03}},
keys[3]: {Key: &common.Hash{0x03}, Value: common.Hash{0x04}},
}
)
for _, entry := range storage {
state.SetState(addr, *entry.Key, entry.Value)
}
// Check a few combinations of limit and start/end.
tests := []struct {
start []byte
limit int
want StorageRangeResult
}{
{
start: []byte{}, limit: 0,
want: StorageRangeResult{storageMap{}, &keys[0]},
},
{
start: []byte{}, limit: 100,
want: StorageRangeResult{storage, nil},
},
{
start: []byte{}, limit: 2,
want: StorageRangeResult{storageMap{keys[0]: storage[keys[0]], keys[1]: storage[keys[1]]}, &keys[2]},
},
{
start: []byte{0x00}, limit: 4,
want: StorageRangeResult{storage, nil},
},
{
start: []byte{0x40}, limit: 2,
want: StorageRangeResult{storageMap{keys[1]: storage[keys[1]], keys[2]: storage[keys[2]]}, &keys[3]},
},
}
for _, test := range tests {
result := storageRangeAt(state.StorageTrie(addr), test.start, test.limit)
if !reflect.DeepEqual(result, test.want) {
t.Fatalf("wrong result for range 0x%x.., limit %d:\ngot %s\nwant %s",
test.start, test.limit, dumper.Sdump(result), dumper.Sdump(&test.want))
}
}
}

View File

@ -80,7 +80,7 @@ type Ethereum struct {
MinerThreads int MinerThreads int
etherbase common.Address etherbase common.Address
netVersionId int networkId uint64
netRPCService *ethapi.PublicNetAPI netRPCService *ethapi.PublicNetAPI
} }
@ -118,7 +118,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
engine: CreateConsensusEngine(ctx, config, chainConfig, chainDb), engine: CreateConsensusEngine(ctx, config, chainConfig, chainDb),
shutdownChan: make(chan bool), shutdownChan: make(chan bool),
stopDbUpgrade: stopDbUpgrade, stopDbUpgrade: stopDbUpgrade,
netVersionId: config.NetworkId, networkId: config.NetworkId,
etherbase: config.Etherbase, etherbase: config.Etherbase,
MinerThreads: config.MinerThreads, MinerThreads: config.MinerThreads,
} }
@ -347,7 +347,7 @@ func (s *Ethereum) Engine() consensus.Engine { return s.engine }
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
func (s *Ethereum) IsListening() bool { return true } // Always listening func (s *Ethereum) IsListening() bool { return true } // Always listening
func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) }
func (s *Ethereum) NetVersion() int { return s.netVersionId } func (s *Ethereum) NetVersion() uint64 { return s.networkId }
func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
// Protocols implements node.Service, returning all the currently configured // Protocols implements node.Service, returning all the currently configured

View File

@ -72,7 +72,7 @@ type Config struct {
Genesis *core.Genesis `toml:",omitempty"` Genesis *core.Genesis `toml:",omitempty"`
// Protocol options // Protocol options
NetworkId int // Network ID to use for selecting peers to connect to NetworkId uint64 // Network ID to use for selecting peers to connect to
SyncMode downloader.SyncMode SyncMode downloader.SyncMode
// Light client options // Light client options

View File

@ -1491,6 +1491,10 @@ func (d *Downloader) qosTuner() {
func (d *Downloader) qosReduceConfidence() { func (d *Downloader) qosReduceConfidence() {
// If we have a single peer, confidence is always 1 // If we have a single peer, confidence is always 1
peers := uint64(d.peers.Len()) peers := uint64(d.peers.Len())
if peers == 0 {
// Ensure peer connectivity races don't catch us off guard
return
}
if peers == 1 { if peers == 1 {
atomic.StoreUint64(&d.rttConfidence, 1000000) atomic.StoreUint64(&d.rttConfidence, 1000000)
return return

View File

@ -15,7 +15,7 @@ import (
func (c Config) MarshalTOML() (interface{}, error) { func (c Config) MarshalTOML() (interface{}, error) {
type Config struct { type Config struct {
Genesis *core.Genesis `toml:",omitempty"` Genesis *core.Genesis `toml:",omitempty"`
NetworkId int NetworkId uint64
SyncMode downloader.SyncMode SyncMode downloader.SyncMode
LightServ int `toml:",omitempty"` LightServ int `toml:",omitempty"`
LightPeers int `toml:",omitempty"` LightPeers int `toml:",omitempty"`
@ -72,7 +72,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
type Config struct { type Config struct {
Genesis *core.Genesis `toml:",omitempty"` Genesis *core.Genesis `toml:",omitempty"`
NetworkId *int NetworkId *uint64
SyncMode *downloader.SyncMode SyncMode *downloader.SyncMode
LightServ *int `toml:",omitempty"` LightServ *int `toml:",omitempty"`
LightPeers *int `toml:",omitempty"` LightPeers *int `toml:",omitempty"`

View File

@ -60,7 +60,7 @@ func errResp(code errCode, format string, v ...interface{}) error {
} }
type ProtocolManager struct { type ProtocolManager struct {
networkId int networkId uint64
fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks) fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks)
acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing) acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing)
@ -96,7 +96,7 @@ type ProtocolManager struct {
// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable // NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
// with the ethereum network. // with the ethereum network.
func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, networkId int, maxPeers int, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database) (*ProtocolManager, error) { func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, networkId uint64, maxPeers int, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database) (*ProtocolManager, error) {
// Create the protocol manager with the base fields // Create the protocol manager with the base fields
manager := &ProtocolManager{ manager := &ProtocolManager{
networkId: networkId, networkId: networkId,
@ -733,7 +733,7 @@ func (self *ProtocolManager) txBroadcastLoop() {
// EthNodeInfo represents a short summary of the Ethereum sub-protocol metadata known // EthNodeInfo represents a short summary of the Ethereum sub-protocol metadata known
// about the host peer. // about the host peer.
type EthNodeInfo struct { type EthNodeInfo struct {
Network int `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3) Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3)
Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain
Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block

View File

@ -173,7 +173,7 @@ func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*te
func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash) { func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash) {
msg := &statusData{ msg := &statusData{
ProtocolVersion: uint32(p.version), ProtocolVersion: uint32(p.version),
NetworkId: uint32(DefaultConfig.NetworkId), NetworkId: DefaultConfig.NetworkId,
TD: td, TD: td,
CurrentBlock: head, CurrentBlock: head,
GenesisBlock: genesis, GenesisBlock: genesis,

View File

@ -230,7 +230,7 @@ func (p *peer) RequestReceipts(hashes []common.Hash) error {
// Handshake executes the eth protocol handshake, negotiating version number, // Handshake executes the eth protocol handshake, negotiating version number,
// network IDs, difficulties, head and genesis blocks. // network IDs, difficulties, head and genesis blocks.
func (p *peer) Handshake(network int, td *big.Int, head common.Hash, genesis common.Hash) error { func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash) error {
// Send out own handshake in a new thread // Send out own handshake in a new thread
errc := make(chan error, 2) errc := make(chan error, 2)
var status statusData // safe to read after two values have been received from errc var status statusData // safe to read after two values have been received from errc
@ -238,7 +238,7 @@ func (p *peer) Handshake(network int, td *big.Int, head common.Hash, genesis com
go func() { go func() {
errc <- p2p.Send(p.rw, StatusMsg, &statusData{ errc <- p2p.Send(p.rw, StatusMsg, &statusData{
ProtocolVersion: uint32(p.version), ProtocolVersion: uint32(p.version),
NetworkId: uint32(network), NetworkId: network,
TD: td, TD: td,
CurrentBlock: head, CurrentBlock: head,
GenesisBlock: genesis, GenesisBlock: genesis,
@ -263,7 +263,7 @@ func (p *peer) Handshake(network int, td *big.Int, head common.Hash, genesis com
return nil return nil
} }
func (p *peer) readStatus(network int, status *statusData, genesis common.Hash) (err error) { func (p *peer) readStatus(network uint64, status *statusData, genesis common.Hash) (err error) {
msg, err := p.rw.ReadMsg() msg, err := p.rw.ReadMsg()
if err != nil { if err != nil {
return err return err
@ -281,7 +281,7 @@ func (p *peer) readStatus(network int, status *statusData, genesis common.Hash)
if status.GenesisBlock != genesis { if status.GenesisBlock != genesis {
return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock[:8], genesis[:8]) return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock[:8], genesis[:8])
} }
if int(status.NetworkId) != network { if status.NetworkId != network {
return errResp(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, network) return errResp(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, network)
} }
if int(status.ProtocolVersion) != p.version { if int(status.ProtocolVersion) != p.version {

View File

@ -105,7 +105,7 @@ type txPool interface {
// statusData is the network packet for the status message. // statusData is the network packet for the status message.
type statusData struct { type statusData struct {
ProtocolVersion uint32 ProtocolVersion uint32
NetworkId uint32 NetworkId uint64
TD *big.Int TD *big.Int
CurrentBlock common.Hash CurrentBlock common.Hash
GenesisBlock common.Hash GenesisBlock common.Hash

View File

@ -55,7 +55,7 @@ func testStatusMsgErrors(t *testing.T, protocol int) {
wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"), wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"),
}, },
{ {
code: StatusMsg, data: statusData{10, uint32(DefaultConfig.NetworkId), td, currentBlock, genesis}, code: StatusMsg, data: statusData{10, DefaultConfig.NetworkId, td, currentBlock, genesis},
wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", protocol), wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", protocol),
}, },
{ {
@ -63,7 +63,7 @@ func testStatusMsgErrors(t *testing.T, protocol int) {
wantError: errResp(ErrNetworkIdMismatch, "999 (!= 1)"), wantError: errResp(ErrNetworkIdMismatch, "999 (!= 1)"),
}, },
{ {
code: StatusMsg, data: statusData{uint32(protocol), uint32(DefaultConfig.NetworkId), td, currentBlock, common.Hash{3}}, code: StatusMsg, data: statusData{uint32(protocol), DefaultConfig.NetworkId, td, currentBlock, common.Hash{3}},
wantError: errResp(ErrGenesisBlockMismatch, "0300000000000000 (!= %x)", genesis[:8]), wantError: errResp(ErrGenesisBlockMismatch, "0300000000000000 (!= %x)", genesis[:8]),
}, },
} }

View File

@ -323,10 +323,10 @@ func (s *Service) login(conn *websocket.Conn) error {
var network, protocol string var network, protocol string
if info := infos.Protocols["eth"]; info != nil { if info := infos.Protocols["eth"]; info != nil {
network = strconv.Itoa(info.(*eth.EthNodeInfo).Network) network = fmt.Sprintf("%d", info.(*eth.EthNodeInfo).Network)
protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0]) protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0])
} else { } else {
network = strconv.Itoa(infos.Protocols["les"].(*eth.EthNodeInfo).Network) network = fmt.Sprintf("%d", infos.Protocols["les"].(*eth.EthNodeInfo).Network)
protocol = fmt.Sprintf("les/%d", les.ProtocolVersions[0]) protocol = fmt.Sprintf("les/%d", les.ProtocolVersions[0])
} }
auth := &authMsg{ auth := &authMsg{

View File

@ -191,7 +191,7 @@ func NewPublicAccountAPI(am *accounts.Manager) *PublicAccountAPI {
// Accounts returns the collection of accounts this node manages // Accounts returns the collection of accounts this node manages
func (s *PublicAccountAPI) Accounts() []common.Address { func (s *PublicAccountAPI) Accounts() []common.Address {
var addresses []common.Address addresses := make([]common.Address, 0) // return [] instead of nil if empty
for _, wallet := range s.am.Wallets() { for _, wallet := range s.am.Wallets() {
for _, account := range wallet.Accounts() { for _, account := range wallet.Accounts() {
addresses = append(addresses, account.Address) addresses = append(addresses, account.Address)
@ -218,7 +218,7 @@ func NewPrivateAccountAPI(b Backend) *PrivateAccountAPI {
// ListAccounts will return a list of addresses for accounts this node manages. // ListAccounts will return a list of addresses for accounts this node manages.
func (s *PrivateAccountAPI) ListAccounts() []common.Address { func (s *PrivateAccountAPI) ListAccounts() []common.Address {
var addresses []common.Address addresses := make([]common.Address, 0) // return [] instead of nil if empty
for _, wallet := range s.am.Wallets() { for _, wallet := range s.am.Wallets() {
for _, account := range wallet.Accounts() { for _, account := range wallet.Accounts() {
addresses = append(addresses, account.Address) addresses = append(addresses, account.Address)
@ -237,7 +237,7 @@ type rawWallet struct {
// ListWallets will return a list of wallets this node manages. // ListWallets will return a list of wallets this node manages.
func (s *PrivateAccountAPI) ListWallets() []rawWallet { func (s *PrivateAccountAPI) ListWallets() []rawWallet {
var wallets []rawWallet wallets := make([]rawWallet, 0) // return [] instead of nil if empty
for _, wallet := range s.am.Wallets() { for _, wallet := range s.am.Wallets() {
wallets = append(wallets, rawWallet{ wallets = append(wallets, rawWallet{
URL: wallet.URL().String(), URL: wallet.URL().String(),
@ -1435,11 +1435,11 @@ func (api *PrivateDebugAPI) SetHead(number hexutil.Uint64) {
// PublicNetAPI offers network related RPC methods // PublicNetAPI offers network related RPC methods
type PublicNetAPI struct { type PublicNetAPI struct {
net *p2p.Server net *p2p.Server
networkVersion int networkVersion uint64
} }
// NewPublicNetAPI creates a new net API instance. // NewPublicNetAPI creates a new net API instance.
func NewPublicNetAPI(net *p2p.Server, networkVersion int) *PublicNetAPI { func NewPublicNetAPI(net *p2p.Server, networkVersion uint64) *PublicNetAPI {
return &PublicNetAPI{net, networkVersion} return &PublicNetAPI{net, networkVersion}
} }

View File

@ -345,6 +345,11 @@ web3._extend({
call: 'debug_getBadBlocks', call: 'debug_getBadBlocks',
params: 0, params: 0,
}), }),
new web3._extend.Method({
name: 'storageRangeAt',
call: 'debug_storageRangeAt',
params: 5,
}),
], ],
properties: [] properties: []
}); });
@ -520,7 +525,105 @@ web3._extend({
const Shh_JS = ` const Shh_JS = `
web3._extend({ web3._extend({
property: 'shh', property: 'shh',
methods: [], methods: [
new web3._extend.Method({
name: 'info',
call: 'shh_info'
}),
new web3._extend.Method({
name: 'setMaxMessageLength',
call: 'shh_setMaxMessageLength',
params: 1
}),
new web3._extend.Method({
name: 'setMinimumPoW',
call: 'shh_setMinimumPoW',
params: 1
}),
new web3._extend.Method({
name: 'allowP2PMessagesFromPeer',
call: 'shh_allowP2PMessagesFromPeer',
params: 1
}),
new web3._extend.Method({
name: 'hasKeyPair',
call: 'shh_hasKeyPair',
params: 1
}),
new web3._extend.Method({
name: 'deleteKeyPair',
call: 'shh_deleteKeyPair',
params: 1
}),
new web3._extend.Method({
name: 'newKeyPair',
call: 'shh_newKeyPair'
}),
new web3._extend.Method({
name: 'getPublicKey',
call: 'shh_getPublicKey',
params: 1
}),
new web3._extend.Method({
name: 'getPrivateKey',
call: 'shh_getPrivateKey',
params: 1
}),
new web3._extend.Method({
name: 'generateSymmetricKey',
call: 'shh_generateSymmetricKey',
}),
new web3._extend.Method({
name: 'addSymmetricKeyDirect',
call: 'shh_addSymmetricKeyDirect',
params: 1
}),
new web3._extend.Method({
name: 'addSymmetricKeyFromPassword',
call: 'shh_addSymmetricKeyFromPassword',
params: 1
}),
new web3._extend.Method({
name: 'hasSymmetricKey',
call: 'shh_hasSymmetricKey',
params: 1
}),
new web3._extend.Method({
name: 'getSymmetricKey',
call: 'shh_getSymmetricKey',
params: 1
}),
new web3._extend.Method({
name: 'deleteSymmetricKey',
call: 'shh_deleteSymmetricKey',
params: 1
}),
new web3._extend.Method({
name: 'subscribe',
call: 'shh_subscribe',
params: 1
}),
new web3._extend.Method({
name: 'unsubscribe',
call: 'shh_unsubscribe',
params: 1
}),
new web3._extend.Method({
name: 'getNewSubscriptionMessages',
call: 'shh_getNewSubscriptionMessages',
params: 1
}),
new web3._extend.Method({
name: 'getFloatingMessages',
call: 'shh_getFloatingMessages',
params: 1
}),
new web3._extend.Method({
name: 'post',
call: 'shh_post',
params: 1
})
],
properties: properties:
[ [
new web3._extend.Property({ new web3._extend.Property({
@ -531,6 +634,7 @@ web3._extend({
] ]
}); });
` `
const SWARMFS_JS = ` const SWARMFS_JS = `
web3._extend({ web3._extend({
property: 'swarmfs', property: 'swarmfs',

View File

@ -61,7 +61,7 @@ type LightEthereum struct {
engine consensus.Engine engine consensus.Engine
accountManager *accounts.Manager accountManager *accounts.Manager
netVersionId int networkId uint64
netRPCService *ethapi.PublicNetAPI netRPCService *ethapi.PublicNetAPI
} }
@ -87,7 +87,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
accountManager: ctx.AccountManager, accountManager: ctx.AccountManager,
engine: eth.CreateConsensusEngine(ctx, config, chainConfig, chainDb), engine: eth.CreateConsensusEngine(ctx, config, chainConfig, chainDb),
shutdownChan: make(chan bool), shutdownChan: make(chan bool),
netVersionId: config.NetworkId, networkId: config.NetworkId,
} }
if eth.blockchain, err = light.NewLightChain(odr, eth.chainConfig, eth.engine, eth.eventMux); err != nil { if eth.blockchain, err = light.NewLightChain(odr, eth.chainConfig, eth.engine, eth.eventMux); err != nil {
return nil, err return nil, err
@ -187,7 +187,7 @@ func (s *LightEthereum) Protocols() []p2p.Protocol {
// Ethereum protocol implementation. // Ethereum protocol implementation.
func (s *LightEthereum) Start(srvr *p2p.Server) error { func (s *LightEthereum) Start(srvr *p2p.Server) error {
log.Warn("Light client mode is an experimental feature") log.Warn("Light client mode is an experimental feature")
s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.netVersionId) s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.networkId)
s.protocolManager.Start(srvr) s.protocolManager.Start(srvr)
return nil return nil
} }

View File

@ -95,7 +95,7 @@ type ProtocolManager struct {
lightSync bool lightSync bool
txpool txPool txpool txPool
txrelay *LesTxRelay txrelay *LesTxRelay
networkId int networkId uint64
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
blockchain BlockChain blockchain BlockChain
chainDb ethdb.Database chainDb ethdb.Database
@ -128,7 +128,7 @@ type ProtocolManager struct {
// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable // NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
// with the ethereum network. // with the ethereum network.
func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, networkId int, mux *event.TypeMux, engine consensus.Engine, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay) (*ProtocolManager, error) { func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, networkId uint64, mux *event.TypeMux, engine consensus.Engine, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay) (*ProtocolManager, error) {
// Create the protocol manager with the base fields // Create the protocol manager with the base fields
manager := &ProtocolManager{ manager := &ProtocolManager{
lightSync: lightSync, lightSync: lightSync,
@ -310,7 +310,7 @@ func (pm *ProtocolManager) Stop() {
log.Info("Light Ethereum protocol stopped") log.Info("Light Ethereum protocol stopped")
} }
func (pm *ProtocolManager) newPeer(pv, nv int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { func (pm *ProtocolManager) newPeer(pv int, nv uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
return newPeer(pv, nv, p, newMeteredMsgWriter(rw)) return newPeer(pv, nv, p, newMeteredMsgWriter(rw))
} }

View File

@ -49,7 +49,7 @@ type peer struct {
rw p2p.MsgReadWriter rw p2p.MsgReadWriter
version int // Protocol version negotiated version int // Protocol version negotiated
network int // Network ID being on network uint64 // Network ID being on
id string id string
@ -69,7 +69,7 @@ type peer struct {
fcCosts requestCostTable fcCosts requestCostTable
} }
func newPeer(version, network int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
id := p.ID() id := p.ID()
return &peer{ return &peer{
@ -384,7 +384,7 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
if rGenesis != genesis { if rGenesis != genesis {
return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", rGenesis[:8], genesis[:8]) return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", rGenesis[:8], genesis[:8])
} }
if int(rNetwork) != p.network { if rNetwork != p.network {
return errResp(ErrNetworkIdMismatch, "%d (!= %d)", rNetwork, p.network) return errResp(ErrNetworkIdMismatch, "%d (!= %d)", rNetwork, p.network)
} }
if int(rVersion) != p.version { if int(rVersion) != p.version {

View File

@ -377,9 +377,6 @@ func (self *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int)
case core.SideStatTy: case core.SideStatTy:
log.Debug("Inserted forked header", "number", header.Number, "hash", header.Hash()) log.Debug("Inserted forked header", "number", header.Number, "hash", header.Hash())
events = append(events, core.ChainSideEvent{Block: types.NewBlockWithHeader(header)}) events = append(events, core.ChainSideEvent{Block: types.NewBlockWithHeader(header)})
case core.SplitStatTy:
events = append(events, core.ChainSplitEvent{Block: types.NewBlockWithHeader(header)})
} }
return err return err
} }

View File

@ -19,6 +19,7 @@ package light
import ( import (
"context" "context"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
@ -46,26 +47,18 @@ func NewLightTrie(id *TrieID, odr OdrBackend, useFakeMap bool) *LightTrie {
// retrieveKey retrieves a single key, returns true and stores nodes in local // retrieveKey retrieves a single key, returns true and stores nodes in local
// database if successful // database if successful
func (t *LightTrie) retrieveKey(ctx context.Context, key []byte) bool { func (t *LightTrie) retrieveKey(ctx context.Context, key []byte) bool {
r := &TrieRequest{Id: t.id, Key: key} r := &TrieRequest{Id: t.id, Key: crypto.Keccak256(key)}
return t.odr.Retrieve(ctx, r) == nil return t.odr.Retrieve(ctx, r) == nil
} }
// do tries and retries to execute a function until it returns with no error or // do tries and retries to execute a function until it returns with no error or
// an error type other than MissingNodeError // an error type other than MissingNodeError
func (t *LightTrie) do(ctx context.Context, fallbackKey []byte, fn func() error) error { func (t *LightTrie) do(ctx context.Context, key []byte, fn func() error) error {
err := fn() err := fn()
for err != nil { for err != nil {
mn, ok := err.(*trie.MissingNodeError) if _, ok := err.(*trie.MissingNodeError); !ok {
if !ok {
return err return err
} }
var key []byte
if mn.PrefixLen+mn.SuffixLen > 0 {
key = mn.Key
} else {
key = fallbackKey
}
if !t.retrieveKey(ctx, key) { if !t.retrieveKey(ctx, key) {
break break
} }

View File

@ -34,7 +34,7 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv2" whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
) )
// NodeConfig represents the collection of configuration values to fine tune the Geth // NodeConfig represents the collection of configuration values to fine tune the Geth
@ -54,7 +54,7 @@ type NodeConfig struct {
// EthereumNetworkID is the network identifier used by the Ethereum protocol to // EthereumNetworkID is the network identifier used by the Ethereum protocol to
// decide if remote peers should be accepted or not. // decide if remote peers should be accepted or not.
EthereumNetworkID int EthereumNetworkID int64 // uint64 in truth, but Java can't handle that...
// EthereumGenesis is the genesis JSON to use to seed the blockchain with. An // EthereumGenesis is the genesis JSON to use to seed the blockchain with. An
// empty genesis state is equivalent to using the mainnet's state. // empty genesis state is equivalent to using the mainnet's state.
@ -148,7 +148,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
ethConf := eth.DefaultConfig ethConf := eth.DefaultConfig
ethConf.Genesis = genesis ethConf.Genesis = genesis
ethConf.SyncMode = downloader.LightSync ethConf.SyncMode = downloader.LightSync
ethConf.NetworkId = config.EthereumNetworkID ethConf.NetworkId = uint64(config.EthereumNetworkID)
ethConf.DatabaseCache = config.EthereumDatabaseCache ethConf.DatabaseCache = config.EthereumDatabaseCache
if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return les.New(ctx, &ethConf) return les.New(ctx, &ethConf)

View File

@ -82,6 +82,9 @@ type Config struct {
// scrypt KDF at the expense of security. // scrypt KDF at the expense of security.
UseLightweightKDF bool `toml:",omitempty"` UseLightweightKDF bool `toml:",omitempty"`
// NoUSB disables hardware wallet monitoring and connectivity.
NoUSB bool `toml:",omitempty"`
// IPCPath is the requested location to place the IPC endpoint. If the path is // IPCPath is the requested location to place the IPC endpoint. If the path is
// a simple file name, it is placed inside the data directory (or on the root // a simple file name, it is placed inside the data directory (or on the root
// pipe path on Windows), whereas if it's a resolvable path name (absolute or // pipe path on Windows), whereas if it's a resolvable path name (absolute or
@ -389,10 +392,12 @@ func makeAccountManager(conf *Config) (*accounts.Manager, string, error) {
backends := []accounts.Backend{ backends := []accounts.Backend{
keystore.NewKeyStore(keydir, scryptN, scryptP), keystore.NewKeyStore(keydir, scryptN, scryptP),
} }
if !conf.NoUSB {
if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
} else { } else {
backends = append(backends, ledgerhub) backends = append(backends, ledgerhub)
} }
}
return accounts.NewManager(backends...), ephemeral, nil return accounts.NewManager(backends...), ephemeral, nil
} }

View File

@ -536,6 +536,7 @@ func (n *Node) Stop() error {
func (n *Node) Wait() { func (n *Node) Wait() {
n.lock.RLock() n.lock.RLock()
if n.server == nil { if n.server == nil {
n.lock.RUnlock()
return return
} }
stop := n.stop stop := n.stop

View File

@ -39,6 +39,18 @@ var TestnetBootnodes = []string{
"enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303", // IE "enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303", // IE
} }
// RinkebyBootnodes are the enode URLs of the P2P bootstrap nodes running on the
// Rinkeby test network.
var RinkebyBootnodes = []string{
"enode://a24ac7c5484ef4ed0c5eb2d36620ba4e4aa13b8c84684e1b4aab0cebea2ae45cb4d375b77eab56516d34bfbd3c1a833fc51296ff084b770b94fb9028c4d25ccf@52.169.42.101:30303", // IE
}
// RinkebyV5Bootnodes are the enode URLs of the P2P bootstrap nodes running on the
// Rinkeby test network for the experimental RLPx v5 topic-discovery network.
var RinkebyV5Bootnodes = []string{
"enode://a24ac7c5484ef4ed0c5eb2d36620ba4e4aa13b8c84684e1b4aab0cebea2ae45cb4d375b77eab56516d34bfbd3c1a833fc51296ff084b770b94fb9028c4d25ccf@52.169.42.101:30303?discport=30304", // IE
}
// DiscoveryV5Bootnodes are the enode URLs of the P2P bootstrap nodes for the // DiscoveryV5Bootnodes are the enode URLs of the P2P bootstrap nodes for the
// experimental RLPx v5 topic-discovery network. // experimental RLPx v5 topic-discovery network.
var DiscoveryV5Bootnodes = []string{ var DiscoveryV5Bootnodes = []string{

View File

@ -37,7 +37,7 @@ var (
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
} }
// TestnetChainConfig contains the chain parameters to run a node on the ropsten test network. // TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network.
TestnetChainConfig = &ChainConfig{ TestnetChainConfig = &ChainConfig{
ChainId: big.NewInt(3), ChainId: big.NewInt(3),
HomesteadBlock: big.NewInt(0), HomesteadBlock: big.NewInt(0),
@ -50,6 +50,22 @@ var (
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
} }
// RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network.
RinkebyChainConfig = &ChainConfig{
ChainId: big.NewInt(4),
HomesteadBlock: big.NewInt(1),
DAOForkBlock: nil,
DAOForkSupport: true,
EIP150Block: big.NewInt(2),
EIP150Hash: common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"),
EIP155Block: big.NewInt(3),
EIP158Block: big.NewInt(3),
Clique: &CliqueConfig{
Period: 15,
Epoch: 30000,
},
}
// AllProtocolChanges contains every protocol change (EIPs) // AllProtocolChanges contains every protocol change (EIPs)
// introduced and accepted by the Ethereum core developers. // introduced and accepted by the Ethereum core developers.
// TestChainConfig is like AllProtocolChanges but has chain ID 1. // TestChainConfig is like AllProtocolChanges but has chain ID 1.

View File

@ -23,7 +23,7 @@ import (
const ( const (
VersionMajor = 1 // Major version component of the current release VersionMajor = 1 // Major version component of the current release
VersionMinor = 6 // Minor version component of the current release VersionMinor = 6 // Minor version component of the current release
VersionPatch = 0 // Patch version component of the current release VersionPatch = 1 // Patch version component of the current release
VersionMeta = "stable" // Version metadata to append to the version string VersionMeta = "stable" // Version metadata to append to the version string
) )

View File

@ -27,6 +27,7 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -373,14 +374,14 @@ func (c *Client) EthSubscribe(ctx context.Context, channel interface{}, args ...
return nil, ErrNotificationsUnsupported return nil, ErrNotificationsUnsupported
} }
msg, err := c.newMessage(subscribeMethod, args...) msg, err := c.newMessage("eth"+subscribeMethodSuffix, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
op := &requestOp{ op := &requestOp{
ids: []json.RawMessage{msg.ID}, ids: []json.RawMessage{msg.ID},
resp: make(chan *jsonrpcMessage), resp: make(chan *jsonrpcMessage),
sub: newClientSubscription(c, chanVal), sub: newClientSubscription(c, "eth", chanVal),
} }
// Send the subscription request. // Send the subscription request.
@ -575,7 +576,7 @@ func (c *Client) closeRequestOps(err error) {
} }
func (c *Client) handleNotification(msg *jsonrpcMessage) { func (c *Client) handleNotification(msg *jsonrpcMessage) {
if msg.Method != notificationMethod { if !strings.HasSuffix(msg.Method, notificationMethodSuffix) {
log.Debug(fmt.Sprint("dropping non-subscription message: ", msg)) log.Debug(fmt.Sprint("dropping non-subscription message: ", msg))
return return
} }
@ -656,6 +657,7 @@ type ClientSubscription struct {
client *Client client *Client
etype reflect.Type etype reflect.Type
channel reflect.Value channel reflect.Value
namespace string
subid string subid string
in chan json.RawMessage in chan json.RawMessage
@ -665,9 +667,10 @@ type ClientSubscription struct {
err chan error err chan error
} }
func newClientSubscription(c *Client, channel reflect.Value) *ClientSubscription { func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription {
sub := &ClientSubscription{ sub := &ClientSubscription{
client: c, client: c,
namespace: namespace,
etype: channel.Type().Elem(), etype: channel.Type().Elem(),
channel: channel, channel: channel,
quit: make(chan struct{}), quit: make(chan struct{}),
@ -774,5 +777,5 @@ func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, e
func (sub *ClientSubscription) requestUnsubscribe() error { func (sub *ClientSubscription) requestUnsubscribe() error {
var result interface{} var result interface{}
return sub.client.Call(&result, unsubscribeMethod, sub.subid) return sub.client.Call(&result, sub.namespace+unsubscribeMethodSuffix, sub.subid)
} }

View File

@ -162,6 +162,11 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler { func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler {
// disable CORS support if user has not specified a custom CORS configuration
if len(allowedOrigins) == 0 {
return srv
}
c := cors.New(cors.Options{ c := cors.New(cors.Options{
AllowedOrigins: allowedOrigins, AllowedOrigins: allowedOrigins,
AllowedMethods: []string{"POST", "GET"}, AllowedMethods: []string{"POST", "GET"},

View File

@ -32,9 +32,9 @@ import (
const ( const (
jsonrpcVersion = "2.0" jsonrpcVersion = "2.0"
serviceMethodSeparator = "_" serviceMethodSeparator = "_"
subscribeMethod = "eth_subscribe" subscribeMethodSuffix = "_subscribe"
unsubscribeMethod = "eth_unsubscribe" unsubscribeMethodSuffix = "_unsubscribe"
notificationMethod = "eth_subscription" notificationMethodSuffix = "_subscription"
) )
type jsonRequest struct { type jsonRequest struct {
@ -164,7 +164,7 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error) {
} }
// subscribe are special, they will always use `subscribeMethod` as first param in the payload // subscribe are special, they will always use `subscribeMethod` as first param in the payload
if in.Method == subscribeMethod { if strings.HasSuffix(in.Method, subscribeMethodSuffix) {
reqs := []rpcRequest{{id: &in.Id, isPubSub: true}} reqs := []rpcRequest{{id: &in.Id, isPubSub: true}}
if len(in.Payload) > 0 { if len(in.Payload) > 0 {
// first param must be subscription name // first param must be subscription name
@ -174,17 +174,16 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error) {
return nil, false, &invalidRequestError{"Unable to parse subscription request"} return nil, false, &invalidRequestError{"Unable to parse subscription request"}
} }
// all subscriptions are made on the eth service reqs[0].service, reqs[0].method = strings.TrimSuffix(in.Method, subscribeMethodSuffix), subscribeMethod[0]
reqs[0].service, reqs[0].method = "eth", subscribeMethod[0]
reqs[0].params = in.Payload reqs[0].params = in.Payload
return reqs, false, nil return reqs, false, nil
} }
return nil, false, &invalidRequestError{"Unable to parse subscription request"} return nil, false, &invalidRequestError{"Unable to parse subscription request"}
} }
if in.Method == unsubscribeMethod { if strings.HasSuffix(in.Method, unsubscribeMethodSuffix) {
return []rpcRequest{{id: &in.Id, isPubSub: true, return []rpcRequest{{id: &in.Id, isPubSub: true,
method: unsubscribeMethod, params: in.Payload}}, false, nil method: in.Method, params: in.Payload}}, false, nil
} }
elems := strings.Split(in.Method, serviceMethodSeparator) elems := strings.Split(in.Method, serviceMethodSeparator)
@ -216,8 +215,8 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error)
id := &in[i].Id id := &in[i].Id
// subscribe are special, they will always use `subscribeMethod` as first param in the payload // subscribe are special, they will always use `subscriptionMethod` as first param in the payload
if r.Method == subscribeMethod { if strings.HasSuffix(r.Method, subscribeMethodSuffix) {
requests[i] = rpcRequest{id: id, isPubSub: true} requests[i] = rpcRequest{id: id, isPubSub: true}
if len(r.Payload) > 0 { if len(r.Payload) > 0 {
// first param must be subscription name // first param must be subscription name
@ -227,8 +226,7 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error)
return nil, false, &invalidRequestError{"Unable to parse subscription request"} return nil, false, &invalidRequestError{"Unable to parse subscription request"}
} }
// all subscriptions are made on the eth service requests[i].service, requests[i].method = strings.TrimSuffix(r.Method, subscribeMethodSuffix), subscribeMethod[0]
requests[i].service, requests[i].method = "eth", subscribeMethod[0]
requests[i].params = r.Payload requests[i].params = r.Payload
continue continue
} }
@ -236,8 +234,8 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error)
return nil, true, &invalidRequestError{"Unable to parse (un)subscribe request arguments"} return nil, true, &invalidRequestError{"Unable to parse (un)subscribe request arguments"}
} }
if r.Method == unsubscribeMethod { if strings.HasSuffix(r.Method, unsubscribeMethodSuffix) {
requests[i] = rpcRequest{id: id, isPubSub: true, method: unsubscribeMethod, params: r.Payload} requests[i] = rpcRequest{id: id, isPubSub: true, method: r.Method, params: r.Payload}
continue continue
} }
@ -325,13 +323,13 @@ func (c *jsonCodec) CreateErrorResponseWithInfo(id interface{}, err Error, info
} }
// CreateNotification will create a JSON-RPC notification with the given subscription id and event as params. // CreateNotification will create a JSON-RPC notification with the given subscription id and event as params.
func (c *jsonCodec) CreateNotification(subid string, event interface{}) interface{} { func (c *jsonCodec) CreateNotification(subid, namespace string, event interface{}) interface{} {
if isHexNum(reflect.TypeOf(event)) { if isHexNum(reflect.TypeOf(event)) {
return &jsonNotification{Version: jsonrpcVersion, Method: notificationMethod, return &jsonNotification{Version: jsonrpcVersion, Method: namespace + notificationMethodSuffix,
Params: jsonSubscription{Subscription: subid, Result: fmt.Sprintf(`%#x`, event)}} Params: jsonSubscription{Subscription: subid, Result: fmt.Sprintf(`%#x`, event)}}
} }
return &jsonNotification{Version: jsonrpcVersion, Method: notificationMethod, return &jsonNotification{Version: jsonrpcVersion, Method: namespace + notificationMethodSuffix,
Params: jsonSubscription{Subscription: subid, Result: event}} Params: jsonSubscription{Subscription: subid, Result: event}}
} }

View File

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"runtime" "runtime"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -96,32 +97,30 @@ func (s *Server) RegisterName(name string, rcvr interface{}) error {
return fmt.Errorf("%s is not exported", reflect.Indirect(rcvrVal).Type().Name()) return fmt.Errorf("%s is not exported", reflect.Indirect(rcvrVal).Type().Name())
} }
methods, subscriptions := suitableCallbacks(rcvrVal, svc.typ)
// already a previous service register under given sname, merge methods/subscriptions // already a previous service register under given sname, merge methods/subscriptions
if regsvc, present := s.services[name]; present { if regsvc, present := s.services[name]; present {
methods, subscriptions := suitableCallbacks(rcvrVal, svc.typ)
if len(methods) == 0 && len(subscriptions) == 0 { if len(methods) == 0 && len(subscriptions) == 0 {
return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr) return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
} }
for _, m := range methods { for _, m := range methods {
regsvc.callbacks[formatName(m.method.Name)] = m regsvc.callbacks[formatName(m.method.Name)] = m
} }
for _, s := range subscriptions { for _, s := range subscriptions {
regsvc.subscriptions[formatName(s.method.Name)] = s regsvc.subscriptions[formatName(s.method.Name)] = s
} }
return nil return nil
} }
svc.name = name svc.name = name
svc.callbacks, svc.subscriptions = suitableCallbacks(rcvrVal, svc.typ) svc.callbacks, svc.subscriptions = methods, subscriptions
if len(svc.callbacks) == 0 && len(svc.subscriptions) == 0 { if len(svc.callbacks) == 0 && len(svc.subscriptions) == 0 {
return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr) return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
} }
s.services[svc.name] = svc s.services[svc.name] = svc
return nil return nil
} }
@ -303,7 +302,7 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque
// active the subscription after the sub id was successfully sent to the client // active the subscription after the sub id was successfully sent to the client
activateSub := func() { activateSub := func() {
notifier, _ := NotifierFromContext(ctx) notifier, _ := NotifierFromContext(ctx)
notifier.activate(subid) notifier.activate(subid, req.svcname)
} }
return codec.CreateResponse(req.id, subid), activateSub return codec.CreateResponse(req.id, subid), activateSub
@ -383,7 +382,7 @@ func (s *Server) execBatch(ctx context.Context, codec ServerCodec, requests []*s
codec.Close() codec.Close()
} }
// when request holds one of more subscribe requests this allows these subscriptions to be actived // when request holds one of more subscribe requests this allows these subscriptions to be activated
for _, c := range callbacks { for _, c := range callbacks {
c() c()
} }
@ -410,7 +409,7 @@ func (s *Server) readRequest(codec ServerCodec) ([]*serverRequest, bool, Error)
continue continue
} }
if r.isPubSub && r.method == unsubscribeMethod { if r.isPubSub && strings.HasSuffix(r.method, unsubscribeMethodSuffix) {
requests[i] = &serverRequest{id: r.id, isUnsubscribe: true} requests[i] = &serverRequest{id: r.id, isUnsubscribe: true}
argTypes := []reflect.Type{reflect.TypeOf("")} // expect subscription id as first arg argTypes := []reflect.Type{reflect.TypeOf("")} // expect subscription id as first arg
if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil { if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil {
@ -439,7 +438,7 @@ func (s *Server) readRequest(codec ServerCodec) ([]*serverRequest, bool, Error)
} }
} }
} else { } else {
requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{subscribeMethod, r.method}} requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.method, r.method}}
} }
continue continue
} }

View File

@ -36,6 +36,7 @@ type ID string
// this subscription to wait for an unsubscribe request for the client, see Err(). // this subscription to wait for an unsubscribe request for the client, see Err().
type Subscription struct { type Subscription struct {
ID ID ID ID
namespace string
err chan error // closed on unsubscribe err chan error // closed on unsubscribe
} }
@ -78,7 +79,7 @@ func NotifierFromContext(ctx context.Context) (*Notifier, bool) {
// are dropped until the subscription is marked as active. This is done // are dropped until the subscription is marked as active. This is done
// by the RPC server after the subscription ID is send to the client. // by the RPC server after the subscription ID is send to the client.
func (n *Notifier) CreateSubscription() *Subscription { func (n *Notifier) CreateSubscription() *Subscription {
s := &Subscription{NewID(), make(chan error)} s := &Subscription{ID: NewID(), err: make(chan error)}
n.subMu.Lock() n.subMu.Lock()
n.inactive[s.ID] = s n.inactive[s.ID] = s
n.subMu.Unlock() n.subMu.Unlock()
@ -91,9 +92,9 @@ func (n *Notifier) Notify(id ID, data interface{}) error {
n.subMu.RLock() n.subMu.RLock()
defer n.subMu.RUnlock() defer n.subMu.RUnlock()
_, active := n.active[id] sub, active := n.active[id]
if active { if active {
notification := n.codec.CreateNotification(string(id), data) notification := n.codec.CreateNotification(string(id), sub.namespace, data)
if err := n.codec.Write(notification); err != nil { if err := n.codec.Write(notification); err != nil {
n.codec.Close() n.codec.Close()
return err return err
@ -124,10 +125,11 @@ func (n *Notifier) unsubscribe(id ID) error {
// notifications are dropped. This method is called by the RPC server after // notifications are dropped. This method is called by the RPC server after
// the subscription ID was sent to client. This prevents notifications being // the subscription ID was sent to client. This prevents notifications being
// send to the client before the subscription ID is send to the client. // send to the client before the subscription ID is send to the client.
func (n *Notifier) activate(id ID) { func (n *Notifier) activate(id ID, namespace string) {
n.subMu.Lock() n.subMu.Lock()
defer n.subMu.Unlock() defer n.subMu.Unlock()
if sub, found := n.inactive[id]; found { if sub, found := n.inactive[id]; found {
sub.namespace = namespace
n.active[id] = sub n.active[id] = sub
delete(n.inactive, id) delete(n.inactive, id)
} }

View File

@ -19,6 +19,7 @@ package rpc
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"sync" "sync"
"testing" "testing"
@ -162,3 +163,162 @@ func TestNotifications(t *testing.T) {
t.Error("unsubscribe callback not called after closing connection") t.Error("unsubscribe callback not called after closing connection")
} }
} }
func waitForMessages(t *testing.T, in *json.Decoder, successes chan<- jsonSuccessResponse,
failures chan<- jsonErrResponse, notifications chan<- jsonNotification) {
// read and parse server messages
for {
var rmsg json.RawMessage
if err := in.Decode(&rmsg); err != nil {
return
}
var responses []map[string]interface{}
if rmsg[0] == '[' {
if err := json.Unmarshal(rmsg, &responses); err != nil {
t.Fatalf("Received invalid message: %s", rmsg)
}
} else {
var msg map[string]interface{}
if err := json.Unmarshal(rmsg, &msg); err != nil {
t.Fatalf("Received invalid message: %s", rmsg)
}
responses = append(responses, msg)
}
for _, msg := range responses {
// determine what kind of msg was received and broadcast
// it to over the corresponding channel
if _, found := msg["result"]; found {
successes <- jsonSuccessResponse{
Version: msg["jsonrpc"].(string),
Id: msg["id"],
Result: msg["result"],
}
continue
}
if _, found := msg["error"]; found {
params := msg["params"].(map[string]interface{})
failures <- jsonErrResponse{
Version: msg["jsonrpc"].(string),
Id: msg["id"],
Error: jsonError{int(params["subscription"].(float64)), params["message"].(string), params["data"]},
}
continue
}
if _, found := msg["params"]; found {
params := msg["params"].(map[string]interface{})
notifications <- jsonNotification{
Version: msg["jsonrpc"].(string),
Method: msg["method"].(string),
Params: jsonSubscription{params["subscription"].(string), params["result"]},
}
continue
}
t.Fatalf("Received invalid message: %s", msg)
}
}
}
// TestSubscriptionMultipleNamespaces ensures that subscriptions can exists
// for multiple different namespaces.
func TestSubscriptionMultipleNamespaces(t *testing.T) {
var (
namespaces = []string{"eth", "shh", "bzz"}
server = NewServer()
service = NotificationTestService{}
clientConn, serverConn = net.Pipe()
out = json.NewEncoder(clientConn)
in = json.NewDecoder(clientConn)
successes = make(chan jsonSuccessResponse)
failures = make(chan jsonErrResponse)
notifications = make(chan jsonNotification)
)
// setup and start server
for _, namespace := range namespaces {
if err := server.RegisterName(namespace, &service); err != nil {
t.Fatalf("unable to register test service %v", err)
}
}
go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation|OptionSubscriptions)
defer server.Stop()
// wait for message and write them to the given channels
go waitForMessages(t, in, successes, failures, notifications)
// create subscriptions one by one
n := 3
for i, namespace := range namespaces {
request := map[string]interface{}{
"id": i,
"method": fmt.Sprintf("%s_subscribe", namespace),
"version": "2.0",
"params": []interface{}{"someSubscription", n, i},
}
if err := out.Encode(&request); err != nil {
t.Fatalf("Could not create subscription: %v", err)
}
}
// create all subscriptions in 1 batch
var requests []interface{}
for i, namespace := range namespaces {
requests = append(requests, map[string]interface{}{
"id": i,
"method": fmt.Sprintf("%s_subscribe", namespace),
"version": "2.0",
"params": []interface{}{"someSubscription", n, i},
})
}
if err := out.Encode(&requests); err != nil {
t.Fatalf("Could not create subscription in batch form: %v", err)
}
timeout := time.After(30 * time.Second)
subids := make(map[string]string, 2*len(namespaces))
count := make(map[string]int, 2*len(namespaces))
for {
done := true
for id, _ := range count {
if count, found := count[id]; !found || count < (2*n) {
done = false
}
}
if done && len(count) == len(namespaces) {
break
}
select {
case suc := <-successes: // subscription created
subids[namespaces[int(suc.Id.(float64))]] = suc.Result.(string)
case failure := <-failures:
t.Errorf("received error: %v", failure.Error)
case notification := <-notifications:
if cnt, found := count[notification.Params.Subscription]; found {
count[notification.Params.Subscription] = cnt + 1
} else {
count[notification.Params.Subscription] = 1
}
case <-timeout:
for _, namespace := range namespaces {
subid, found := subids[namespace]
if !found {
t.Errorf("Subscription for '%s' not created", namespace)
continue
}
if count, found := count[subid]; !found || count < n {
t.Errorf("Didn't receive all notifications (%d<%d) in time for namespace '%s'", count, n, namespace)
}
}
return
}
}
}

View File

@ -19,11 +19,11 @@ package rpc
import ( import (
"fmt" "fmt"
"math" "math"
"math/big"
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
"github.com/ethereum/go-ethereum/common/hexutil"
"gopkg.in/fatih/set.v0" "gopkg.in/fatih/set.v0"
) )
@ -104,35 +104,29 @@ type ServerCodec interface {
// Read next request // Read next request
ReadRequestHeaders() ([]rpcRequest, bool, Error) ReadRequestHeaders() ([]rpcRequest, bool, Error)
// Parse request argument to the given types // Parse request argument to the given types
ParseRequestArguments([]reflect.Type, interface{}) ([]reflect.Value, Error) ParseRequestArguments(argTypes []reflect.Type, params interface{}) ([]reflect.Value, Error)
// Assemble success response, expects response id and payload // Assemble success response, expects response id and payload
CreateResponse(interface{}, interface{}) interface{} CreateResponse(id interface{}, reply interface{}) interface{}
// Assemble error response, expects response id and error // Assemble error response, expects response id and error
CreateErrorResponse(interface{}, Error) interface{} CreateErrorResponse(id interface{}, err Error) interface{}
// Assemble error response with extra information about the error through info // Assemble error response with extra information about the error through info
CreateErrorResponseWithInfo(id interface{}, err Error, info interface{}) interface{} CreateErrorResponseWithInfo(id interface{}, err Error, info interface{}) interface{}
// Create notification response // Create notification response
CreateNotification(string, interface{}) interface{} CreateNotification(id, namespace string, event interface{}) interface{}
// Write msg to client. // Write msg to client.
Write(interface{}) error Write(msg interface{}) error
// Close underlying data stream // Close underlying data stream
Close() Close()
// Closed when underlying connection is closed // Closed when underlying connection is closed
Closed() <-chan interface{} Closed() <-chan interface{}
} }
var (
pendingBlockNumber = big.NewInt(-2)
latestBlockNumber = big.NewInt(-1)
earliestBlockNumber = big.NewInt(0)
maxBlockNumber = big.NewInt(math.MaxInt64)
)
type BlockNumber int64 type BlockNumber int64
const ( const (
PendingBlockNumber = BlockNumber(-2) PendingBlockNumber = BlockNumber(-2)
LatestBlockNumber = BlockNumber(-1) LatestBlockNumber = BlockNumber(-1)
EarliestBlockNumber = BlockNumber(0)
) )
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports: // UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
@ -143,45 +137,32 @@ const (
// - an out of range error when the given block number is either too little or too large // - an out of range error when the given block number is either too little or too large
func (bn *BlockNumber) UnmarshalJSON(data []byte) error { func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
input := strings.TrimSpace(string(data)) input := strings.TrimSpace(string(data))
if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' {
input = input[1 : len(input)-1] input = input[1 : len(input)-1]
} }
if len(input) == 0 { switch input {
*bn = BlockNumber(latestBlockNumber.Int64()) case "earliest":
*bn = EarliestBlockNumber
return nil
case "latest":
*bn = LatestBlockNumber
return nil
case "pending":
*bn = PendingBlockNumber
return nil return nil
} }
in := new(big.Int) blckNum, err := hexutil.DecodeUint64(input)
_, ok := in.SetString(input, 0) if err != nil {
return err
}
if blckNum > math.MaxInt64 {
return fmt.Errorf("Blocknumber too high")
}
if !ok { // test if user supplied string tag *bn = BlockNumber(blckNum)
strBlockNumber := input
if strBlockNumber == "latest" {
*bn = BlockNumber(latestBlockNumber.Int64())
return nil return nil
}
if strBlockNumber == "earliest" {
*bn = BlockNumber(earliestBlockNumber.Int64())
return nil
}
if strBlockNumber == "pending" {
*bn = BlockNumber(pendingBlockNumber.Int64())
return nil
}
return fmt.Errorf(`invalid blocknumber %s`, data)
}
if in.Cmp(earliestBlockNumber) >= 0 && in.Cmp(maxBlockNumber) <= 0 {
*bn = BlockNumber(in.Int64())
return nil
}
return fmt.Errorf("blocknumber not in range [%d, %d]", earliestBlockNumber, maxBlockNumber)
} }
func (bn BlockNumber) Int64() int64 { func (bn BlockNumber) Int64() int64 {

66
rpc/types_test.go Normal file
View File

@ -0,0 +1,66 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library 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.
//
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
"encoding/json"
"testing"
"github.com/ethereum/go-ethereum/common/math"
)
func TestBlockNumberJSONUnmarshal(t *testing.T) {
tests := []struct {
input string
mustFail bool
expected BlockNumber
}{
0: {`"0x"`, true, BlockNumber(0)},
1: {`"0x0"`, false, BlockNumber(0)},
2: {`"0X1"`, false, BlockNumber(1)},
3: {`"0x00"`, true, BlockNumber(0)},
4: {`"0x01"`, true, BlockNumber(0)},
5: {`"0x1"`, false, BlockNumber(1)},
6: {`"0x12"`, false, BlockNumber(18)},
7: {`"0x7fffffffffffffff"`, false, BlockNumber(math.MaxInt64)},
8: {`"0x8000000000000000"`, true, BlockNumber(0)},
9: {"0", true, BlockNumber(0)},
10: {`"ff"`, true, BlockNumber(0)},
11: {`"pending"`, false, PendingBlockNumber},
12: {`"latest"`, false, LatestBlockNumber},
13: {`"earliest"`, false, EarliestBlockNumber},
14: {`someString`, true, BlockNumber(0)},
15: {`""`, true, BlockNumber(0)},
16: {``, true, BlockNumber(0)},
}
for i, test := range tests {
var num BlockNumber
err := json.Unmarshal([]byte(test.input), &num)
if test.mustFail && err == nil {
t.Errorf("Test %d should fail", i)
continue
}
if !test.mustFail && err != nil {
t.Errorf("Test %d should pass but got err: %v", i, err)
continue
}
if num != test.expected {
t.Errorf("Test %d got unexpected value, want %d, got %d", i, test.expected, num)
}
}
}

View File

@ -16,49 +16,54 @@
package trie package trie
func compactEncode(hexSlice []byte) []byte { // Trie keys are dealt with in three distinct encodings:
//
// KEYBYTES encoding contains the actual key and nothing else. This encoding is the
// input to most API functions.
//
// HEX encoding contains one byte for each nibble of the key and an optional trailing
// 'terminator' byte of value 0x10 which indicates whether or not the node at the key
// contains a value. Hex key encoding is used for nodes loaded in memory because it's
// convenient to access.
//
// COMPACT encoding is defined by the Ethereum Yellow Paper (it's called "hex prefix
// encoding" there) and contains the bytes of the key and a flag. The high nibble of the
// first byte contains the flag; the lowest bit encoding the oddness of the length and
// the second-lowest encoding whether the node at the key is a value node. The low nibble
// of the first byte is zero in the case of an even number of nibbles and the first nibble
// in the case of an odd number. All remaining nibbles (now an even number) fit properly
// into the remaining bytes. Compact encoding is used for nodes stored on disk.
func hexToCompact(hex []byte) []byte {
terminator := byte(0) terminator := byte(0)
if hexSlice[len(hexSlice)-1] == 16 { if hasTerm(hex) {
terminator = 1 terminator = 1
hexSlice = hexSlice[:len(hexSlice)-1] hex = hex[:len(hex)-1]
} }
var ( buf := make([]byte, len(hex)/2+1)
odd = byte(len(hexSlice) % 2) buf[0] = terminator << 5 // the flag byte
buflen = len(hexSlice)/2 + 1 if len(hex)&1 == 1 {
bi, hi = 0, 0 // indices buf[0] |= 1 << 4 // odd flag
hs = byte(0) // shift: flips between 0 and 4 buf[0] |= hex[0] // first nibble is contained in the first byte
) hex = hex[1:]
if odd == 0 {
bi = 1
hs = 4
}
buf := make([]byte, buflen)
buf[0] = terminator<<5 | byte(odd)<<4
for bi < len(buf) && hi < len(hexSlice) {
buf[bi] |= hexSlice[hi] << hs
if hs == 0 {
bi++
}
hi, hs = hi+1, hs^(1<<2)
} }
decodeNibbles(hex, buf[1:])
return buf return buf
} }
func compactDecode(str []byte) []byte { func compactToHex(compact []byte) []byte {
base := compactHexDecode(str) base := keybytesToHex(compact)
base = base[:len(base)-1] base = base[:len(base)-1]
// apply terminator flag
if base[0] >= 2 { if base[0] >= 2 {
base = append(base, 16) base = append(base, 16)
} }
if base[0]%2 == 1 { // apply odd flag
base = base[1:] chop := 2 - base[0]&1
} else { return base[chop:]
base = base[2:]
}
return base
} }
func compactHexDecode(str []byte) []byte { func keybytesToHex(str []byte) []byte {
l := len(str)*2 + 1 l := len(str)*2 + 1
var nibbles = make([]byte, l) var nibbles = make([]byte, l)
for i, b := range str { for i, b := range str {
@ -69,35 +74,24 @@ func compactHexDecode(str []byte) []byte {
return nibbles return nibbles
} }
// compactHexEncode encodes a series of nibbles into a byte array // hexToKeybytes turns hex nibbles into key bytes.
func compactHexEncode(nibbles []byte) []byte { // This can only be used for keys of even length.
nl := len(nibbles) func hexToKeybytes(hex []byte) []byte {
if nl == 0 { if hasTerm(hex) {
return nil hex = hex[:len(hex)-1]
} }
if nibbles[nl-1] == 16 { if len(hex)&1 != 0 {
nl-- panic("can't convert hex key of odd length")
} }
l := (nl + 1) / 2 key := make([]byte, (len(hex)+1)/2)
var str = make([]byte, l) decodeNibbles(hex, key)
for i := range str { return key
b := nibbles[i*2] * 16
if nl > i*2 {
b += nibbles[i*2+1]
}
str[i] = b
}
return str
} }
func decodeCompact(key []byte) []byte { func decodeNibbles(nibbles []byte, bytes []byte) {
l := len(key) / 2 for bi, ni := 0, 0; ni < len(nibbles); bi, ni = bi+1, ni+2 {
var res = make([]byte, l) bytes[bi] = nibbles[ni]<<4 | nibbles[ni+1]
for i := 0; i < l; i++ {
v1, v0 := key[2*i], key[2*i+1]
res[i] = v1*16 + v0
} }
return res
} }
// prefixLen returns the length of the common prefix of a and b. // prefixLen returns the length of the common prefix of a and b.
@ -114,15 +108,7 @@ func prefixLen(a, b []byte) int {
return i return i
} }
// hasTerm returns whether a hex key has the terminator flag.
func hasTerm(s []byte) bool { func hasTerm(s []byte) bool {
return s[len(s)-1] == 16 return len(s) > 0 && s[len(s)-1] == 16
}
func remTerm(s []byte) []byte {
if hasTerm(s) {
b := make([]byte, len(s)-1)
copy(b, s)
return b
}
return s
} }

View File

@ -17,113 +17,88 @@
package trie package trie
import ( import (
"encoding/hex" "bytes"
"testing" "testing"
checker "gopkg.in/check.v1"
) )
func TestEncoding(t *testing.T) { checker.TestingT(t) } func TestHexCompact(t *testing.T) {
tests := []struct{ hex, compact []byte }{
type TrieEncodingSuite struct{} // empty keys, with and without terminator.
{hex: []byte{}, compact: []byte{0x00}},
var _ = checker.Suite(&TrieEncodingSuite{}) {hex: []byte{16}, compact: []byte{0x20}},
// odd length, no terminator
func (s *TrieEncodingSuite) TestCompactEncode(c *checker.C) { {hex: []byte{1, 2, 3, 4, 5}, compact: []byte{0x11, 0x23, 0x45}},
// even compact encode // even length, no terminator
test1 := []byte{1, 2, 3, 4, 5} {hex: []byte{0, 1, 2, 3, 4, 5}, compact: []byte{0x00, 0x01, 0x23, 0x45}},
res1 := compactEncode(test1) // odd length, terminator
c.Assert(res1, checker.DeepEquals, []byte("\x11\x23\x45")) {hex: []byte{15, 1, 12, 11, 8, 16 /*term*/}, compact: []byte{0x3f, 0x1c, 0xb8}},
// even length, terminator
// odd compact encode {hex: []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}, compact: []byte{0x20, 0x0f, 0x1c, 0xb8}},
test2 := []byte{0, 1, 2, 3, 4, 5} }
res2 := compactEncode(test2) for _, test := range tests {
c.Assert(res2, checker.DeepEquals, []byte("\x00\x01\x23\x45")) if c := hexToCompact(test.hex); !bytes.Equal(c, test.compact) {
t.Errorf("hexToCompact(%x) -> %x, want %x", test.hex, c, test.compact)
//odd terminated compact encode }
test3 := []byte{0, 15, 1, 12, 11, 8 /*term*/, 16} if h := compactToHex(test.compact); !bytes.Equal(h, test.hex) {
res3 := compactEncode(test3) t.Errorf("compactToHex(%x) -> %x, want %x", test.compact, h, test.hex)
c.Assert(res3, checker.DeepEquals, []byte("\x20\x0f\x1c\xb8")) }
// even terminated compact encode
test4 := []byte{15, 1, 12, 11, 8 /*term*/, 16}
res4 := compactEncode(test4)
c.Assert(res4, checker.DeepEquals, []byte("\x3f\x1c\xb8"))
}
func (s *TrieEncodingSuite) TestCompactHexDecode(c *checker.C) {
exp := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16}
res := compactHexDecode([]byte("verb"))
c.Assert(res, checker.DeepEquals, exp)
}
func (s *TrieEncodingSuite) TestCompactHexEncode(c *checker.C) {
exp := []byte("verb")
res := compactHexEncode([]byte{7, 6, 6, 5, 7, 2, 6, 2, 16})
c.Assert(res, checker.DeepEquals, exp)
}
func (s *TrieEncodingSuite) TestCompactDecode(c *checker.C) {
// odd compact decode
exp := []byte{1, 2, 3, 4, 5}
res := compactDecode([]byte("\x11\x23\x45"))
c.Assert(res, checker.DeepEquals, exp)
// even compact decode
exp = []byte{0, 1, 2, 3, 4, 5}
res = compactDecode([]byte("\x00\x01\x23\x45"))
c.Assert(res, checker.DeepEquals, exp)
// even terminated compact decode
exp = []byte{0, 15, 1, 12, 11, 8 /*term*/, 16}
res = compactDecode([]byte("\x20\x0f\x1c\xb8"))
c.Assert(res, checker.DeepEquals, exp)
// even terminated compact decode
exp = []byte{15, 1, 12, 11, 8 /*term*/, 16}
res = compactDecode([]byte("\x3f\x1c\xb8"))
c.Assert(res, checker.DeepEquals, exp)
}
func (s *TrieEncodingSuite) TestDecodeCompact(c *checker.C) {
exp, _ := hex.DecodeString("012345")
res := decodeCompact([]byte{0, 1, 2, 3, 4, 5})
c.Assert(res, checker.DeepEquals, exp)
exp, _ = hex.DecodeString("012345")
res = decodeCompact([]byte{0, 1, 2, 3, 4, 5, 16})
c.Assert(res, checker.DeepEquals, exp)
exp, _ = hex.DecodeString("abcdef")
res = decodeCompact([]byte{10, 11, 12, 13, 14, 15})
c.Assert(res, checker.DeepEquals, exp)
}
func BenchmarkCompactEncode(b *testing.B) {
testBytes := []byte{0, 15, 1, 12, 11, 8 /*term*/, 16}
for i := 0; i < b.N; i++ {
compactEncode(testBytes)
} }
} }
func BenchmarkCompactDecode(b *testing.B) { func TestHexKeybytes(t *testing.T) {
testBytes := []byte{0, 15, 1, 12, 11, 8 /*term*/, 16} tests := []struct{ key, hexIn, hexOut []byte }{
for i := 0; i < b.N; i++ { {key: []byte{}, hexIn: []byte{16}, hexOut: []byte{16}},
compactDecode(testBytes) {key: []byte{}, hexIn: []byte{}, hexOut: []byte{16}},
{
key: []byte{0x12, 0x34, 0x56},
hexIn: []byte{1, 2, 3, 4, 5, 6, 16},
hexOut: []byte{1, 2, 3, 4, 5, 6, 16},
},
{
key: []byte{0x12, 0x34, 0x5},
hexIn: []byte{1, 2, 3, 4, 0, 5, 16},
hexOut: []byte{1, 2, 3, 4, 0, 5, 16},
},
{
key: []byte{0x12, 0x34, 0x56},
hexIn: []byte{1, 2, 3, 4, 5, 6},
hexOut: []byte{1, 2, 3, 4, 5, 6, 16},
},
}
for _, test := range tests {
if h := keybytesToHex(test.key); !bytes.Equal(h, test.hexOut) {
t.Errorf("keybytesToHex(%x) -> %x, want %x", test.key, h, test.hexOut)
}
if k := hexToKeybytes(test.hexIn); !bytes.Equal(k, test.key) {
t.Errorf("hexToKeybytes(%x) -> %x, want %x", test.hexIn, k, test.key)
}
} }
} }
func BenchmarkCompactHexDecode(b *testing.B) { func BenchmarkHexToCompact(b *testing.B) {
testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}
for i := 0; i < b.N; i++ {
hexToCompact(testBytes)
}
}
func BenchmarkCompactToHex(b *testing.B) {
testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}
for i := 0; i < b.N; i++ {
compactToHex(testBytes)
}
}
func BenchmarkKeybytesToHex(b *testing.B) {
testBytes := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16} testBytes := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16}
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
compactHexDecode(testBytes) keybytesToHex(testBytes)
} }
} }
func BenchmarkDecodeCompact(b *testing.B) { func BenchmarkHexToKeybytes(b *testing.B) {
testBytes := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16} testBytes := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16}
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
decodeCompact(testBytes) hexToKeybytes(testBytes)
} }
} }

View File

@ -30,10 +30,6 @@ import (
// //
// RootHash is the original root of the trie that contains the node // RootHash is the original root of the trie that contains the node
// //
// Key is a binary-encoded key that contains the prefix that leads to the first
// missing node and optionally a suffix that hints on which further nodes should
// also be retrieved
//
// PrefixLen is the nibble length of the key prefix that leads from the root to // PrefixLen is the nibble length of the key prefix that leads from the root to
// the missing node // the missing node
// //
@ -42,7 +38,6 @@ import (
// such hints in the error message) // such hints in the error message)
type MissingNodeError struct { type MissingNodeError struct {
RootHash, NodeHash common.Hash RootHash, NodeHash common.Hash
Key []byte
PrefixLen, SuffixLen int PrefixLen, SuffixLen int
} }

View File

@ -105,7 +105,7 @@ func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, err
case *shortNode: case *shortNode:
// Hash the short node's child, caching the newly hashed subtree // Hash the short node's child, caching the newly hashed subtree
collapsed, cached := n.copy(), n.copy() collapsed, cached := n.copy(), n.copy()
collapsed.Key = compactEncode(n.Key) collapsed.Key = hexToCompact(n.Key)
cached.Key = common.CopyBytes(n.Key) cached.Key = common.CopyBytes(n.Key)
if _, ok := n.Val.(valueNode); !ok { if _, ok := n.Val.(valueNode); !ok {

View File

@ -19,9 +19,13 @@ package trie
import ( import (
"bytes" "bytes"
"container/heap" "container/heap"
"errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
var iteratorEnd = errors.New("end of iteration")
// Iterator is a key-value trie iterator that traverses a Trie. // Iterator is a key-value trie iterator that traverses a Trie.
type Iterator struct { type Iterator struct {
nodeIt NodeIterator nodeIt NodeIterator
@ -30,15 +34,8 @@ type Iterator struct {
Value []byte // Current data value on which the iterator is positioned on Value []byte // Current data value on which the iterator is positioned on
} }
// NewIterator creates a new key-value iterator. // NewIterator creates a new key-value iterator from a node iterator
func NewIterator(trie *Trie) *Iterator { func NewIterator(it NodeIterator) *Iterator {
return &Iterator{
nodeIt: NewNodeIterator(trie),
}
}
// FromNodeIterator creates a new key-value iterator from a node iterator
func NewIteratorFromNodeIterator(it NodeIterator) *Iterator {
return &Iterator{ return &Iterator{
nodeIt: it, nodeIt: it,
} }
@ -48,7 +45,7 @@ func NewIteratorFromNodeIterator(it NodeIterator) *Iterator {
func (it *Iterator) Next() bool { func (it *Iterator) Next() bool {
for it.nodeIt.Next(true) { for it.nodeIt.Next(true) {
if it.nodeIt.Leaf() { if it.nodeIt.Leaf() {
it.Key = decodeCompact(it.nodeIt.Path()) it.Key = hexToKeybytes(it.nodeIt.Path())
it.Value = it.nodeIt.LeafBlob() it.Value = it.nodeIt.LeafBlob()
return true return true
} }
@ -85,25 +82,24 @@ type nodeIteratorState struct {
hash common.Hash // Hash of the node being iterated (nil if not standalone) hash common.Hash // Hash of the node being iterated (nil if not standalone)
node node // Trie node being iterated node node // Trie node being iterated
parent common.Hash // Hash of the first full ancestor node (nil if current is the root) parent common.Hash // Hash of the first full ancestor node (nil if current is the root)
child int // Child to be processed next index int // Child to be processed next
pathlen int // Length of the path to this node pathlen int // Length of the path to this node
} }
type nodeIterator struct { type nodeIterator struct {
trie *Trie // Trie being iterated trie *Trie // Trie being iterated
stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state
err error // Failure set in case of an internal error in the iterator err error // Failure set in case of an internal error in the iterator
path []byte // Path to the current node path []byte // Path to the current node
} }
// NewNodeIterator creates an post-order trie iterator. func newNodeIterator(trie *Trie, start []byte) NodeIterator {
func NewNodeIterator(trie *Trie) NodeIterator {
if trie.Hash() == emptyState { if trie.Hash() == emptyState {
return new(nodeIterator) return new(nodeIterator)
} }
return &nodeIterator{trie: trie} it := &nodeIterator{trie: trie}
it.seek(start)
return it
} }
// Hash returns the hash of the current node // Hash returns the hash of the current node
@ -153,6 +149,9 @@ func (it *nodeIterator) Path() []byte {
// Error returns the error set in case of an internal error in the iterator // Error returns the error set in case of an internal error in the iterator
func (it *nodeIterator) Error() error { func (it *nodeIterator) Error() error {
if it.err == iteratorEnd {
return nil
}
return it.err return it.err
} }
@ -161,47 +160,54 @@ func (it *nodeIterator) Error() error {
// sets the Error field to the encountered failure. If `descend` is false, // sets the Error field to the encountered failure. If `descend` is false,
// skips iterating over any subnodes of the current node. // skips iterating over any subnodes of the current node.
func (it *nodeIterator) Next(descend bool) bool { func (it *nodeIterator) Next(descend bool) bool {
// If the iterator failed previously, don't do anything
if it.err != nil { if it.err != nil {
return false return false
} }
// Otherwise step forward with the iterator and report any errors // Otherwise step forward with the iterator and report any errors
if err := it.step(descend); err != nil { state, parentIndex, path, err := it.peek(descend)
if err != nil {
it.err = err it.err = err
return false return false
} }
return it.trie != nil it.push(state, parentIndex, path)
return true
} }
// step moves the iterator to the next node of the trie. func (it *nodeIterator) seek(prefix []byte) {
func (it *nodeIterator) step(descend bool) error { // The path we're looking for is the hex encoded key without terminator.
if it.trie == nil { key := keybytesToHex(prefix)
// Abort if we reached the end of the iteration key = key[:len(key)-1]
return nil // Move forward until we're just before the closest match to key.
for {
state, parentIndex, path, err := it.peek(bytes.HasPrefix(key, it.path))
if err != nil || bytes.Compare(path, key) >= 0 {
it.err = err
return
} }
it.push(state, parentIndex, path)
}
}
// peek creates the next state of the iterator.
func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, error) {
if len(it.stack) == 0 { if len(it.stack) == 0 {
// Initialize the iterator if we've just started. // Initialize the iterator if we've just started.
root := it.trie.Hash() root := it.trie.Hash()
state := &nodeIteratorState{node: it.trie.root, child: -1} state := &nodeIteratorState{node: it.trie.root, index: -1}
if root != emptyRoot { if root != emptyRoot {
state.hash = root state.hash = root
} }
it.stack = append(it.stack, state) return state, nil, nil, nil
return nil
} }
if !descend { if !descend {
// If we're skipping children, pop the current node first // If we're skipping children, pop the current node first
it.path = it.path[:it.stack[len(it.stack)-1].pathlen] it.pop()
it.stack = it.stack[:len(it.stack)-1]
} }
// Continue iteration to the next child // Continue iteration to the next child
outer:
for { for {
if len(it.stack) == 0 { if len(it.stack) == 0 {
it.trie = nil return nil, nil, nil, iteratorEnd
return nil
} }
parent := it.stack[len(it.stack)-1] parent := it.stack[len(it.stack)-1]
ancestor := parent.hash ancestor := parent.hash
@ -209,63 +215,76 @@ outer:
ancestor = parent.parent ancestor = parent.parent
} }
if node, ok := parent.node.(*fullNode); ok { if node, ok := parent.node.(*fullNode); ok {
// Full node, iterate over children // Full node, move to the first non-nil child.
for parent.child++; parent.child < len(node.Children); parent.child++ { for i := parent.index + 1; i < len(node.Children); i++ {
child := node.Children[parent.child] child := node.Children[i]
if child != nil { if child != nil {
hash, _ := child.cache() hash, _ := child.cache()
it.stack = append(it.stack, &nodeIteratorState{ state := &nodeIteratorState{
hash: common.BytesToHash(hash), hash: common.BytesToHash(hash),
node: child, node: child,
parent: ancestor, parent: ancestor,
child: -1, index: -1,
pathlen: len(it.path), pathlen: len(it.path),
}) }
it.path = append(it.path, byte(parent.child)) path := append(it.path, byte(i))
break outer parent.index = i - 1
return state, &parent.index, path, nil
} }
} }
} else if node, ok := parent.node.(*shortNode); ok { } else if node, ok := parent.node.(*shortNode); ok {
// Short node, return the pointer singleton child // Short node, return the pointer singleton child
if parent.child < 0 { if parent.index < 0 {
parent.child++
hash, _ := node.Val.cache() hash, _ := node.Val.cache()
it.stack = append(it.stack, &nodeIteratorState{ state := &nodeIteratorState{
hash: common.BytesToHash(hash), hash: common.BytesToHash(hash),
node: node.Val, node: node.Val,
parent: ancestor, parent: ancestor,
child: -1, index: -1,
pathlen: len(it.path), pathlen: len(it.path),
})
if hasTerm(node.Key) {
it.path = append(it.path, node.Key[:len(node.Key)-1]...)
} else {
it.path = append(it.path, node.Key...)
} }
break var path []byte
if hasTerm(node.Key) {
path = append(it.path, node.Key[:len(node.Key)-1]...)
} else {
path = append(it.path, node.Key...)
}
return state, &parent.index, path, nil
} }
} else if hash, ok := parent.node.(hashNode); ok { } else if hash, ok := parent.node.(hashNode); ok {
// Hash node, resolve the hash child from the database // Hash node, resolve the hash child from the database
if parent.child < 0 { if parent.index < 0 {
parent.child++
node, err := it.trie.resolveHash(hash, nil, nil) node, err := it.trie.resolveHash(hash, nil, nil)
if err != nil { if err != nil {
return err return it.stack[len(it.stack)-1], &parent.index, it.path, err
} }
it.stack = append(it.stack, &nodeIteratorState{ state := &nodeIteratorState{
hash: common.BytesToHash(hash), hash: common.BytesToHash(hash),
node: node, node: node,
parent: ancestor, parent: ancestor,
child: -1, index: -1,
pathlen: len(it.path), pathlen: len(it.path),
}) }
break return state, &parent.index, it.path, nil
} }
} }
// No more child nodes, move back up.
it.pop()
}
}
func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path []byte) {
it.path = path
it.stack = append(it.stack, state)
if parentIndex != nil {
*parentIndex += 1
}
}
func (it *nodeIterator) pop() {
parent := it.stack[len(it.stack)-1]
it.path = it.path[:parent.pathlen] it.path = it.path[:parent.pathlen]
it.stack = it.stack[:len(it.stack)-1] it.stack = it.stack[:len(it.stack)-1]
}
return nil
} }
func compareNodes(a, b NodeIterator) int { func compareNodes(a, b NodeIterator) int {

View File

@ -17,6 +17,8 @@
package trie package trie
import ( import (
"bytes"
"fmt"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -42,7 +44,7 @@ func TestIterator(t *testing.T) {
trie.Commit() trie.Commit()
found := make(map[string]string) found := make(map[string]string)
it := NewIterator(trie) it := NewIterator(trie.NodeIterator(nil))
for it.Next() { for it.Next() {
found[string(it.Key)] = string(it.Value) found[string(it.Key)] = string(it.Value)
} }
@ -72,7 +74,7 @@ func TestIteratorLargeData(t *testing.T) {
vals[string(value2.k)] = value2 vals[string(value2.k)] = value2
} }
it := NewIterator(trie) it := NewIterator(trie.NodeIterator(nil))
for it.Next() { for it.Next() {
vals[string(it.Key)].t = true vals[string(it.Key)].t = true
} }
@ -99,7 +101,7 @@ func TestNodeIteratorCoverage(t *testing.T) {
// Gather all the node hashes found by the iterator // Gather all the node hashes found by the iterator
hashes := make(map[common.Hash]struct{}) hashes := make(map[common.Hash]struct{})
for it := NewNodeIterator(trie); it.Next(true); { for it := trie.NodeIterator(nil); it.Next(true); {
if it.Hash() != (common.Hash{}) { if it.Hash() != (common.Hash{}) {
hashes[it.Hash()] = struct{}{} hashes[it.Hash()] = struct{}{}
} }
@ -117,18 +119,20 @@ func TestNodeIteratorCoverage(t *testing.T) {
} }
} }
var testdata1 = []struct{ k, v string }{ type kvs struct{ k, v string }
{"bar", "b"},
var testdata1 = []kvs{
{"barb", "ba"}, {"barb", "ba"},
{"bars", "bb"},
{"bard", "bc"}, {"bard", "bc"},
{"bars", "bb"},
{"bar", "b"},
{"fab", "z"}, {"fab", "z"},
{"foo", "a"},
{"food", "ab"}, {"food", "ab"},
{"foos", "aa"}, {"foos", "aa"},
{"foo", "a"},
} }
var testdata2 = []struct{ k, v string }{ var testdata2 = []kvs{
{"aardvark", "c"}, {"aardvark", "c"},
{"bar", "b"}, {"bar", "b"},
{"barb", "bd"}, {"barb", "bd"},
@ -140,6 +144,47 @@ var testdata2 = []struct{ k, v string }{
{"jars", "d"}, {"jars", "d"},
} }
func TestIteratorSeek(t *testing.T) {
trie := newEmpty()
for _, val := range testdata1 {
trie.Update([]byte(val.k), []byte(val.v))
}
// Seek to the middle.
it := NewIterator(trie.NodeIterator([]byte("fab")))
if err := checkIteratorOrder(testdata1[4:], it); err != nil {
t.Fatal(err)
}
// Seek to a non-existent key.
it = NewIterator(trie.NodeIterator([]byte("barc")))
if err := checkIteratorOrder(testdata1[1:], it); err != nil {
t.Fatal(err)
}
// Seek beyond the end.
it = NewIterator(trie.NodeIterator([]byte("z")))
if err := checkIteratorOrder(nil, it); err != nil {
t.Fatal(err)
}
}
func checkIteratorOrder(want []kvs, it *Iterator) error {
for it.Next() {
if len(want) == 0 {
return fmt.Errorf("didn't expect any more values, got key %q", it.Key)
}
if !bytes.Equal(it.Key, []byte(want[0].k)) {
return fmt.Errorf("wrong key: got %q, want %q", it.Key, want[0].k)
}
want = want[1:]
}
if len(want) > 0 {
return fmt.Errorf("iterator ended early, want key %q", want[0])
}
return nil
}
func TestDifferenceIterator(t *testing.T) { func TestDifferenceIterator(t *testing.T) {
triea := newEmpty() triea := newEmpty()
for _, val := range testdata1 { for _, val := range testdata1 {
@ -154,8 +199,8 @@ func TestDifferenceIterator(t *testing.T) {
trieb.Commit() trieb.Commit()
found := make(map[string]string) found := make(map[string]string)
di, _ := NewDifferenceIterator(NewNodeIterator(triea), NewNodeIterator(trieb)) di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil))
it := NewIteratorFromNodeIterator(di) it := NewIterator(di)
for it.Next() { for it.Next() {
found[string(it.Key)] = string(it.Value) found[string(it.Key)] = string(it.Value)
} }
@ -189,8 +234,8 @@ func TestUnionIterator(t *testing.T) {
} }
trieb.Commit() trieb.Commit()
di, _ := NewUnionIterator([]NodeIterator{NewNodeIterator(triea), NewNodeIterator(trieb)}) di, _ := NewUnionIterator([]NodeIterator{triea.NodeIterator(nil), trieb.NodeIterator(nil)})
it := NewIteratorFromNodeIterator(di) it := NewIterator(di)
all := []struct{ k, v string }{ all := []struct{ k, v string }{
{"aardvark", "c"}, {"aardvark", "c"},

View File

@ -139,8 +139,8 @@ func decodeShort(hash, buf, elems []byte, cachegen uint16) (node, error) {
return nil, err return nil, err
} }
flag := nodeFlag{hash: hash, gen: cachegen} flag := nodeFlag{hash: hash, gen: cachegen}
key := compactDecode(kbuf) key := compactToHex(kbuf)
if key[len(key)-1] == 16 { if hasTerm(key) {
// value node // value node
val, _, err := rlp.SplitString(rest) val, _, err := rlp.SplitString(rest)
if err != nil { if err != nil {

View File

@ -38,7 +38,7 @@ import (
// absence of the key. // absence of the key.
func (t *Trie) Prove(key []byte) []rlp.RawValue { func (t *Trie) Prove(key []byte) []rlp.RawValue {
// Collect all nodes on the path to key. // Collect all nodes on the path to key.
key = compactHexDecode(key) key = keybytesToHex(key)
nodes := []node{} nodes := []node{}
tn := t.root tn := t.root
for len(key) > 0 && tn != nil { for len(key) > 0 && tn != nil {
@ -89,7 +89,7 @@ func (t *Trie) Prove(key []byte) []rlp.RawValue {
// returns an error if the proof contains invalid trie nodes or the // returns an error if the proof contains invalid trie nodes or the
// wrong value. // wrong value.
func VerifyProof(rootHash common.Hash, key []byte, proof []rlp.RawValue) (value []byte, err error) { func VerifyProof(rootHash common.Hash, key []byte, proof []rlp.RawValue) (value []byte, err error) {
key = compactHexDecode(key) key = keybytesToHex(key)
sha := sha3.NewKeccak256() sha := sha3.NewKeccak256()
wantHash := rootHash.Bytes() wantHash := rootHash.Bytes()
for i, buf := range proof { for i, buf := range proof {

View File

@ -156,12 +156,10 @@ func (t *SecureTrie) Root() []byte {
return t.trie.Root() return t.trie.Root()
} }
func (t *SecureTrie) Iterator() *Iterator { // NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration
return t.trie.Iterator() // starts at the key after the given start key.
} func (t *SecureTrie) NodeIterator(start []byte) NodeIterator {
return t.trie.NodeIterator(start)
func (t *SecureTrie) NodeIterator() NodeIterator {
return NewNodeIterator(&t.trie)
} }
// CommitTo writes all nodes and the secure hash pre-images to the given database. // CommitTo writes all nodes and the secure hash pre-images to the given database.

View File

@ -80,7 +80,7 @@ func checkTrieConsistency(db Database, root common.Hash) error {
if err != nil { if err != nil {
return nil // // Consider a non existent state consistent return nil // // Consider a non existent state consistent
} }
it := NewNodeIterator(trie) it := trie.NodeIterator(nil)
for it.Next(true) { for it.Next(true) {
} }
return it.Error() return it.Error()

View File

@ -125,9 +125,10 @@ func New(root common.Hash, db Database) (*Trie, error) {
return trie, nil return trie, nil
} }
// Iterator returns an iterator over all mappings in the trie. // NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at
func (t *Trie) Iterator() *Iterator { // the key after the given start key.
return NewIterator(t) func (t *Trie) NodeIterator(start []byte) NodeIterator {
return newNodeIterator(t, start)
} }
// Get returns the value for key stored in the trie. // Get returns the value for key stored in the trie.
@ -144,7 +145,7 @@ func (t *Trie) Get(key []byte) []byte {
// The value bytes must not be modified by the caller. // The value bytes must not be modified by the caller.
// If a node was not found in the database, a MissingNodeError is returned. // If a node was not found in the database, a MissingNodeError is returned.
func (t *Trie) TryGet(key []byte) ([]byte, error) { func (t *Trie) TryGet(key []byte) ([]byte, error) {
key = compactHexDecode(key) key = keybytesToHex(key)
value, newroot, didResolve, err := t.tryGet(t.root, key, 0) value, newroot, didResolve, err := t.tryGet(t.root, key, 0)
if err == nil && didResolve { if err == nil && didResolve {
t.root = newroot t.root = newroot
@ -211,7 +212,7 @@ func (t *Trie) Update(key, value []byte) {
// //
// If a node was not found in the database, a MissingNodeError is returned. // If a node was not found in the database, a MissingNodeError is returned.
func (t *Trie) TryUpdate(key, value []byte) error { func (t *Trie) TryUpdate(key, value []byte) error {
k := compactHexDecode(key) k := keybytesToHex(key)
if len(value) != 0 { if len(value) != 0 {
_, n, err := t.insert(t.root, nil, k, valueNode(value)) _, n, err := t.insert(t.root, nil, k, valueNode(value))
if err != nil { if err != nil {
@ -307,7 +308,7 @@ func (t *Trie) Delete(key []byte) {
// TryDelete removes any existing value for key from the trie. // TryDelete removes any existing value for key from the trie.
// If a node was not found in the database, a MissingNodeError is returned. // If a node was not found in the database, a MissingNodeError is returned.
func (t *Trie) TryDelete(key []byte) error { func (t *Trie) TryDelete(key []byte) error {
k := compactHexDecode(key) k := keybytesToHex(key)
_, n, err := t.delete(t.root, nil, k) _, n, err := t.delete(t.root, nil, k)
if err != nil { if err != nil {
return err return err
@ -450,7 +451,6 @@ func (t *Trie) resolveHash(n hashNode, prefix, suffix []byte) (node, error) {
return nil, &MissingNodeError{ return nil, &MissingNodeError{
RootHash: t.originalRoot, RootHash: t.originalRoot,
NodeHash: common.BytesToHash(n), NodeHash: common.BytesToHash(n),
Key: compactHexEncode(append(prefix, suffix...)),
PrefixLen: len(prefix), PrefixLen: len(prefix),
SuffixLen: len(suffix), SuffixLen: len(suffix),
} }

View File

@ -439,7 +439,7 @@ func runRandTest(rt randTest) bool {
tr = newtr tr = newtr
case opItercheckhash: case opItercheckhash:
checktr, _ := New(common.Hash{}, nil) checktr, _ := New(common.Hash{}, nil)
it := tr.Iterator() it := NewIterator(tr.NodeIterator(nil))
for it.Next() { for it.Next() {
checktr.Update(it.Key, it.Value) checktr.Update(it.Key, it.Value)
} }

View File

@ -2,87 +2,64 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// This code was translated into a form compatible with 6a from the public
// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html
// +build amd64,!gccgo,!appengine // +build amd64,!gccgo,!appengine
// func cswap(inout *[5]uint64, v uint64) // func cswap(inout *[4][5]uint64, v uint64)
TEXT ·cswap(SB),7,$0 TEXT ·cswap(SB),7,$0
MOVQ inout+0(FP),DI MOVQ inout+0(FP),DI
MOVQ v+8(FP),SI MOVQ v+8(FP),SI
CMPQ SI,$1 SUBQ $1, SI
MOVQ 0(DI),SI NOTQ SI
MOVQ 80(DI),DX MOVQ SI, X15
MOVQ 8(DI),CX PSHUFD $0x44, X15, X15
MOVQ 88(DI),R8
MOVQ SI,R9 MOVOU 0(DI), X0
CMOVQEQ DX,SI MOVOU 16(DI), X2
CMOVQEQ R9,DX MOVOU 32(DI), X4
MOVQ CX,R9 MOVOU 48(DI), X6
CMOVQEQ R8,CX MOVOU 64(DI), X8
CMOVQEQ R9,R8 MOVOU 80(DI), X1
MOVQ SI,0(DI) MOVOU 96(DI), X3
MOVQ DX,80(DI) MOVOU 112(DI), X5
MOVQ CX,8(DI) MOVOU 128(DI), X7
MOVQ R8,88(DI) MOVOU 144(DI), X9
MOVQ 16(DI),SI
MOVQ 96(DI),DX MOVO X1, X10
MOVQ 24(DI),CX MOVO X3, X11
MOVQ 104(DI),R8 MOVO X5, X12
MOVQ SI,R9 MOVO X7, X13
CMOVQEQ DX,SI MOVO X9, X14
CMOVQEQ R9,DX
MOVQ CX,R9 PXOR X0, X10
CMOVQEQ R8,CX PXOR X2, X11
CMOVQEQ R9,R8 PXOR X4, X12
MOVQ SI,16(DI) PXOR X6, X13
MOVQ DX,96(DI) PXOR X8, X14
MOVQ CX,24(DI) PAND X15, X10
MOVQ R8,104(DI) PAND X15, X11
MOVQ 32(DI),SI PAND X15, X12
MOVQ 112(DI),DX PAND X15, X13
MOVQ 40(DI),CX PAND X15, X14
MOVQ 120(DI),R8 PXOR X10, X0
MOVQ SI,R9 PXOR X10, X1
CMOVQEQ DX,SI PXOR X11, X2
CMOVQEQ R9,DX PXOR X11, X3
MOVQ CX,R9 PXOR X12, X4
CMOVQEQ R8,CX PXOR X12, X5
CMOVQEQ R9,R8 PXOR X13, X6
MOVQ SI,32(DI) PXOR X13, X7
MOVQ DX,112(DI) PXOR X14, X8
MOVQ CX,40(DI) PXOR X14, X9
MOVQ R8,120(DI)
MOVQ 48(DI),SI MOVOU X0, 0(DI)
MOVQ 128(DI),DX MOVOU X2, 16(DI)
MOVQ 56(DI),CX MOVOU X4, 32(DI)
MOVQ 136(DI),R8 MOVOU X6, 48(DI)
MOVQ SI,R9 MOVOU X8, 64(DI)
CMOVQEQ DX,SI MOVOU X1, 80(DI)
CMOVQEQ R9,DX MOVOU X3, 96(DI)
MOVQ CX,R9 MOVOU X5, 112(DI)
CMOVQEQ R8,CX MOVOU X7, 128(DI)
CMOVQEQ R9,R8 MOVOU X9, 144(DI)
MOVQ SI,48(DI)
MOVQ DX,128(DI)
MOVQ CX,56(DI)
MOVQ R8,136(DI)
MOVQ 64(DI),SI
MOVQ 144(DI),DX
MOVQ 72(DI),CX
MOVQ 152(DI),R8
MOVQ SI,R9
CMOVQEQ DX,SI
CMOVQEQ R9,DX
MOVQ CX,R9
CMOVQEQ R8,CX
CMOVQEQ R9,R8
MOVQ SI,64(DI)
MOVQ DX,144(DI)
MOVQ CX,72(DI)
MOVQ R8,152(DI)
MOVQ DI,AX
MOVQ SI,DX
RET RET

View File

@ -8,6 +8,10 @@
package curve25519 package curve25519
import (
"encoding/binary"
)
// This code is a port of the public domain, "ref10" implementation of // This code is a port of the public domain, "ref10" implementation of
// curve25519 from SUPERCOP 20130419 by D. J. Bernstein. // curve25519 from SUPERCOP 20130419 by D. J. Bernstein.
@ -50,17 +54,11 @@ func feCopy(dst, src *fieldElement) {
// //
// Preconditions: b in {0,1}. // Preconditions: b in {0,1}.
func feCSwap(f, g *fieldElement, b int32) { func feCSwap(f, g *fieldElement, b int32) {
var x fieldElement
b = -b b = -b
for i := range x {
x[i] = b & (f[i] ^ g[i])
}
for i := range f { for i := range f {
f[i] ^= x[i] t := b & (f[i] ^ g[i])
} f[i] ^= t
for i := range g { g[i] ^= t
g[i] ^= x[i]
} }
} }
@ -75,12 +73,7 @@ func load3(in []byte) int64 {
// load4 reads a 32-bit, little-endian value from in. // load4 reads a 32-bit, little-endian value from in.
func load4(in []byte) int64 { func load4(in []byte) int64 {
var r int64 return int64(binary.LittleEndian.Uint32(in))
r = int64(in[0])
r |= int64(in[1]) << 8
r |= int64(in[2]) << 16
r |= int64(in[3]) << 24
return r
} }
func feFromBytes(dst *fieldElement, src *[32]byte) { func feFromBytes(dst *fieldElement, src *[32]byte) {

View File

@ -268,7 +268,7 @@ type CertChecker struct {
// HostKeyFallback is called when CertChecker.CheckHostKey encounters a // HostKeyFallback is called when CertChecker.CheckHostKey encounters a
// public key that is not a certificate. It must implement host key // public key that is not a certificate. It must implement host key
// validation or else, if nil, all such keys are rejected. // validation or else, if nil, all such keys are rejected.
HostKeyFallback func(addr string, remote net.Addr, key PublicKey) error HostKeyFallback HostKeyCallback
// IsRevoked is called for each certificate so that revocation checking // IsRevoked is called for each certificate so that revocation checking
// can be implemented. It should return true if the given certificate // can be implemented. It should return true if the given certificate

View File

@ -5,6 +5,7 @@
package ssh package ssh
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -13,7 +14,7 @@ import (
) )
// Client implements a traditional SSH client that supports shells, // Client implements a traditional SSH client that supports shells,
// subprocesses, port forwarding and tunneled dialing. // subprocesses, TCP port/streamlocal forwarding and tunneled dialing.
type Client struct { type Client struct {
Conn Conn
@ -59,6 +60,7 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
conn.forwards.closeAll() conn.forwards.closeAll()
}() }()
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip")) go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-streamlocal@openssh.com"))
return conn return conn
} }
@ -68,6 +70,11 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) { func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
fullConf := *config fullConf := *config
fullConf.SetDefaults() fullConf.SetDefaults()
if fullConf.HostKeyCallback == nil {
c.Close()
return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback")
}
conn := &connection{ conn := &connection{
sshConn: sshConn{conn: c}, sshConn: sshConn{conn: c},
} }
@ -173,6 +180,13 @@ func Dial(network, addr string, config *ClientConfig) (*Client, error) {
return NewClient(c, chans, reqs), nil return NewClient(c, chans, reqs), nil
} }
// HostKeyCallback is the function type used for verifying server
// keys. A HostKeyCallback must return nil if the host key is OK, or
// an error to reject it. It receives the hostname as passed to Dial
// or NewClientConn. The remote address is the RemoteAddr of the
// net.Conn underlying the the SSH connection.
type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
// A ClientConfig structure is used to configure a Client. It must not be // A ClientConfig structure is used to configure a Client. It must not be
// modified after having been passed to an SSH function. // modified after having been passed to an SSH function.
type ClientConfig struct { type ClientConfig struct {
@ -188,10 +202,12 @@ type ClientConfig struct {
// be used during authentication. // be used during authentication.
Auth []AuthMethod Auth []AuthMethod
// HostKeyCallback, if not nil, is called during the cryptographic // HostKeyCallback is called during the cryptographic
// handshake to validate the server's host key. A nil HostKeyCallback // handshake to validate the server's host key. The client
// implies that all host keys are accepted. // configuration must supply this callback for the connection
HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error // to succeed. The functions InsecureIgnoreHostKey or
// FixedHostKey can be used for simplistic host key checks.
HostKeyCallback HostKeyCallback
// ClientVersion contains the version identification string that will // ClientVersion contains the version identification string that will
// be used for the connection. If empty, a reasonable default is used. // be used for the connection. If empty, a reasonable default is used.
@ -209,3 +225,33 @@ type ClientConfig struct {
// A Timeout of zero means no timeout. // A Timeout of zero means no timeout.
Timeout time.Duration Timeout time.Duration
} }
// InsecureIgnoreHostKey returns a function that can be used for
// ClientConfig.HostKeyCallback to accept any host key. It should
// not be used for production code.
func InsecureIgnoreHostKey() HostKeyCallback {
return func(hostname string, remote net.Addr, key PublicKey) error {
return nil
}
}
type fixedHostKey struct {
key PublicKey
}
func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error {
if f.key == nil {
return fmt.Errorf("ssh: required host key was nil")
}
if !bytes.Equal(key.Marshal(), f.key.Marshal()) {
return fmt.Errorf("ssh: host key mismatch")
}
return nil
}
// FixedHostKey returns a function for use in
// ClientConfig.HostKeyCallback to accept only a specific host key.
func FixedHostKey(key PublicKey) HostKeyCallback {
hk := &fixedHostKey{key}
return hk.check
}

View File

@ -179,31 +179,26 @@ func (cb publicKeyCallback) method() string {
} }
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
// Authentication is performed in two stages. The first stage sends an // Authentication is performed by sending an enquiry to test if a key is
// enquiry to test if each key is acceptable to the remote. The second // acceptable to the remote. If the key is acceptable, the client will
// stage attempts to authenticate with the valid keys obtained in the // attempt to authenticate with the valid key. If not the client will repeat
// first stage. // the process with the remaining keys.
signers, err := cb() signers, err := cb()
if err != nil { if err != nil {
return false, nil, err return false, nil, err
} }
var validKeys []Signer var methods []string
for _, signer := range signers { for _, signer := range signers {
if ok, err := validateKey(signer.PublicKey(), user, c); ok { ok, err := validateKey(signer.PublicKey(), user, c)
validKeys = append(validKeys, signer)
} else {
if err != nil { if err != nil {
return false, nil, err return false, nil, err
} }
} if !ok {
continue
} }
// methods that may continue if this auth is not successful.
var methods []string
for _, signer := range validKeys {
pub := signer.PublicKey() pub := signer.PublicKey()
pubKey := pub.Marshal() pubKey := pub.Marshal()
sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{ sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
User: user, User: user,
@ -236,13 +231,29 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
if err != nil { if err != nil {
return false, nil, err return false, nil, err
} }
if success {
// If authentication succeeds or the list of available methods does not
// contain the "publickey" method, do not attempt to authenticate with any
// other keys. According to RFC 4252 Section 7, the latter can occur when
// additional authentication methods are required.
if success || !containsMethod(methods, cb.method()) {
return success, methods, err return success, methods, err
} }
} }
return false, methods, nil return false, methods, nil
} }
func containsMethod(methods []string, method string) bool {
for _, m := range methods {
if m == method {
return true
}
}
return false
}
// validateKey validates the key provided is acceptable to the server. // validateKey validates the key provided is acceptable to the server.
func validateKey(key PublicKey, user string, c packetConn) (bool, error) { func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
pubKey := key.Marshal() pubKey := key.Marshal()

View File

@ -9,6 +9,7 @@ import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"io" "io"
"math"
"sync" "sync"
_ "crypto/sha1" _ "crypto/sha1"
@ -40,7 +41,7 @@ var supportedKexAlgos = []string{
kexAlgoDH14SHA1, kexAlgoDH1SHA1, kexAlgoDH14SHA1, kexAlgoDH1SHA1,
} }
// supportedKexAlgos specifies the supported host-key algorithms (i.e. methods // supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
// of authenticating servers) in preference order. // of authenticating servers) in preference order.
var supportedHostKeyAlgos = []string{ var supportedHostKeyAlgos = []string{
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
@ -186,7 +187,7 @@ type Config struct {
// The maximum number of bytes sent or received after which a // The maximum number of bytes sent or received after which a
// new key is negotiated. It must be at least 256. If // new key is negotiated. It must be at least 256. If
// unspecified, 1 gigabyte is used. // unspecified, a size suitable for the chosen cipher is used.
RekeyThreshold uint64 RekeyThreshold uint64
// The allowed key exchanges algorithms. If unspecified then a // The allowed key exchanges algorithms. If unspecified then a
@ -230,11 +231,12 @@ func (c *Config) SetDefaults() {
} }
if c.RekeyThreshold == 0 { if c.RekeyThreshold == 0 {
// RFC 4253, section 9 suggests rekeying after 1G. // cipher specific default
c.RekeyThreshold = 1 << 30 } else if c.RekeyThreshold < minRekeyThreshold {
}
if c.RekeyThreshold < minRekeyThreshold {
c.RekeyThreshold = minRekeyThreshold c.RekeyThreshold = minRekeyThreshold
} else if c.RekeyThreshold >= math.MaxInt64 {
// Avoid weirdness if somebody uses -1 as a threshold.
c.RekeyThreshold = math.MaxInt64
} }
} }

View File

@ -14,5 +14,8 @@ others.
References: References:
[PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD [PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1 [SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
This package does not fall under the stability promise of the Go language itself,
so its API may be changed when pressing needs arise.
*/ */
package ssh // import "golang.org/x/crypto/ssh" package ssh // import "golang.org/x/crypto/ssh"

View File

@ -74,7 +74,7 @@ type handshakeTransport struct {
startKex chan *pendingKex startKex chan *pendingKex
// data for host key checking // data for host key checking
hostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error hostKeyCallback HostKeyCallback
dialAddress string dialAddress string
remoteAddr net.Addr remoteAddr net.Addr
@ -107,6 +107,8 @@ func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion,
config: config, config: config,
} }
t.resetReadThresholds()
t.resetWriteThresholds()
// We always start with a mandatory key exchange. // We always start with a mandatory key exchange.
t.requestKex <- struct{}{} t.requestKex <- struct{}{}
@ -237,6 +239,17 @@ func (t *handshakeTransport) requestKeyExchange() {
} }
} }
func (t *handshakeTransport) resetWriteThresholds() {
t.writePacketsLeft = packetRekeyThreshold
if t.config.RekeyThreshold > 0 {
t.writeBytesLeft = int64(t.config.RekeyThreshold)
} else if t.algorithms != nil {
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
} else {
t.writeBytesLeft = 1 << 30
}
}
func (t *handshakeTransport) kexLoop() { func (t *handshakeTransport) kexLoop() {
write: write:
@ -285,12 +298,8 @@ write:
t.writeError = err t.writeError = err
t.sentInitPacket = nil t.sentInitPacket = nil
t.sentInitMsg = nil t.sentInitMsg = nil
t.writePacketsLeft = packetRekeyThreshold
if t.config.RekeyThreshold > 0 { t.resetWriteThresholds()
t.writeBytesLeft = int64(t.config.RekeyThreshold)
} else if t.algorithms != nil {
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
}
// we have completed the key exchange. Since the // we have completed the key exchange. Since the
// reader is still blocked, it is safe to clear out // reader is still blocked, it is safe to clear out
@ -344,6 +353,17 @@ write:
// key exchange itself. // key exchange itself.
const packetRekeyThreshold = (1 << 31) const packetRekeyThreshold = (1 << 31)
func (t *handshakeTransport) resetReadThresholds() {
t.readPacketsLeft = packetRekeyThreshold
if t.config.RekeyThreshold > 0 {
t.readBytesLeft = int64(t.config.RekeyThreshold)
} else if t.algorithms != nil {
t.readBytesLeft = t.algorithms.r.rekeyBytes()
} else {
t.readBytesLeft = 1 << 30
}
}
func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) { func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) {
p, err := t.conn.readPacket() p, err := t.conn.readPacket()
if err != nil { if err != nil {
@ -391,12 +411,7 @@ func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) {
return nil, err return nil, err
} }
t.readPacketsLeft = packetRekeyThreshold t.resetReadThresholds()
if t.config.RekeyThreshold > 0 {
t.readBytesLeft = int64(t.config.RekeyThreshold)
} else {
t.readBytesLeft = t.algorithms.r.rekeyBytes()
}
// By default, a key exchange is hidden from higher layers by // By default, a key exchange is hidden from higher layers by
// translating it into msgIgnore. // translating it into msgIgnore.
@ -574,7 +589,9 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
} }
result.SessionID = t.sessionID result.SessionID = t.sessionID
t.conn.prepareKeyChange(t.algorithms, result) if err := t.conn.prepareKeyChange(t.algorithms, result); err != nil {
return err
}
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil { if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
return err return err
} }
@ -614,12 +631,10 @@ func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *
return nil, err return nil, err
} }
if t.hostKeyCallback != nil {
err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey) err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
return result, nil return result, nil
} }

View File

@ -824,7 +824,7 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
// Implemented based on the documentation at // Implemented based on the documentation at
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) { func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) {
magic := append([]byte("openssh-key-v1"), 0) magic := append([]byte("openssh-key-v1"), 0)
if !bytes.Equal(magic, key[0:len(magic)]) { if !bytes.Equal(magic, key[0:len(magic)]) {
return nil, errors.New("ssh: invalid openssh private key format") return nil, errors.New("ssh: invalid openssh private key format")
@ -844,14 +844,15 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
return nil, err return nil, err
} }
if w.KdfName != "none" || w.CipherName != "none" {
return nil, errors.New("ssh: cannot decode encrypted private keys")
}
pk1 := struct { pk1 := struct {
Check1 uint32 Check1 uint32
Check2 uint32 Check2 uint32
Keytype string Keytype string
Pub []byte Rest []byte `ssh:"rest"`
Priv []byte
Comment string
Pad []byte `ssh:"rest"`
}{} }{}
if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil { if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil {
@ -862,24 +863,75 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
return nil, errors.New("ssh: checkint mismatch") return nil, errors.New("ssh: checkint mismatch")
} }
// we only handle ed25519 keys currently // we only handle ed25519 and rsa keys currently
if pk1.Keytype != KeyAlgoED25519 { switch pk1.Keytype {
return nil, errors.New("ssh: unhandled key type") case KeyAlgoRSA:
// https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773
key := struct {
N *big.Int
E *big.Int
D *big.Int
Iqmp *big.Int
P *big.Int
Q *big.Int
Comment string
Pad []byte `ssh:"rest"`
}{}
if err := Unmarshal(pk1.Rest, &key); err != nil {
return nil, err
} }
for i, b := range pk1.Pad { for i, b := range key.Pad {
if int(b) != i+1 { if int(b) != i+1 {
return nil, errors.New("ssh: padding not as expected") return nil, errors.New("ssh: padding not as expected")
} }
} }
if len(pk1.Priv) != ed25519.PrivateKeySize { pk := &rsa.PrivateKey{
PublicKey: rsa.PublicKey{
N: key.N,
E: int(key.E.Int64()),
},
D: key.D,
Primes: []*big.Int{key.P, key.Q},
}
if err := pk.Validate(); err != nil {
return nil, err
}
pk.Precompute()
return pk, nil
case KeyAlgoED25519:
key := struct {
Pub []byte
Priv []byte
Comment string
Pad []byte `ssh:"rest"`
}{}
if err := Unmarshal(pk1.Rest, &key); err != nil {
return nil, err
}
if len(key.Priv) != ed25519.PrivateKeySize {
return nil, errors.New("ssh: private key unexpected length") return nil, errors.New("ssh: private key unexpected length")
} }
for i, b := range key.Pad {
if int(b) != i+1 {
return nil, errors.New("ssh: padding not as expected")
}
}
pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize)) pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
copy(pk, pk1.Priv) copy(pk, key.Priv)
return &pk, nil return &pk, nil
default:
return nil, errors.New("ssh: unhandled key type")
}
} }
// FingerprintLegacyMD5 returns the user presentation of the key's // FingerprintLegacyMD5 returns the user presentation of the key's

View File

@ -45,6 +45,12 @@ type ServerConfig struct {
// authenticating. // authenticating.
NoClientAuth bool NoClientAuth bool
// MaxAuthTries specifies the maximum number of authentication attempts
// permitted per connection. If set to a negative number, the number of
// attempts are unlimited. If set to zero, the number of attempts are limited
// to 6.
MaxAuthTries int
// PasswordCallback, if non-nil, is called when a user // PasswordCallback, if non-nil, is called when a user
// attempts to authenticate using a password. // attempts to authenticate using a password.
PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error)
@ -141,6 +147,10 @@ type ServerConn struct {
// Request and NewChannel channels must be serviced, or the connection // Request and NewChannel channels must be serviced, or the connection
// will hang. // will hang.
func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) { func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
if config.MaxAuthTries == 0 {
config.MaxAuthTries = 6
}
fullConf := *config fullConf := *config
fullConf.SetDefaults() fullConf.SetDefaults()
s := &connection{ s := &connection{
@ -267,8 +277,23 @@ func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, err
var cache pubKeyCache var cache pubKeyCache
var perms *Permissions var perms *Permissions
authFailures := 0
userAuthLoop: userAuthLoop:
for { for {
if authFailures >= config.MaxAuthTries && config.MaxAuthTries > 0 {
discMsg := &disconnectMsg{
Reason: 2,
Message: "too many authentication failures",
}
if err := s.transport.writePacket(Marshal(discMsg)); err != nil {
return nil, err
}
return nil, discMsg
}
var userAuthReq userAuthRequestMsg var userAuthReq userAuthRequestMsg
if packet, err := s.transport.readPacket(); err != nil { if packet, err := s.transport.readPacket(); err != nil {
return nil, err return nil, err
@ -289,6 +314,11 @@ userAuthLoop:
if config.NoClientAuth { if config.NoClientAuth {
authErr = nil authErr = nil
} }
// allow initial attempt of 'none' without penalty
if authFailures == 0 {
authFailures--
}
case "password": case "password":
if config.PasswordCallback == nil { if config.PasswordCallback == nil {
authErr = errors.New("ssh: password auth not configured") authErr = errors.New("ssh: password auth not configured")
@ -360,6 +390,7 @@ userAuthLoop:
if isQuery { if isQuery {
// The client can query if the given public key // The client can query if the given public key
// would be okay. // would be okay.
if len(payload) > 0 { if len(payload) > 0 {
return nil, parseError(msgUserAuthRequest) return nil, parseError(msgUserAuthRequest)
} }
@ -409,6 +440,8 @@ userAuthLoop:
break userAuthLoop break userAuthLoop
} }
authFailures++
var failureMsg userAuthFailureMsg var failureMsg userAuthFailureMsg
if config.PasswordCallback != nil { if config.PasswordCallback != nil {
failureMsg.Methods = append(failureMsg.Methods, "password") failureMsg.Methods = append(failureMsg.Methods, "password")

115
vendor/golang.org/x/crypto/ssh/streamlocal.go generated vendored Normal file
View File

@ -0,0 +1,115 @@
package ssh
import (
"errors"
"io"
"net"
)
// streamLocalChannelOpenDirectMsg is a struct used for SSH_MSG_CHANNEL_OPEN message
// with "direct-streamlocal@openssh.com" string.
//
// See openssh-portable/PROTOCOL, section 2.4. connection: Unix domain socket forwarding
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL#L235
type streamLocalChannelOpenDirectMsg struct {
socketPath string
reserved0 string
reserved1 uint32
}
// forwardedStreamLocalPayload is a struct used for SSH_MSG_CHANNEL_OPEN message
// with "forwarded-streamlocal@openssh.com" string.
type forwardedStreamLocalPayload struct {
SocketPath string
Reserved0 string
}
// streamLocalChannelForwardMsg is a struct used for SSH2_MSG_GLOBAL_REQUEST message
// with "streamlocal-forward@openssh.com"/"cancel-streamlocal-forward@openssh.com" string.
type streamLocalChannelForwardMsg struct {
socketPath string
}
// ListenUnix is similar to ListenTCP but uses a Unix domain socket.
func (c *Client) ListenUnix(socketPath string) (net.Listener, error) {
m := streamLocalChannelForwardMsg{
socketPath,
}
// send message
ok, _, err := c.SendRequest("streamlocal-forward@openssh.com", true, Marshal(&m))
if err != nil {
return nil, err
}
if !ok {
return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer")
}
ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"})
return &unixListener{socketPath, c, ch}, nil
}
func (c *Client) dialStreamLocal(socketPath string) (Channel, error) {
msg := streamLocalChannelOpenDirectMsg{
socketPath: socketPath,
}
ch, in, err := c.OpenChannel("direct-streamlocal@openssh.com", Marshal(&msg))
if err != nil {
return nil, err
}
go DiscardRequests(in)
return ch, err
}
type unixListener struct {
socketPath string
conn *Client
in <-chan forward
}
// Accept waits for and returns the next connection to the listener.
func (l *unixListener) Accept() (net.Conn, error) {
s, ok := <-l.in
if !ok {
return nil, io.EOF
}
ch, incoming, err := s.newCh.Accept()
if err != nil {
return nil, err
}
go DiscardRequests(incoming)
return &chanConn{
Channel: ch,
laddr: &net.UnixAddr{
Name: l.socketPath,
Net: "unix",
},
raddr: &net.UnixAddr{
Name: "@",
Net: "unix",
},
}, nil
}
// Close closes the listener.
func (l *unixListener) Close() error {
// this also closes the listener.
l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"})
m := streamLocalChannelForwardMsg{
l.socketPath,
}
ok, _, err := l.conn.SendRequest("cancel-streamlocal-forward@openssh.com", true, Marshal(&m))
if err == nil && !ok {
err = errors.New("ssh: cancel-streamlocal-forward@openssh.com failed")
}
return err
}
// Addr returns the listener's network address.
func (l *unixListener) Addr() net.Addr {
return &net.UnixAddr{
Name: l.socketPath,
Net: "unix",
}
}

View File

@ -20,12 +20,20 @@ import (
// addr. Incoming connections will be available by calling Accept on // addr. Incoming connections will be available by calling Accept on
// the returned net.Listener. The listener must be serviced, or the // the returned net.Listener. The listener must be serviced, or the
// SSH connection may hang. // SSH connection may hang.
// N must be "tcp", "tcp4", "tcp6", or "unix".
func (c *Client) Listen(n, addr string) (net.Listener, error) { func (c *Client) Listen(n, addr string) (net.Listener, error) {
switch n {
case "tcp", "tcp4", "tcp6":
laddr, err := net.ResolveTCPAddr(n, addr) laddr, err := net.ResolveTCPAddr(n, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.ListenTCP(laddr) return c.ListenTCP(laddr)
case "unix":
return c.ListenUnix(addr)
default:
return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
}
} }
// Automatic port allocation is broken with OpenSSH before 6.0. See // Automatic port allocation is broken with OpenSSH before 6.0. See
@ -116,7 +124,7 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
} }
// Register this forward, using the port number we obtained. // Register this forward, using the port number we obtained.
ch := c.forwards.add(*laddr) ch := c.forwards.add(laddr)
return &tcpListener{laddr, c, ch}, nil return &tcpListener{laddr, c, ch}, nil
} }
@ -131,7 +139,7 @@ type forwardList struct {
// forwardEntry represents an established mapping of a laddr on a // forwardEntry represents an established mapping of a laddr on a
// remote ssh server to a channel connected to a tcpListener. // remote ssh server to a channel connected to a tcpListener.
type forwardEntry struct { type forwardEntry struct {
laddr net.TCPAddr laddr net.Addr
c chan forward c chan forward
} }
@ -140,15 +148,15 @@ type forwardEntry struct {
// the original forward-request. // the original forward-request.
type forward struct { type forward struct {
newCh NewChannel // the ssh client channel underlying this forward newCh NewChannel // the ssh client channel underlying this forward
raddr *net.TCPAddr // the raddr of the incoming connection raddr net.Addr // the raddr of the incoming connection
} }
func (l *forwardList) add(addr net.TCPAddr) chan forward { func (l *forwardList) add(addr net.Addr) chan forward {
l.Lock() l.Lock()
defer l.Unlock() defer l.Unlock()
f := forwardEntry{ f := forwardEntry{
addr, laddr: addr,
make(chan forward, 1), c: make(chan forward, 1),
} }
l.entries = append(l.entries, f) l.entries = append(l.entries, f)
return f.c return f.c
@ -176,8 +184,15 @@ func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) {
func (l *forwardList) handleChannels(in <-chan NewChannel) { func (l *forwardList) handleChannels(in <-chan NewChannel) {
for ch := range in { for ch := range in {
var (
laddr net.Addr
raddr net.Addr
err error
)
switch channelType := ch.ChannelType(); channelType {
case "forwarded-tcpip":
var payload forwardedTCPPayload var payload forwardedTCPPayload
if err := Unmarshal(ch.ExtraData(), &payload); err != nil { if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error()) ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error())
continue continue
} }
@ -187,33 +202,51 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) {
// format. It is implied that this should be an IP // format. It is implied that this should be an IP
// address, as it would be impossible to connect to it // address, as it would be impossible to connect to it
// otherwise. // otherwise.
laddr, err := parseTCPAddr(payload.Addr, payload.Port) laddr, err = parseTCPAddr(payload.Addr, payload.Port)
if err != nil { if err != nil {
ch.Reject(ConnectionFailed, err.Error()) ch.Reject(ConnectionFailed, err.Error())
continue continue
} }
raddr, err := parseTCPAddr(payload.OriginAddr, payload.OriginPort) raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort)
if err != nil { if err != nil {
ch.Reject(ConnectionFailed, err.Error()) ch.Reject(ConnectionFailed, err.Error())
continue continue
} }
if ok := l.forward(*laddr, *raddr, ch); !ok { case "forwarded-streamlocal@openssh.com":
var payload forwardedStreamLocalPayload
if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error())
continue
}
laddr = &net.UnixAddr{
Name: payload.SocketPath,
Net: "unix",
}
raddr = &net.UnixAddr{
Name: "@",
Net: "unix",
}
default:
panic(fmt.Errorf("ssh: unknown channel type %s", channelType))
}
if ok := l.forward(laddr, raddr, ch); !ok {
// Section 7.2, implementations MUST reject spurious incoming // Section 7.2, implementations MUST reject spurious incoming
// connections. // connections.
ch.Reject(Prohibited, "no forward for address") ch.Reject(Prohibited, "no forward for address")
continue continue
} }
} }
} }
// remove removes the forward entry, and the channel feeding its // remove removes the forward entry, and the channel feeding its
// listener. // listener.
func (l *forwardList) remove(addr net.TCPAddr) { func (l *forwardList) remove(addr net.Addr) {
l.Lock() l.Lock()
defer l.Unlock() defer l.Unlock()
for i, f := range l.entries { for i, f := range l.entries {
if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port { if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() {
l.entries = append(l.entries[:i], l.entries[i+1:]...) l.entries = append(l.entries[:i], l.entries[i+1:]...)
close(f.c) close(f.c)
return return
@ -231,12 +264,12 @@ func (l *forwardList) closeAll() {
l.entries = nil l.entries = nil
} }
func (l *forwardList) forward(laddr, raddr net.TCPAddr, ch NewChannel) bool { func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
l.Lock() l.Lock()
defer l.Unlock() defer l.Unlock()
for _, f := range l.entries { for _, f := range l.entries {
if laddr.IP.Equal(f.laddr.IP) && laddr.Port == f.laddr.Port { if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() {
f.c <- forward{ch, &raddr} f.c <- forward{newCh: ch, raddr: raddr}
return true return true
} }
} }
@ -262,7 +295,7 @@ func (l *tcpListener) Accept() (net.Conn, error) {
} }
go DiscardRequests(incoming) go DiscardRequests(incoming)
return &tcpChanConn{ return &chanConn{
Channel: ch, Channel: ch,
laddr: l.laddr, laddr: l.laddr,
raddr: s.raddr, raddr: s.raddr,
@ -277,7 +310,7 @@ func (l *tcpListener) Close() error {
} }
// this also closes the listener. // this also closes the listener.
l.conn.forwards.remove(*l.laddr) l.conn.forwards.remove(l.laddr)
ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m)) ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m))
if err == nil && !ok { if err == nil && !ok {
err = errors.New("ssh: cancel-tcpip-forward failed") err = errors.New("ssh: cancel-tcpip-forward failed")
@ -293,6 +326,9 @@ func (l *tcpListener) Addr() net.Addr {
// Dial initiates a connection to the addr from the remote host. // Dial initiates a connection to the addr from the remote host.
// The resulting connection has a zero LocalAddr() and RemoteAddr(). // The resulting connection has a zero LocalAddr() and RemoteAddr().
func (c *Client) Dial(n, addr string) (net.Conn, error) { func (c *Client) Dial(n, addr string) (net.Conn, error) {
var ch Channel
switch n {
case "tcp", "tcp4", "tcp6":
// Parse the address into host and numeric port. // Parse the address into host and numeric port.
host, portString, err := net.SplitHostPort(addr) host, portString, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
@ -302,20 +338,40 @@ func (c *Client) Dial(n, addr string) (net.Conn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
ch, err = c.dial(net.IPv4zero.String(), 0, host, int(port))
if err != nil {
return nil, err
}
// Use a zero address for local and remote address. // Use a zero address for local and remote address.
zeroAddr := &net.TCPAddr{ zeroAddr := &net.TCPAddr{
IP: net.IPv4zero, IP: net.IPv4zero,
Port: 0, Port: 0,
} }
ch, err := c.dial(net.IPv4zero.String(), 0, host, int(port)) return &chanConn{
if err != nil {
return nil, err
}
return &tcpChanConn{
Channel: ch, Channel: ch,
laddr: zeroAddr, laddr: zeroAddr,
raddr: zeroAddr, raddr: zeroAddr,
}, nil }, nil
case "unix":
var err error
ch, err = c.dialStreamLocal(addr)
if err != nil {
return nil, err
}
return &chanConn{
Channel: ch,
laddr: &net.UnixAddr{
Name: "@",
Net: "unix",
},
raddr: &net.UnixAddr{
Name: addr,
Net: "unix",
},
}, nil
default:
return nil, fmt.Errorf("ssh: unsupported protocol: %s", n)
}
} }
// DialTCP connects to the remote address raddr on the network net, // DialTCP connects to the remote address raddr on the network net,
@ -332,7 +388,7 @@ func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &tcpChanConn{ return &chanConn{
Channel: ch, Channel: ch,
laddr: laddr, laddr: laddr,
raddr: raddr, raddr: raddr,
@ -366,26 +422,26 @@ type tcpChan struct {
Channel // the backing channel Channel // the backing channel
} }
// tcpChanConn fulfills the net.Conn interface without // chanConn fulfills the net.Conn interface without
// the tcpChan having to hold laddr or raddr directly. // the tcpChan having to hold laddr or raddr directly.
type tcpChanConn struct { type chanConn struct {
Channel Channel
laddr, raddr net.Addr laddr, raddr net.Addr
} }
// LocalAddr returns the local network address. // LocalAddr returns the local network address.
func (t *tcpChanConn) LocalAddr() net.Addr { func (t *chanConn) LocalAddr() net.Addr {
return t.laddr return t.laddr
} }
// RemoteAddr returns the remote network address. // RemoteAddr returns the remote network address.
func (t *tcpChanConn) RemoteAddr() net.Addr { func (t *chanConn) RemoteAddr() net.Addr {
return t.raddr return t.raddr
} }
// SetDeadline sets the read and write deadlines associated // SetDeadline sets the read and write deadlines associated
// with the connection. // with the connection.
func (t *tcpChanConn) SetDeadline(deadline time.Time) error { func (t *chanConn) SetDeadline(deadline time.Time) error {
if err := t.SetReadDeadline(deadline); err != nil { if err := t.SetReadDeadline(deadline); err != nil {
return err return err
} }
@ -396,12 +452,14 @@ func (t *tcpChanConn) SetDeadline(deadline time.Time) error {
// A zero value for t means Read will not time out. // A zero value for t means Read will not time out.
// After the deadline, the error from Read will implement net.Error // After the deadline, the error from Read will implement net.Error
// with Timeout() == true. // with Timeout() == true.
func (t *tcpChanConn) SetReadDeadline(deadline time.Time) error { func (t *chanConn) SetReadDeadline(deadline time.Time) error {
// for compatibility with previous version,
// the error message contains "tcpChan"
return errors.New("ssh: tcpChan: deadline not supported") return errors.New("ssh: tcpChan: deadline not supported")
} }
// SetWriteDeadline exists to satisfy the net.Conn interface // SetWriteDeadline exists to satisfy the net.Conn interface
// but is not implemented by this type. It always returns an error. // but is not implemented by this type. It always returns an error.
func (t *tcpChanConn) SetWriteDeadline(deadline time.Time) error { func (t *chanConn) SetWriteDeadline(deadline time.Time) error {
return errors.New("ssh: tcpChan: deadline not supported") return errors.New("ssh: tcpChan: deadline not supported")
} }

View File

@ -14,14 +14,12 @@ import (
// State contains the state of a terminal. // State contains the state of a terminal.
type State struct { type State struct {
termios syscall.Termios state *unix.Termios
} }
// IsTerminal returns true if the given file descriptor is a terminal. // IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool { func IsTerminal(fd int) bool {
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c _, err := unix.IoctlGetTermio(fd, unix.TCGETA)
var termio unix.Termio
err := unix.IoctlSetTermio(fd, unix.TCGETA, &termio)
return err == nil return err == nil
} }
@ -71,3 +69,60 @@ func ReadPassword(fd int) ([]byte, error) {
return ret, nil return ret, nil
} }
// MakeRaw puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
// see http://cr.illumos.org/~webrev/andy_js/1060/
func MakeRaw(fd int) (*State, error) {
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
if err != nil {
return nil, err
}
oldTermios := *oldTermiosPtr
newTermios := oldTermios
newTermios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
newTermios.Oflag &^= syscall.OPOST
newTermios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
newTermios.Cflag &^= syscall.CSIZE | syscall.PARENB
newTermios.Cflag |= syscall.CS8
newTermios.Cc[unix.VMIN] = 1
newTermios.Cc[unix.VTIME] = 0
if err := unix.IoctlSetTermios(fd, unix.TCSETS, &newTermios); err != nil {
return nil, err
}
return &State{
state: oldTermiosPtr,
}, nil
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd int, oldState *State) error {
return unix.IoctlSetTermios(fd, unix.TCSETS, oldState.state)
}
// GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal.
func GetState(fd int) (*State, error) {
oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS)
if err != nil {
return nil, err
}
return &State{
state: oldTermiosPtr,
}, nil
}
// GetSize returns the dimensions of the given terminal.
func GetSize(fd int) (width, height int, err error) {
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
if err != nil {
return 0, 0, err
}
return int(ws.Col), int(ws.Row), nil
}

66
vendor/vendor.json vendored
View File

@ -384,92 +384,92 @@
{ {
"checksumSHA1": "TT1rac6kpQp2vz24m5yDGUNQ/QQ=", "checksumSHA1": "TT1rac6kpQp2vz24m5yDGUNQ/QQ=",
"path": "golang.org/x/crypto/cast5", "path": "golang.org/x/crypto/cast5",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "C1KKOxFoW7/W/NFNpiXK+boguNo=", "checksumSHA1": "nAu0XmCeC6WnUySyI8R7w4cxAqU=",
"path": "golang.org/x/crypto/curve25519", "path": "golang.org/x/crypto/curve25519",
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-03-17T13:29:17Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=", "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=",
"path": "golang.org/x/crypto/ed25519", "path": "golang.org/x/crypto/ed25519",
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-03-17T13:29:17Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=", "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=",
"path": "golang.org/x/crypto/ed25519/internal/edwards25519", "path": "golang.org/x/crypto/ed25519/internal/edwards25519",
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-03-17T13:29:17Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "IIhFTrLlmlc6lEFSitqi4aw2lw0=", "checksumSHA1": "IIhFTrLlmlc6lEFSitqi4aw2lw0=",
"path": "golang.org/x/crypto/openpgp", "path": "golang.org/x/crypto/openpgp",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "olOKkhrdkYQHZ0lf1orrFQPQrv4=", "checksumSHA1": "olOKkhrdkYQHZ0lf1orrFQPQrv4=",
"path": "golang.org/x/crypto/openpgp/armor", "path": "golang.org/x/crypto/openpgp/armor",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "eo/KtdjieJQXH7Qy+faXFcF70ME=", "checksumSHA1": "eo/KtdjieJQXH7Qy+faXFcF70ME=",
"path": "golang.org/x/crypto/openpgp/elgamal", "path": "golang.org/x/crypto/openpgp/elgamal",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "rlxVSaGgqdAgwblsErxTxIfuGfg=", "checksumSHA1": "rlxVSaGgqdAgwblsErxTxIfuGfg=",
"path": "golang.org/x/crypto/openpgp/errors", "path": "golang.org/x/crypto/openpgp/errors",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "LWdaR8Q9yn6eBCcnGl0HvJRDUBE=", "checksumSHA1": "LWdaR8Q9yn6eBCcnGl0HvJRDUBE=",
"path": "golang.org/x/crypto/openpgp/packet", "path": "golang.org/x/crypto/openpgp/packet",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "s2qT4UwvzBSkzXuiuMkowif1Olw=", "checksumSHA1": "s2qT4UwvzBSkzXuiuMkowif1Olw=",
"path": "golang.org/x/crypto/openpgp/s2k", "path": "golang.org/x/crypto/openpgp/s2k",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "1MGpGDQqnUoRpv7VEcQrXOBydXE=", "checksumSHA1": "1MGpGDQqnUoRpv7VEcQrXOBydXE=",
"path": "golang.org/x/crypto/pbkdf2", "path": "golang.org/x/crypto/pbkdf2",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "y/oIaxq2d3WPizRZfVjo8RCRYTU=", "checksumSHA1": "y/oIaxq2d3WPizRZfVjo8RCRYTU=",
"path": "golang.org/x/crypto/ripemd160", "path": "golang.org/x/crypto/ripemd160",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "E8pDMGySfy5Mw+jzXOkOxo35bww=", "checksumSHA1": "E8pDMGySfy5Mw+jzXOkOxo35bww=",
"path": "golang.org/x/crypto/scrypt", "path": "golang.org/x/crypto/scrypt",
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-02-08T20:51:15Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "fsrFs762jlaILyqqQImS1GfvIvw=", "checksumSHA1": "8sVsMTphul+B0sI0qAv4TE1ZxUk=",
"path": "golang.org/x/crypto/ssh", "path": "golang.org/x/crypto/ssh",
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-03-17T13:29:17Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "xiderUuvye8Kpn7yX3niiJg32bE=", "checksumSHA1": "ZaU56svwLgiJD0y8JOB3+/mpYBA=",
"path": "golang.org/x/crypto/ssh/terminal", "path": "golang.org/x/crypto/ssh/terminal",
"revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e",
"revisionTime": "2017-03-17T13:29:17Z" "revisionTime": "2017-04-25T18:31:00Z"
}, },
{ {
"checksumSHA1": "Y+HGqEkYM15ir+J93MEaHdyFy0c=", "checksumSHA1": "Y+HGqEkYM15ir+J93MEaHdyFy0c=",

View File

@ -58,15 +58,19 @@ func TestDBKey(t *testing.T) {
} }
func generateEnvelope(t *testing.T) *whisper.Envelope { func generateEnvelope(t *testing.T) *whisper.Envelope {
h := crypto.Keccak256Hash([]byte("test sample data"))
params := &whisper.MessageParams{ params := &whisper.MessageParams{
KeySym: []byte("test key"), KeySym: h[:],
Topic: whisper.TopicType{}, Topic: whisper.TopicType{},
Payload: []byte("test payload"), Payload: []byte("test payload"),
PoW: powRequirement, PoW: powRequirement,
WorkTime: 2, WorkTime: 2,
} }
msg := whisper.NewSentMessage(params) msg, err := whisper.NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params) env, err := msg.Wrap(params)
if err != nil { if err != nil {
t.Fatalf("failed to wrap with seed %d: %s.", seed, err) t.Fatalf("failed to wrap with seed %d: %s.", seed, err)
@ -188,7 +192,10 @@ func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope {
Src: p.key, Src: p.key,
} }
msg := whisper.NewSentMessage(params) msg, err := whisper.NewSentMessage(params)
if err != nil {
t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
}
env, err := msg.Wrap(params) env, err := msg.Wrap(params)
if err != nil { if err != nil {
t.Fatalf("failed to wrap with seed %d: %s.", seed, err) t.Fatalf("failed to wrap with seed %d: %s.", seed, err)

Some files were not shown because too many files have changed in this diff Show More