Compare commits

...

82 Commits

Author SHA1 Message Date
0e703d92ac Merge branch 'release/0.9.26' 2015-05-28 18:21:14 +02:00
12b90600eb core: moved guards 2015-05-28 18:18:23 +02:00
2587b0ea62 Merge branch 'release/0.9.26' into develop 2015-05-28 18:01:54 +02:00
f082c1b895 Merge branch 'release/0.9.26' 2015-05-28 18:01:40 +02:00
d51d74eb55 cmd/geth: bump version v0.9.26 2015-05-28 17:43:05 +02:00
35806ccc1c build server fix 2015-05-28 17:18:13 +02:00
b25e8b7079 Merge pull request #1142 from obscuren/100-pct-block-prop
eth: 100% block propogation
2015-05-28 08:07:11 -07:00
e5d7627427 eth: 100% block propogation 2015-05-28 17:01:44 +02:00
a225ef9c13 Merge pull request #1140 from Gustav-Simonsson/fix_account_unlock_logging
Validate account length and avoid slicing in logging
2015-05-28 07:39:13 -07:00
b6e137b2b4 Merge pull request #1141 from obscuren/parallelisation-issue
Parallelisation issue
2015-05-28 07:37:37 -07:00
03178a77b6 Merge pull request #1132 from obscuren/log_optimisations
core: log optimisations
2015-05-28 07:35:07 -07:00
16038b4e67 core: added bad block 2015-05-28 16:26:19 +02:00
109f995684 core: log block hash during nonce error 2015-05-28 15:46:36 +02:00
75f5ae80fd core: partially removed nonce parallelisation and added merge error chk
Invalid forks are now detected

Current setup of parellelisation actually inserts bad blocks. This fix
is tmp until a better one is found
2015-05-28 15:35:50 +02:00
9138955ba5 Validate account length and avoid slicing in logging 2015-05-28 15:20:05 +02:00
4baa5ca963 Merge pull request #1137 from obscuren/web3_update
cmd/geth: updated web3
2015-05-28 04:40:14 -07:00
598e454d46 cmd/geth: updated web3 2015-05-28 13:24:09 +02:00
9f467c387a Merge pull request #1123 from fjl/lean-blockchain-commands
cmd/geth: leaner blockchain commands
2015-05-28 04:17:47 -07:00
8add3bb009 Merge pull request #1135 from karalabe/tempban-invalid-hashes
core, eth/downloader: expose the bad hashes, check in downloader
2015-05-28 04:13:08 -07:00
29b0480cfb core, eth/downloader: expose the bad hashes, check in downloader 2015-05-28 14:03:10 +03:00
e84bbcce3c cmd/geth: don't flush databases after import 2015-05-28 01:20:58 +02:00
e1fe75e3b6 cmd/utils: use constant for import batch size 2015-05-28 01:20:58 +02:00
a8bc2181c9 cmd/utils: skip batches with known blocks during import
This makes block importing restartable.
2015-05-28 01:20:58 +02:00
67effb94b6 cmd/geth, cmd/utils: make chain importing interruptible
Interrupting import with Ctrl-C could cause database corruption
because the signal wasn't handled. utils.ImportChain now checks
for a queued interrupt on every batch.
2015-05-28 01:09:26 +02:00
705beb4c25 cmd/utils: print errors only once if stdout and stderr are the same file 2015-05-28 01:09:26 +02:00
74706a0f02 cmd/geth, cmd/utils: rename utils.Get* -> utils.Make*
The renaming should make it clearer that these functions create a new
instance for every call. @obscuren suggested this renaming a while ago.
2015-05-28 01:09:26 +02:00
8e4512a5e7 p2p/nat: bump timeout in TestAutoDiscRace 2015-05-28 01:09:26 +02:00
651030c98d cmd/geth: move blockchain commands to chaincmd.go 2015-05-28 01:09:26 +02:00
62671c93c4 cmd/mist: use utils.SetupLogger 2015-05-28 01:09:26 +02:00
3b9808f23c cmd/geth, cmd/utils: don't use Ethereum for import, export and upgradedb
The blockchain commands don't need the full stack. With this change,
p2p, miner, downloader, etc are no longer started for blockchain
operations.
2015-05-28 01:09:26 +02:00
e3253b5d5e core: fixed an issue with storing receipts 2015-05-28 01:00:23 +02:00
27e0d2a973 Merge pull request #1128 from karalabe/hard-disconnect-trial
eth: hard disconnect if a peer is flaky
2015-05-27 15:28:39 -07:00
5479be9f64 Merge pull request #1129 from obscuren/database_cache_removal
ethdb, common: cache removal
2015-05-27 10:00:08 -07:00
903b95fffa Merge pull request #1124 from karalabe/detaied-download-progress
cmd/geth: expand admin.progress() to something meaningful
2015-05-27 09:05:46 -07:00
020006a8ed common, ethdb: removed caching and LastTD 2015-05-27 18:03:16 +02:00
5235e01b8d eth: hard disconnect if a peer is flaky 2015-05-27 18:58:51 +03:00
7595716816 core: adjust gas calculation 2015-05-27 17:01:28 +02:00
3f91ee4ff8 cmd/geth: expand admin.progress() to something meaningful 2015-05-27 16:46:46 +03:00
8951a03db3 Merge pull request #1121 from obscuren/miner_time_fix
Miner time fix
2015-05-27 04:51:42 -07:00
e13f413ef5 Merge pull request #1112 from fjl/fix-console-exit
cmd/geth: exit the console cleanly when interrupted
2015-05-27 04:40:29 -07:00
69f7a1da5a Merge pull request #1122 from Gustav-Simonsson/improve_validate_header_comments
Update ValidateHeader comments
2015-05-27 04:34:34 -07:00
912ae80350 miner: Added 5 blocks wait in prep for #1067 2015-05-27 13:33:52 +02:00
12650e16d3 core, miner: fixed miner time issue and removed future blocks
* Miner should no longer generate blocks with a time stamp less or equal
than it's parent.
* Future blocks are no longer processed and queued directly.
  Closes #1118
2015-05-27 13:30:52 +02:00
34729c365b Merge pull request #1067 from carver/deep-mining-log
miner: log locally mined blocks after they are 5-deep in the chain
2015-05-27 04:30:31 -07:00
bf5f0b1d0c Update ValidateHeader comments 2015-05-27 13:30:24 +02:00
4b29e5ba85 Merge pull request #1120 from Gustav-Simonsson/revert_gas_limit_change
Revert "core: block.gasLimit - parent.gasLimit <= parent.gasLimit / G…
2015-05-27 04:23:55 -07:00
14955bd454 Revert "core: block.gasLimit - parent.gasLimit <= parent.gasLimit / GasLimitBoundDivisor"
This reverts commit be2b0501b5.
2015-05-27 13:01:06 +02:00
de12183d38 deep-mining-log: need ring buffer to be one bigger for all-blocks-mined case 2015-05-26 18:55:52 -07:00
6019f1bb0a deep-mining-log: only track non-stale blocks
if you track stale blocks, then you quickly overflow your ring buffer in the local network case where you're mining every block and generating a lot of stales.
2015-05-26 18:54:56 -07:00
f5ce848cfe Merge branch 'release/0.9.25' into develop 2015-05-27 02:07:13 +02:00
70867904a0 Merge branch 'release/0.9.25' 2015-05-27 02:07:03 +02:00
2c532a7255 cmd/geth: bump version 0.9.25 2015-05-27 02:06:52 +02:00
aada35af9b Merge pull request #1114 from obscuren/develop
core: block.gasLimit - parent.gasLimit <= parent.gasLimit / GasLimitB…
2015-05-26 17:04:42 -07:00
be2b0501b5 core: block.gasLimit - parent.gasLimit <= parent.gasLimit / GasLimitBoundDivisor 2015-05-27 01:52:03 +02:00
3590591e67 Merge pull request #1113 from obscuren/develop
core: block database version update
2015-05-26 16:47:10 -07:00
222249e622 cmd/geth: Flush instead of close. This solves a nil ptr error 2015-05-27 01:38:41 +02:00
b2f2806055 cmd/geth, core: Updated DB version & seedhash debug method 2015-05-27 01:38:41 +02:00
9253fc337e cmd/geth: exit the console cleanly when interrupted
This fix applies mostly to unsupported terminals that do not trigger the
special interrupt handling in liner. Supported terminals were covered
because liner.Prompt returns an error if Ctrl-C is pressed.
2015-05-27 00:54:48 +02:00
612f01400f p2p/discover: bond with seed nodes too (runs only if findnode failed) 2015-05-26 23:30:41 +02:00
3630432dfb p2p/discovery: fix a cornercase loop if no seeds or bootnodes are known 2015-05-26 23:30:40 +02:00
f539ed1e66 p2p/discover: force refresh if the table is empty 2015-05-26 23:30:40 +02:00
5076170f34 p2p/discover: permit temporary bond failures for previously known nodes 2015-05-26 23:30:40 +02:00
6078aa08eb p2p/discover: watch find failures, evacuate on too many, rebond if failed 2015-05-26 23:30:40 +02:00
64174f196f p2p/discover: add support for counting findnode failures 2015-05-26 23:30:40 +02:00
6a674ffea5 Merge pull request #1108 from karalabe/fine-seeding
Fine tune seeder and p2p peer handling
2015-05-26 22:03:11 +02:00
b1f7b5d1f6 Merge pull request #1111 from obscuren/neg_tx_check
core: check negative value transactions. Closes #1109
2015-05-26 11:45:23 -07:00
c37389f19c core: check negative value transactions. Closes #1109 2015-05-26 20:38:26 +02:00
a55f408c10 Merge pull request #1090 from fjl/jsre-fixes
jsre: fixes for concurrent use, improved timer handling
2015-05-26 11:06:54 -07:00
39b1fe8e44 Merge pull request #1086 from debris/solidity_std
common/compiler: compile solidity contracts with std library
2015-05-26 10:58:09 -07:00
365eea9fba Merge pull request #1106 from karalabe/silence-useless-downloader-log
eth/downloader: silence "Added N blocks from..." if N == 0
2015-05-26 09:45:44 -07:00
4de8213887 cmd/geth: fix js console test for p2p server update 2015-05-26 19:35:31 +03:00
68898a4d6b p2p: fix Self() panic if listening is disabled 2015-05-26 19:16:05 +03:00
e1a0ee8fc5 cmd/geth, cmd/utils, eth, p2p: pass and honor a no discovery flag 2015-05-26 19:07:24 +03:00
278183c7e7 eth, p2p: start the p2p server even if maxpeers == 0 2015-05-26 17:49:37 +03:00
ceea1a7051 Merge branch 'develop' 2015-05-26 15:35:57 +02:00
eae0927597 core: prevent crash when last block fails 2015-05-26 15:35:51 +02:00
3083ec5e32 eth/downloader: silence "Added N blocks from..." if N == 0 2015-05-26 16:10:28 +03:00
e221a449e0 cmd/geth, jsre, rpc: run all JS code on the event loop
Some JSRE methods (PrettyPrint, ToVal) bypassed the event loop. All
calls to the JS VM are now wrapped. In order to make this somewhat more
foolproof, the otto VM is now a local variable inside the event loop.
2015-05-25 02:27:37 +02:00
c31f8e2bd7 compile solidity contracts with std library 2015-05-24 19:12:18 +02:00
f1ce5877ba do not export ring buffer struct 2015-05-23 12:09:59 -07:00
8a7fb5fd34 do not export constant for when to log a deep block you mined 2015-05-23 12:09:52 -07:00
ba295ec6fe Log locally mined blocks, after they are 5-deep in the chain
This helps determine which blocks are unlikely to end up as uncles

 * Store the 5 most recent locally mined block numbers
 * On every imported block, check if the 5-deep block num is in that store
 * Also confirm that the block is signed with miner's coinbase

Why not just check the coinbase? This log is useful if you're running
multiple miners and want to know if *this* miner is performing well.
2015-05-22 12:53:32 -07:00
36 changed files with 1151 additions and 2230 deletions

View File

@ -88,6 +88,7 @@ func (js *jsre) adminBindings() {
debug.Set("getBlockRlp", js.getBlockRlp)
debug.Set("setHead", js.setHead)
debug.Set("processBlock", js.debugBlock)
debug.Set("seedhash", js.seedHash)
// undocumented temporary
debug.Set("waitForBlocks", js.waitForBlocks)
}
@ -118,6 +119,27 @@ func (js *jsre) getBlock(call otto.FunctionCall) (*types.Block, error) {
return block, nil
}
func (js *jsre) seedHash(call otto.FunctionCall) otto.Value {
if len(call.ArgumentList) > 0 {
if call.Argument(0).IsNumber() {
num, _ := call.Argument(0).ToInteger()
hash, err := ethash.GetSeedHash(uint64(num))
if err != nil {
fmt.Println(err)
return otto.UndefinedValue()
}
v, _ := call.Otto.ToValue(fmt.Sprintf("0x%x", hash))
return v
} else {
fmt.Println("arg not a number")
}
} else {
fmt.Println("requires number argument")
}
return otto.UndefinedValue()
}
func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value {
txs := js.ethereum.TxPool().GetTransactions()
@ -144,7 +166,8 @@ func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value {
}
}
return js.re.ToVal(ltxs)
v, _ := call.Otto.ToValue(ltxs)
return v
}
func (js *jsre) resend(call otto.FunctionCall) otto.Value {
@ -175,7 +198,8 @@ func (js *jsre) resend(call otto.FunctionCall) otto.Value {
}
js.ethereum.TxPool().RemoveTransactions(types.Transactions{tx.tx})
return js.re.ToVal(ret)
v, _ := call.Otto.ToValue(ret)
return v
}
fmt.Println("first argument must be a transaction")
@ -198,12 +222,13 @@ func (js *jsre) sign(call otto.FunctionCall) otto.Value {
fmt.Println(err)
return otto.UndefinedValue()
}
v, err := js.xeth.Sign(signer, data, false)
signed, err := js.xeth.Sign(signer, data, false)
if err != nil {
fmt.Println(err)
return otto.UndefinedValue()
}
return js.re.ToVal(v)
v, _ := call.Otto.ToValue(signed)
return v
}
func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value {
@ -217,10 +242,11 @@ func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value {
vm.Debug = true
_, err = js.ethereum.BlockProcessor().RetryProcess(block)
if err != nil {
glog.Infoln(err)
fmt.Println(err)
}
vm.Debug = old
fmt.Println("ok")
return otto.UndefinedValue()
}
@ -236,9 +262,9 @@ func (js *jsre) setHead(call otto.FunctionCall) otto.Value {
}
func (js *jsre) downloadProgress(call otto.FunctionCall) otto.Value {
current, max := js.ethereum.Downloader().Stats()
return js.re.ToVal(fmt.Sprintf("%d/%d", current, max))
pending, cached := js.ethereum.Downloader().Stats()
v, _ := call.Otto.ToValue(map[string]interface{}{"pending": pending, "cached": cached})
return v
}
func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value {
@ -248,7 +274,8 @@ func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value {
return otto.UndefinedValue()
}
encoded, _ := rlp.EncodeToBytes(block)
return js.re.ToVal(fmt.Sprintf("%x", encoded))
v, _ := call.Otto.ToValue(fmt.Sprintf("%x", encoded))
return v
}
func (js *jsre) setExtra(call otto.FunctionCall) otto.Value {
@ -278,8 +305,9 @@ func (js *jsre) setGasPrice(call otto.FunctionCall) otto.Value {
return otto.UndefinedValue()
}
func (js *jsre) hashrate(otto.FunctionCall) otto.Value {
return js.re.ToVal(js.ethereum.Miner().HashRate())
func (js *jsre) hashrate(call otto.FunctionCall) otto.Value {
v, _ := call.Otto.ToValue(js.ethereum.Miner().HashRate())
return v
}
func (js *jsre) makeDAG(call otto.FunctionCall) otto.Value {
@ -495,15 +523,18 @@ func (js *jsre) newAccount(call otto.FunctionCall) otto.Value {
fmt.Printf("Could not create the account: %v", err)
return otto.UndefinedValue()
}
return js.re.ToVal(acct.Address.Hex())
v, _ := call.Otto.ToValue(acct.Address.Hex())
return v
}
func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value {
return js.re.ToVal(js.ethereum.NodeInfo())
v, _ := call.Otto.ToValue(js.ethereum.NodeInfo())
return v
}
func (js *jsre) peers(call otto.FunctionCall) otto.Value {
return js.re.ToVal(js.ethereum.PeersInfo())
v, _ := call.Otto.ToValue(js.ethereum.PeersInfo())
return v
}
func (js *jsre) importChain(call otto.FunctionCall) otto.Value {
@ -562,7 +593,8 @@ func (js *jsre) dumpBlock(call otto.FunctionCall) otto.Value {
statedb := state.New(block.Root(), js.ethereum.StateDb())
dump := statedb.RawDump()
return js.re.ToVal(dump)
v, _ := call.Otto.ToValue(dump)
return v
}
func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value {
@ -611,7 +643,8 @@ func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value {
return otto.UndefinedValue()
case height = <-wait:
}
return js.re.ToVal(height.Uint64())
v, _ := call.Otto.ToValue(height.Uint64())
return v
}
func (js *jsre) sleep(call otto.FunctionCall) otto.Value {
@ -704,8 +737,8 @@ func (js *jsre) register(call otto.FunctionCall) otto.Value {
return otto.UndefinedValue()
}
return js.re.ToVal(contenthash.Hex())
v, _ := call.Otto.ToValue(contenthash.Hex())
return v
}
func (js *jsre) registerUrl(call otto.FunctionCall) otto.Value {
@ -764,7 +797,8 @@ func (js *jsre) getContractInfo(call otto.FunctionCall) otto.Value {
fmt.Println(err)
return otto.UndefinedValue()
}
return js.re.ToVal(info)
v, _ := call.Otto.ToValue(info)
return v
}
func (js *jsre) startNatSpec(call otto.FunctionCall) otto.Value {

View File

@ -12,7 +12,7 @@ import (
"github.com/ethereum/go-ethereum/tests"
)
var blocktestCmd = cli.Command{
var blocktestCommand = cli.Command{
Action: runBlockTest,
Name: "blocktest",
Usage: `loads a block test file`,

159
cmd/geth/chaincmd.go Normal file
View File

@ -0,0 +1,159 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strconv"
"time"
"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/logger/glog"
)
var (
importCommand = cli.Command{
Action: importChain,
Name: "import",
Usage: `import a blockchain file`,
}
exportCommand = cli.Command{
Action: exportChain,
Name: "export",
Usage: `export blockchain into file`,
}
upgradedbCommand = cli.Command{
Action: upgradeDB,
Name: "upgradedb",
Usage: "upgrade chainblock database",
}
removedbCommand = cli.Command{
Action: removeDB,
Name: "removedb",
Usage: "Remove blockchain and state databases",
}
dumpCommand = cli.Command{
Action: dump,
Name: "dump",
Usage: `dump a specific block from storage`,
Description: `
The arguments are interpreted as block numbers or hashes.
Use "ethereum dump 0" to dump the genesis block.
`,
}
)
func importChain(ctx *cli.Context) {
if len(ctx.Args()) != 1 {
utils.Fatalf("This command requires an argument.")
}
chain, blockDB, stateDB, extraDB := utils.MakeChain(ctx)
start := time.Now()
err := utils.ImportChain(chain, ctx.Args().First())
closeAll(blockDB, stateDB, extraDB)
if err != nil {
utils.Fatalf("Import error: %v", err)
}
fmt.Printf("Import done in %v", time.Since(start))
}
func exportChain(ctx *cli.Context) {
if len(ctx.Args()) != 1 {
utils.Fatalf("This command requires an argument.")
}
chain, _, _, _ := utils.MakeChain(ctx)
start := time.Now()
if err := utils.ExportChain(chain, ctx.Args().First()); err != nil {
utils.Fatalf("Export error: %v\n", err)
}
fmt.Printf("Export done in %v", time.Since(start))
}
func removeDB(ctx *cli.Context) {
confirm, err := utils.PromptConfirm("Remove local databases?")
if err != nil {
utils.Fatalf("%v", err)
}
if confirm {
fmt.Println("Removing chain and state databases...")
start := time.Now()
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "blockchain"))
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "state"))
fmt.Printf("Removed in %v\n", time.Since(start))
} else {
fmt.Println("Operation aborted")
}
}
func upgradeDB(ctx *cli.Context) {
glog.Infoln("Upgrading blockchain database")
chain, blockDB, stateDB, extraDB := utils.MakeChain(ctx)
v, _ := blockDB.Get([]byte("BlockchainVersion"))
bcVersion := int(common.NewValue(v).Uint())
if bcVersion == 0 {
bcVersion = core.BlockChainVersion
}
// Export the current chain.
filename := fmt.Sprintf("blockchain_%d_%s.chain", bcVersion, time.Now().Format("20060102_150405"))
exportFile := filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), filename)
if err := utils.ExportChain(chain, exportFile); err != nil {
utils.Fatalf("Unable to export chain for reimport %s", err)
}
closeAll(blockDB, stateDB, extraDB)
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "blockchain"))
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "state"))
// Import the chain file.
chain, blockDB, stateDB, extraDB = utils.MakeChain(ctx)
blockDB.Put([]byte("BlockchainVersion"), common.NewValue(core.BlockChainVersion).Bytes())
err := utils.ImportChain(chain, exportFile)
closeAll(blockDB, stateDB, extraDB)
if err != nil {
utils.Fatalf("Import error %v (a backup is made in %s, use the import command to import it)", err, exportFile)
} else {
os.Remove(exportFile)
glog.Infoln("Import finished")
}
}
func dump(ctx *cli.Context) {
chain, _, stateDB, _ := utils.MakeChain(ctx)
for _, arg := range ctx.Args() {
var block *types.Block
if hashish(arg) {
block = chain.GetBlock(common.HexToHash(arg))
} else {
num, _ := strconv.Atoi(arg)
block = chain.GetBlockByNumber(uint64(num))
}
if block == nil {
fmt.Println("{}")
utils.Fatalf("block not found")
} else {
state := state.New(block.Root(), stateDB)
fmt.Printf("%s\n", state.Dump())
}
}
}
// hashish returns true for strings that look like hashes.
func hashish(x string) bool {
_, err := strconv.Atoi(x)
return err != nil
}
func closeAll(dbs ...common.Database) {
for _, db := range dbs {
db.Close()
}
}

View File

@ -22,6 +22,7 @@ import (
"fmt"
"math/big"
"os"
"os/signal"
"path/filepath"
"strings"
@ -47,7 +48,8 @@ type dumbterm struct{ r *bufio.Reader }
func (r dumbterm) Prompt(p string) (string, error) {
fmt.Print(p)
return r.r.ReadString('\n')
line, err := r.r.ReadString('\n')
return strings.TrimSuffix(line, "\n"), err
}
func (r dumbterm) PasswordPrompt(p string) (string, error) {
@ -104,7 +106,7 @@ func newJSRE(ethereum *eth.Ethereum, libPath, corsDomain string, interactive boo
func (js *jsre) apiBindings(f xeth.Frontend) {
xe := xeth.New(js.ethereum, f)
ethApi := rpc.NewEthereumApi(xe)
jeth := rpc.NewJeth(ethApi, js.re.ToVal, js.re)
jeth := rpc.NewJeth(ethApi, js.re)
js.re.Set("jeth", struct{}{})
t, _ := js.re.Get("jeth")
@ -182,30 +184,52 @@ func (self *jsre) exec(filename string) error {
}
func (self *jsre) interactive() {
for {
input, err := self.Prompt(self.ps1)
if err != nil {
break
}
if input == "" {
continue
}
str += input + "\n"
self.setIndent()
if indentCount <= 0 {
if input == "exit" {
break
// Read input lines.
prompt := make(chan string)
inputln := make(chan string)
go func() {
defer close(inputln)
for {
line, err := self.Prompt(<-prompt)
if err != nil {
return
}
inputln <- line
}
}()
// Wait for Ctrl-C, too.
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt)
defer func() {
if self.atexit != nil {
self.atexit()
}
self.re.Stop(false)
}()
for {
prompt <- self.ps1
select {
case <-sig:
fmt.Println("caught interrupt, exiting")
return
case input, ok := <-inputln:
if !ok || indentCount <= 0 && input == "exit" {
return
}
if input == "" {
continue
}
str += input + "\n"
self.setIndent()
if indentCount <= 0 {
hist := str[:len(str)-1]
self.AppendHistory(hist)
self.parseInput(str)
str = ""
}
hist := str[:len(str)-1]
self.AppendHistory(hist)
self.parseInput(str)
str = ""
}
}
if self.atexit != nil {
self.atexit()
}
self.re.Stop(false)
}
func (self *jsre) withHistory(op func(*os.File)) {

View File

@ -35,6 +35,7 @@ const (
var (
versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`))
testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f"))
testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}`
)
@ -72,6 +73,7 @@ func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) {
ks := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore"))
am := accounts.NewManager(ks)
ethereum, err := eth.New(&eth.Config{
NodeKey: testNodeKey,
DataDir: tmp,
AccountManager: am,
MaxPeers: 0,
@ -122,7 +124,7 @@ func TestNodeInfo(t *testing.T) {
}
defer ethereum.Stop()
defer os.RemoveAll(tmp)
want := `{"DiscPort":0,"IP":"0.0.0.0","ListenAddr":"","Name":"test","NodeID":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","NodeUrl":"enode://00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000@0.0.0.0:0","TCPPort":0,"Td":"0"}`
want := `{"DiscPort":0,"IP":"0.0.0.0","ListenAddr":"","Name":"test","NodeID":"4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5","NodeUrl":"enode://4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5@0.0.0.0:0","TCPPort":0,"Td":"131072"}`
checkEvalJSON(t, repl, `admin.nodeInfo()`, want)
}

View File

@ -24,31 +24,27 @@ import (
"fmt"
"io"
"io/ioutil"
_ "net/http/pprof"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"github.com/codegangsta/cli"
"github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/logger"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
)
import _ "net/http/pprof"
const (
ClientIdentifier = "Geth"
Version = "0.9.24"
Version = "0.9.26"
)
var (
@ -68,7 +64,12 @@ func init() {
app.Action = run
app.HideVersion = true // we have a command to print the version
app.Commands = []cli.Command{
blocktestCmd,
blocktestCommand,
importCommand,
exportCommand,
upgradedbCommand,
removedbCommand,
dumpCommand,
{
Action: makedag,
Name: "makedag",
@ -193,15 +194,6 @@ nodes.
},
},
},
{
Action: dump,
Name: "dump",
Usage: `dump a specific block from storage`,
Description: `
The arguments are interpreted as block numbers or hashes.
Use "ethereum dump 0" to dump the genesis block.
`,
},
{
Action: console,
Name: "console",
@ -221,26 +213,6 @@ 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
`,
},
{
Action: importchain,
Name: "import",
Usage: `import a blockchain file`,
},
{
Action: exportchain,
Name: "export",
Usage: `export blockchain into file`,
},
{
Action: upgradeDb,
Name: "upgradedb",
Usage: "upgrade chainblock database",
},
{
Action: removeDb,
Name: "removedb",
Usage: "Remove blockchain and state databases",
},
}
app.Flags = []cli.Flag{
utils.IdentityFlag,
@ -260,6 +232,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.AutoDAGFlag,
utils.NATFlag,
utils.NatspecEnabledFlag,
utils.NoDiscoverFlag,
utils.NodeKeyFileFlag,
utils.NodeKeyHexFlag,
utils.RPCEnabledFlag,
@ -281,17 +254,12 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.SolcPathFlag,
}
app.Before = func(ctx *cli.Context) error {
utils.SetupLogger(ctx)
if ctx.GlobalBool(utils.PProfEanbledFlag.Name) {
utils.StartPProf(ctx)
}
return nil
}
// missing:
// flag.StringVar(&ConfigFile, "conf", defaultConfigFile, "config file")
// flag.BoolVar(&DiffTool, "difftool", false, "creates output for diff'ing. Sets LogLevel=0")
// flag.StringVar(&DiffType, "diff", "all", "sets the level of diff output [vm, all]. Has no effect if difftool=false")
}
func main() {
@ -372,13 +340,13 @@ func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (pass
var err error
// Load startup keys. XXX we are going to need a different format
if len(account) == 0 {
if !((len(account) == 40) || (len(account) == 42)) { // with or without 0x
utils.Fatalf("Invalid account address '%s'", account)
}
// Attempt to unlock the account 3 times
attempts := 3
for tries := 0; tries < attempts; tries++ {
msg := fmt.Sprintf("Unlocking account %s...%s | Attempt %d/%d", account[:8], account[len(account)-6:], tries+1, attempts)
msg := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", account, tries+1, attempts)
passphrase = getPassPhrase(ctx, msg, false)
err = am.Unlock(common.HexToAddress(account), passphrase)
if err == nil {
@ -426,7 +394,7 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) {
}
func accountList(ctx *cli.Context) {
am := utils.GetAccountManager(ctx)
am := utils.MakeAccountManager(ctx)
accts, err := am.Accounts()
if err != nil {
utils.Fatalf("Could not list accounts: %v", err)
@ -468,7 +436,7 @@ func getPassPhrase(ctx *cli.Context, desc string, confirmation bool) (passphrase
}
func accountCreate(ctx *cli.Context) {
am := utils.GetAccountManager(ctx)
am := utils.MakeAccountManager(ctx)
passphrase := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true)
acct, err := am.NewAccount(passphrase)
if err != nil {
@ -487,7 +455,7 @@ func importWallet(ctx *cli.Context) {
utils.Fatalf("Could not read wallet file: %v", err)
}
am := utils.GetAccountManager(ctx)
am := utils.MakeAccountManager(ctx)
passphrase := getPassPhrase(ctx, "", false)
acct, err := am.ImportPreSaleKey(keyJson, passphrase)
@ -502,7 +470,7 @@ func accountImport(ctx *cli.Context) {
if len(keyfile) == 0 {
utils.Fatalf("keyfile must be given as argument")
}
am := utils.GetAccountManager(ctx)
am := utils.MakeAccountManager(ctx)
passphrase := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true)
acct, err := am.Import(keyfile, passphrase)
if err != nil {
@ -511,153 +479,6 @@ func accountImport(ctx *cli.Context) {
fmt.Printf("Address: %x\n", acct)
}
func importchain(ctx *cli.Context) {
if len(ctx.Args()) != 1 {
utils.Fatalf("This command requires an argument.")
}
cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx)
cfg.SkipBcVersionCheck = true
ethereum, err := eth.New(cfg)
if err != nil {
utils.Fatalf("%v\n", err)
}
chainmgr := ethereum.ChainManager()
start := time.Now()
err = utils.ImportChain(chainmgr, ctx.Args().First())
if err != nil {
utils.Fatalf("Import error: %v\n", err)
}
// force database flush
ethereum.BlockDb().Close()
ethereum.StateDb().Close()
ethereum.ExtraDb().Close()
fmt.Printf("Import done in %v", time.Since(start))
return
}
func exportchain(ctx *cli.Context) {
if len(ctx.Args()) != 1 {
utils.Fatalf("This command requires an argument.")
}
cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx)
cfg.SkipBcVersionCheck = true
ethereum, err := eth.New(cfg)
if err != nil {
utils.Fatalf("%v\n", err)
}
chainmgr := ethereum.ChainManager()
start := time.Now()
err = utils.ExportChain(chainmgr, ctx.Args().First())
if err != nil {
utils.Fatalf("Export error: %v\n", err)
}
fmt.Printf("Export done in %v", time.Since(start))
return
}
func removeDb(ctx *cli.Context) {
confirm, err := utils.PromptConfirm("Remove local databases?")
if err != nil {
utils.Fatalf("%v", err)
}
if confirm {
fmt.Println("Removing chain and state databases...")
start := time.Now()
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "blockchain"))
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "state"))
fmt.Printf("Removed in %v\n", time.Since(start))
} else {
fmt.Println("Operation aborted")
}
}
func upgradeDb(ctx *cli.Context) {
fmt.Println("Upgrade blockchain DB")
cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx)
cfg.SkipBcVersionCheck = true
ethereum, err := eth.New(cfg)
if err != nil {
utils.Fatalf("%v\n", err)
}
v, _ := ethereum.BlockDb().Get([]byte("BlockchainVersion"))
bcVersion := int(common.NewValue(v).Uint())
if bcVersion == 0 {
bcVersion = core.BlockChainVersion
}
filename := fmt.Sprintf("blockchain_%d_%s.chain", bcVersion, time.Now().Format("20060102_150405"))
exportFile := filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), filename)
err = utils.ExportChain(ethereum.ChainManager(), exportFile)
if err != nil {
utils.Fatalf("Unable to export chain for reimport %s\n", err)
}
ethereum.BlockDb().Close()
ethereum.StateDb().Close()
ethereum.ExtraDb().Close()
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "blockchain"))
os.RemoveAll(filepath.Join(ctx.GlobalString(utils.DataDirFlag.Name), "state"))
ethereum, err = eth.New(cfg)
if err != nil {
utils.Fatalf("%v\n", err)
}
ethereum.BlockDb().Put([]byte("BlockchainVersion"), common.NewValue(core.BlockChainVersion).Bytes())
err = utils.ImportChain(ethereum.ChainManager(), exportFile)
if err != nil {
utils.Fatalf("Import error %v (a backup is made in %s, use the import command to import it)\n", err, exportFile)
}
// force database flush
ethereum.BlockDb().Close()
ethereum.StateDb().Close()
ethereum.ExtraDb().Close()
os.Remove(exportFile)
fmt.Println("Import finished")
}
func dump(ctx *cli.Context) {
chainmgr, _, stateDb := utils.GetChain(ctx)
for _, arg := range ctx.Args() {
var block *types.Block
if hashish(arg) {
block = chainmgr.GetBlock(common.HexToHash(arg))
} else {
num, _ := strconv.Atoi(arg)
block = chainmgr.GetBlockByNumber(uint64(num))
}
if block == nil {
fmt.Println("{}")
utils.Fatalf("block not found")
} else {
statedb := state.New(block.Root(), stateDb)
fmt.Printf("%s\n", statedb.Dump())
}
}
}
func makedag(ctx *cli.Context) {
args := ctx.Args()
wrongArgs := func() {
@ -700,9 +521,3 @@ func version(c *cli.Context) {
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
}
// hashish returns true for strings that look like hashes.
func hashish(x string) bool {
_, err := strconv.Atoi(x)
return err != nil
}

View File

@ -102,7 +102,7 @@ window.filter = filter;
var amount = parseInt( value.value );
console.log("transact: ", to.value, " => ", amount)
contract.sendTransaction({from: eth.accounts[0]}).send( to.value, amount );
contract.send.sendTransaction(to.value, amount ,{from: eth.accounts[0]});
to.value = "";
value.value = "";

View File

@ -86,6 +86,10 @@ func init() {
utils.BlockchainVersionFlag,
utils.NetworkIdFlag,
}
app.Before = func(ctx *cli.Context) error {
utils.SetupLogger(ctx)
return nil
}
}
func main() {

View File

@ -40,6 +40,10 @@ import (
"github.com/peterh/liner"
)
const (
importBatchSize = 2500
)
var interruptCallbacks = []func(os.Signal){}
// Register interrupt handlers callbacks
@ -125,10 +129,17 @@ func initDataDir(Datadir string) {
}
}
// Fatalf formats a message to standard output and exits the program.
// Fatalf formats a message to standard error and exits the program.
// The message is also printed to standard output if standard error
// is redirected to a different file.
func Fatalf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "Fatal: "+format+"\n", args...)
fmt.Fprintf(os.Stdout, "Fatal: "+format+"\n", args...)
w := io.MultiWriter(os.Stdout, os.Stderr)
outf, _ := os.Stdout.Stat()
errf, _ := os.Stderr.Stat()
if outf != nil && errf != nil && os.SameFile(outf, errf) {
w = os.Stderr
}
fmt.Fprintf(w, "Fatal: "+format+"\n", args...)
logger.Flush()
os.Exit(1)
}
@ -166,53 +177,86 @@ func FormatTransactionData(data string) []byte {
return d
}
func ImportChain(chainmgr *core.ChainManager, fn string) error {
fmt.Printf("importing blockchain '%s'\n", fn)
fh, err := os.OpenFile(fn, os.O_RDONLY, os.ModePerm)
func ImportChain(chain *core.ChainManager, fn string) error {
// Watch for Ctrl-C while the import is running.
// If a signal is received, the import will stop at the next batch.
interrupt := make(chan os.Signal, 1)
stop := make(chan struct{})
signal.Notify(interrupt, os.Interrupt)
defer signal.Stop(interrupt)
defer close(interrupt)
go func() {
if _, ok := <-interrupt; ok {
glog.Info("caught interrupt during import, will stop at next batch")
}
close(stop)
}()
checkInterrupt := func() bool {
select {
case <-stop:
return true
default:
return false
}
}
glog.Infoln("Importing blockchain", fn)
fh, err := os.Open(fn)
if err != nil {
return err
}
defer fh.Close()
chainmgr.Reset()
stream := rlp.NewStream(fh, 0)
var i, n int
batchSize := 2500
blocks := make(types.Blocks, batchSize)
for ; ; i++ {
var b types.Block
if err := stream.Decode(&b); err == io.EOF {
break
} else if err != nil {
return fmt.Errorf("at block %d: %v", i, err)
// Run actual the import.
blocks := make(types.Blocks, importBatchSize)
n := 0
for batch := 0; ; batch++ {
// Load a batch of RLP blocks.
if checkInterrupt() {
return fmt.Errorf("interrupted")
}
blocks[n] = &b
n++
if n == batchSize {
if _, err := chainmgr.InsertChain(blocks); err != nil {
return fmt.Errorf("invalid block %v", err)
i := 0
for ; i < importBatchSize; i++ {
var b types.Block
if err := stream.Decode(&b); err == io.EOF {
break
} else if err != nil {
return fmt.Errorf("at block %d: %v", n, err)
}
n = 0
blocks = make(types.Blocks, batchSize)
blocks[i] = &b
n++
}
if i == 0 {
break
}
// Import the batch.
if checkInterrupt() {
return fmt.Errorf("interrupted")
}
if hasAllBlocks(chain, blocks[:i]) {
glog.Infof("skipping batch %d, all blocks present [%x / %x]",
batch, blocks[0].Hash().Bytes()[:4], blocks[i-1].Hash().Bytes()[:4])
continue
}
if _, err := chain.InsertChain(blocks[:i]); err != nil {
return fmt.Errorf("invalid block %d: %v", n, err)
}
}
if n > 0 {
if _, err := chainmgr.InsertChain(blocks[:n]); err != nil {
return fmt.Errorf("invalid block %v", err)
}
}
fmt.Printf("imported %d blocks\n", i)
return nil
}
func hasAllBlocks(chain *core.ChainManager, bs []*types.Block) bool {
for _, b := range bs {
if !chain.HasBlock(b.Hash()) {
return false
}
}
return true
}
func ExportChain(chainmgr *core.ChainManager, fn string) error {
fmt.Printf("exporting blockchain '%s'\n", fn)
glog.Infoln("Exporting blockchain to", fn)
fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
if err != nil {
return err
@ -221,6 +265,6 @@ func ExportChain(chainmgr *core.ChainManager, fn string) error {
if err := chainmgr.Export(fh); err != nil {
return err
}
fmt.Printf("exported blockchain\n")
glog.Infoln("Exported blockchain to", fn)
return nil
}

View File

@ -235,6 +235,10 @@ var (
Usage: "NAT port mapping mechanism (any|none|upnp|pmp|extip:<IP>)",
Value: "any",
}
NoDiscoverFlag = cli.BoolFlag{
Name: "nodiscover",
Usage: "Disables the peer discovery mechanism (manual peer addition)",
}
WhisperEnabledFlag = cli.BoolFlag{
Name: "shh",
Usage: "Enable whisper",
@ -252,7 +256,8 @@ var (
}
)
func GetNAT(ctx *cli.Context) nat.Interface {
// MakeNAT creates a port mapper from set command line flags.
func MakeNAT(ctx *cli.Context) nat.Interface {
natif, err := nat.Parse(ctx.GlobalString(NATFlag.Name))
if err != nil {
Fatalf("Option %s: %v", NATFlag.Name, err)
@ -260,7 +265,8 @@ func GetNAT(ctx *cli.Context) nat.Interface {
return natif
}
func GetNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) {
// MakeNodeKey creates a node key from set command line flags.
func MakeNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) {
hex, file := ctx.GlobalString(NodeKeyHexFlag.Name), ctx.GlobalString(NodeKeyFileFlag.Name)
var err error
switch {
@ -278,21 +284,12 @@ func GetNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) {
return key
}
// MakeEthConfig creates ethereum options from set command line flags.
func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
// Set verbosity on glog
glog.SetV(ctx.GlobalInt(VerbosityFlag.Name))
glog.CopyStandardLogTo("INFO")
// Set the log type
//glog.SetToStderr(ctx.GlobalBool(LogToStdErrFlag.Name))
glog.SetToStderr(true)
// Set the log dir
glog.SetLogDir(ctx.GlobalString(LogFileFlag.Name))
customName := ctx.GlobalString(IdentityFlag.Name)
if len(customName) > 0 {
clientID += "/" + customName
}
return &eth.Config{
Name: common.MakeName(clientID, version),
DataDir: ctx.GlobalString(DataDirFlag.Name),
@ -305,14 +302,15 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
LogJSON: ctx.GlobalString(LogJSONFlag.Name),
Etherbase: ctx.GlobalString(EtherbaseFlag.Name),
MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name),
AccountManager: GetAccountManager(ctx),
AccountManager: MakeAccountManager(ctx),
VmDebug: ctx.GlobalBool(VMDebugFlag.Name),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name),
Port: ctx.GlobalString(ListenPortFlag.Name),
NAT: GetNAT(ctx),
NAT: MakeNAT(ctx),
NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name),
NodeKey: GetNodeKey(ctx),
Discovery: !ctx.GlobalBool(NoDiscoverFlag.Name),
NodeKey: MakeNodeKey(ctx),
Shh: ctx.GlobalBool(WhisperEnabledFlag.Name),
Dial: true,
BootNodes: ctx.GlobalString(BootnodesFlag.Name),
@ -320,38 +318,41 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
SolcPath: ctx.GlobalString(SolcPathFlag.Name),
AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name),
}
}
func GetChain(ctx *cli.Context) (*core.ChainManager, common.Database, common.Database) {
dataDir := ctx.GlobalString(DataDirFlag.Name)
// SetupLogger configures glog from the logging-related command line flags.
func SetupLogger(ctx *cli.Context) {
glog.SetV(ctx.GlobalInt(VerbosityFlag.Name))
glog.CopyStandardLogTo("INFO")
glog.SetToStderr(true)
glog.SetLogDir(ctx.GlobalString(LogFileFlag.Name))
}
blockDb, err := ethdb.NewLDBDatabase(filepath.Join(dataDir, "blockchain"))
if err != nil {
// MakeChain creates a chain manager from set command line flags.
func MakeChain(ctx *cli.Context) (chain *core.ChainManager, blockDB, stateDB, extraDB common.Database) {
dd := ctx.GlobalString(DataDirFlag.Name)
var err error
if blockDB, err = ethdb.NewLDBDatabase(filepath.Join(dd, "blockchain")); err != nil {
Fatalf("Could not open database: %v", err)
}
stateDb, err := ethdb.NewLDBDatabase(filepath.Join(dataDir, "state"))
if err != nil {
if stateDB, err = ethdb.NewLDBDatabase(filepath.Join(dd, "state")); err != nil {
Fatalf("Could not open database: %v", err)
}
extraDb, err := ethdb.NewLDBDatabase(filepath.Join(dataDir, "extra"))
if err != nil {
if extraDB, err = ethdb.NewLDBDatabase(filepath.Join(dd, "extra")); err != nil {
Fatalf("Could not open database: %v", err)
}
eventMux := new(event.TypeMux)
pow := ethash.New()
chainManager := core.NewChainManager(blockDb, stateDb, pow, eventMux)
txPool := core.NewTxPool(eventMux, chainManager.State, chainManager.GasLimit)
blockProcessor := core.NewBlockProcessor(stateDb, extraDb, pow, txPool, chainManager, eventMux)
chainManager.SetProcessor(blockProcessor)
return chainManager, blockDb, stateDb
chain = core.NewChainManager(blockDB, stateDB, pow, eventMux)
txpool := core.NewTxPool(eventMux, chain.State, chain.GasLimit)
proc := core.NewBlockProcessor(stateDB, extraDB, pow, txpool, chain, eventMux)
chain.SetProcessor(proc)
return chain, blockDB, stateDB, extraDB
}
func GetAccountManager(ctx *cli.Context) *accounts.Manager {
// MakeChain creates an account manager from set command line flags.
func MakeAccountManager(ctx *cli.Context) *accounts.Manager {
dataDir := ctx.GlobalString(DataDirFlag.Name)
ks := crypto.NewKeyStorePassphrase(filepath.Join(dataDir, "keystore"))
return accounts.NewManager(ks)

View File

@ -34,6 +34,8 @@ var (
"file", //
"--natspec-dev", // Request to output the contract's Natspec developer documentation.
"file",
"--add-std",
"1",
}
)

View File

@ -5,7 +5,6 @@ type Database interface {
Put(key []byte, value []byte)
Get(key []byte) ([]byte, error)
Delete(key []byte) error
LastKnownTD() []byte
Close()
Flush() error
}

View File

@ -21,7 +21,7 @@ import (
const (
// must be bumped when consensus algorithm is changed, this forces the upgradedb
// command to be run (forces the blocks to be imported again using the new algorithm)
BlockChainVersion = 2
BlockChainVersion = 3
)
var receiptsPre = []byte("receipts-")
@ -40,11 +40,6 @@ type BlockProcessor struct {
txpool *TxPool
// The last attempted block is mainly used for debugging purposes
// This does not have to be a valid block and will be set during
// 'Process' & canonical validation.
lastAttemptedBlock *types.Block
events event.Subscription
eventMux *event.TypeMux
@ -159,6 +154,9 @@ func (sm *BlockProcessor) RetryProcess(block *types.Block) (logs state.Logs, err
return nil, ParentError(header.ParentHash)
}
parent := sm.bc.GetBlock(header.ParentHash)
if !sm.Pow.Verify(block) {
return nil, ValidationError("Block's nonce is invalid (= %x)", block.Nonce)
}
return sm.processWithParent(block, parent)
}
@ -185,8 +183,6 @@ func (sm *BlockProcessor) Process(block *types.Block) (logs state.Logs, err erro
}
func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs state.Logs, err error) {
sm.lastAttemptedBlock = block
// Create a new state based on the parent's root (e.g., create copy)
state := state.New(parent.Root(), sm.db)
@ -252,6 +248,12 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs st
return
}
// store the receipts
err = putReceipts(sm.extraDb, block.Hash(), receipts)
if err != nil {
return nil, err
}
// Calculate the td for this block
//td = CalculateTD(block, parent)
// Sync the current block's state to the database
@ -265,26 +267,11 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs st
putTx(sm.extraDb, tx, block, uint64(i))
}
receiptsRlp := block.Receipts().RlpEncode()
sm.extraDb.Put(append(receiptsPre, block.Hash().Bytes()...), receiptsRlp)
return state.Logs(), nil
}
func (self *BlockProcessor) GetBlockReceipts(bhash common.Hash) (receipts types.Receipts, err error) {
var rdata []byte
rdata, err = self.extraDb.Get(append(receiptsPre, bhash[:]...))
if err == nil {
err = rlp.DecodeBytes(rdata, &receipts)
}
return
}
// Validates the current block. Returns an error if the block was invalid,
// an uncle or anything that isn't on the current block chain.
// Validation validates easy over difficult (dagger takes longer time = difficult)
// See YP section 4.3.4. "Block Header Validity"
// Validates a block. Returns an error if the block is invalid.
func (sm *BlockProcessor) ValidateHeader(block, parent *types.Header, checkPow bool) error {
if big.NewInt(int64(len(block.Extra))).Cmp(params.MaximumExtraDataSize) == 1 {
return fmt.Errorf("Block extra data too long (%d)", len(block.Extra))
@ -295,7 +282,6 @@ func (sm *BlockProcessor) ValidateHeader(block, parent *types.Header, checkPow b
return fmt.Errorf("Difficulty check failed for block %v, %v", block.Difficulty, expd)
}
// block.gasLimit - parent.gasLimit <= parent.gasLimit / GasLimitBoundDivisor
a := new(big.Int).Sub(block.GasLimit, parent.GasLimit)
a.Abs(a)
b := new(big.Int).Div(parent.GasLimit, params.GasLimitBoundDivisor)
@ -303,8 +289,7 @@ func (sm *BlockProcessor) ValidateHeader(block, parent *types.Header, checkPow b
return fmt.Errorf("GasLimit check failed for block %v (%v > %v)", block.GasLimit, a, b)
}
// Allow future blocks up to 10 seconds
if int64(block.Time) > time.Now().Unix()+4 {
if int64(block.Time) > time.Now().Unix() {
return BlockFutureErr
}
@ -391,13 +376,25 @@ func (sm *BlockProcessor) VerifyUncles(statedb *state.StateDB, block, parent *ty
return nil
}
// GetBlockReceipts returns the receipts beloniging to the block hash
func (sm *BlockProcessor) GetBlockReceipts(bhash common.Hash) (receipts types.Receipts, err error) {
return getBlockReceipts(sm.extraDb, bhash)
}
// GetLogs returns the logs of the given block. This method is using a two step approach
// where it tries to get it from the (updated) method which gets them from the receipts or
// the depricated way by re-processing the block.
func (sm *BlockProcessor) GetLogs(block *types.Block) (logs state.Logs, err error) {
if !sm.bc.HasBlock(block.Header().ParentHash) {
return nil, ParentError(block.Header().ParentHash)
receipts, err := sm.GetBlockReceipts(block.Hash())
if err == nil && len(receipts) > 0 {
// coalesce logs
for _, receipt := range receipts {
logs = append(logs, receipt.Logs()...)
}
return
}
sm.lastAttemptedBlock = block
// TODO: remove backward compatibility
var (
parent = sm.bc.GetBlock(block.Header().ParentHash)
state = state.New(parent.Root(), sm.db)
@ -408,6 +405,16 @@ func (sm *BlockProcessor) GetLogs(block *types.Block) (logs state.Logs, err erro
return state.Logs(), nil
}
func getBlockReceipts(db common.Database, bhash common.Hash) (receipts types.Receipts, err error) {
var rdata []byte
rdata, err = db.Get(append(receiptsPre, bhash[:]...))
if err == nil {
err = rlp.DecodeBytes(rdata, &receipts)
}
return
}
func putTx(db common.Database, tx *types.Transaction, block *types.Block, i uint64) {
rlpEnc, err := rlp.EncodeToBytes(tx)
if err != nil {
@ -431,3 +438,19 @@ func putTx(db common.Database, tx *types.Transaction, block *types.Block, i uint
}
db.Put(append(tx.Hash().Bytes(), 0x0001), rlpMeta)
}
func putReceipts(db common.Database, hash common.Hash, receipts types.Receipts) error {
storageReceipts := make([]*types.ReceiptForStorage, len(receipts))
for i, receipt := range receipts {
storageReceipts[i] = (*types.ReceiptForStorage)(receipt)
}
bytes, err := rlp.EncodeToBytes(storageReceipts)
if err != nil {
return err
}
db.Put(append(receiptsPre, hash[:]...), bytes)
return nil
}

View File

@ -5,6 +5,8 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/pow/ezp"
@ -35,3 +37,33 @@ func TestNumber(t *testing.T) {
t.Errorf("didn't expect block number error")
}
}
func TestPutReceipt(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
var addr common.Address
addr[0] = 1
var hash common.Hash
hash[0] = 2
receipt := new(types.Receipt)
receipt.SetLogs(state.Logs{&state.Log{
Address: addr,
Topics: []common.Hash{hash},
Data: []byte("hi"),
Number: 42,
TxHash: hash,
TxIndex: 0,
BlockHash: hash,
Index: 0,
}})
putReceipts(db, hash, types.Receipts{receipt})
receipts, err := getBlockReceipts(db, hash)
if err != nil {
t.Error("got err:", err)
}
if len(receipts) != 1 {
t.Error("expected to get 1 receipt, got", len(receipts))
}
}

View File

@ -2,7 +2,9 @@ package core
import "github.com/ethereum/go-ethereum/common"
var badHashes = []common.Hash{
common.HexToHash("f269c503aed286caaa0d114d6a5320e70abbc2febe37953207e76a2873f2ba79"),
common.HexToHash("38f5bbbffd74804820ffa4bab0cd540e9de229725afb98c1a7e57936f4a714bc"),
// Set of manually tracked bad hashes (usually hard forks)
var BadHashes = map[common.Hash]bool{
common.HexToHash("f269c503aed286caaa0d114d6a5320e70abbc2febe37953207e76a2873f2ba79"): true,
common.HexToHash("38f5bbbffd74804820ffa4bab0cd540e9de229725afb98c1a7e57936f4a714bc"): true,
common.HexToHash("7064455b364775a16afbdecd75370e912c6e2879f202eda85b9beae547fff3ac"): true,
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"math/big"
"os"
"runtime"
"sync"
"time"
@ -68,6 +69,7 @@ func CalcGasLimit(parent *types.Block) *big.Int {
gl := new(big.Int).Sub(parent.GasLimit(), decay)
gl = gl.Add(gl, contrib)
gl = gl.Add(gl, big.NewInt(1))
gl = common.BigMax(gl, params.MinGasLimit)
if gl.Cmp(params.GenesisGasLimit) < 0 {
@ -119,7 +121,7 @@ func NewChainManager(blockDb, stateDb common.Database, pow pow.PoW, mux *event.T
bc.setLastState()
// Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
for _, hash := range badHashes {
for hash, _ := range BadHashes {
if block := bc.GetBlock(hash); block != nil {
glog.V(logger.Error).Infof("Found bad hash. Reorganising chain to state %x\n", block.ParentHash().Bytes()[:4])
block = bc.GetBlock(block.ParentHash())
@ -233,14 +235,23 @@ func (bc *ChainManager) setLastState() {
data, _ := bc.blockDb.Get([]byte("LastBlock"))
if len(data) != 0 {
block := bc.GetBlock(common.BytesToHash(data))
bc.currentBlock = block
bc.lastBlockHash = block.Hash()
// Set the last know difficulty (might be 0x0 as initial value, Genesis)
bc.td = common.BigD(bc.blockDb.LastKnownTD())
if block != nil {
bc.currentBlock = block
bc.lastBlockHash = block.Hash()
} else { // TODO CLEAN THIS UP TMP CODE
block = bc.GetBlockByNumber(400000)
if block == nil {
fmt.Println("Fatal. LastBlock not found. Report this issue")
os.Exit(1)
}
bc.currentBlock = block
bc.lastBlockHash = block.Hash()
bc.insert(block)
}
} else {
bc.Reset()
}
bc.td = bc.currentBlock.Td
bc.currentGasLimit = CalcGasLimit(bc.currentBlock)
if glog.V(logger.Info) {
@ -471,7 +482,7 @@ func (self *ChainManager) GetAncestors(block *types.Block, length int) (blocks [
}
func (bc *ChainManager) setTotalDifficulty(td *big.Int) {
bc.blockDb.Put([]byte("LTD"), td.Bytes())
//bc.blockDb.Put([]byte("LTD"), td.Bytes())
bc.td = td
}
@ -537,18 +548,21 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) {
tstart = time.Now()
)
// check the nonce in parallel to the block processing
// this speeds catching up significantly
nonceErrCh := make(chan error)
go func() {
nonceErrCh <- verifyNonces(self.pow, chain)
}()
for i, block := range chain {
if block == nil {
continue
}
if BadHashes[block.Hash()] {
err := fmt.Errorf("Found known bad hash in chain %x", block.Hash())
blockErr(block, err)
return i, err
}
// create a nonce channel for parallisation of the nonce check
nonceErrCh := make(chan error)
go verifyBlockNonce(self.pow, block, nonceErrCh)
// Setting block.Td regardless of error (known for example) prevents errors down the line
// in the protocol handler
block.Td = new(big.Int).Set(CalcTD(block, self.GetBlock(block.ParentHash())))
@ -557,13 +571,14 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) {
// all others will fail too (unless a known block is returned).
logs, err := self.processor.Process(block)
if err != nil {
// empty the nonce channel
<-nonceErrCh
if IsKnownBlockErr(err) {
stats.ignored++
continue
}
// Do not penelise on future block. We'll need a block queue eventually that will queue
// future block for future use
if err == BlockFutureErr {
block.SetQueued(true)
self.futureBlocks.Push(block)
@ -582,18 +597,23 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) {
return i, err
}
// Wait and check nonce channel and make sure it checks out fine
// otherwise return the error
if err := <-nonceErrCh; err != nil {
return i, err
}
cblock := self.currentBlock
// Write block to database. Eventually we'll have to improve on this and throw away blocks that are
// not in the canonical chain.
self.write(block)
// Compare the TD of the last known block in the canonical chain to make sure it's greater.
// At this point it's possible that a different chain (fork) becomes the new canonical chain.
if block.Td.Cmp(self.td) > 0 {
// chain fork
if block.ParentHash() != cblock.Hash() {
// during split we merge two different chains and create the new canonical chain
self.merge(cblock, block)
err := self.merge(cblock, block)
if err != nil {
return i, err
}
queue[i] = ChainSplitEvent{block, logs}
queueEvent.splitCount++
@ -626,19 +646,16 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) {
queue[i] = ChainSideEvent{block, logs}
queueEvent.sideCount++
}
// Write block to database. Eventually we'll have to improve on this and throw away blocks that are
// not in the canonical chain.
self.write(block)
// Delete from future blocks
self.futureBlocks.Delete(block.Hash())
stats.processed++
}
// check and wait for the nonce error channel and
// make sure no nonce error was thrown in the process
err := <-nonceErrCh
if err != nil {
return 0, err
}
if (stats.queued > 0 || stats.processed > 0 || stats.ignored > 0) && bool(glog.V(logger.Info)) {
tend := time.Since(tstart)
start, end := chain[0], chain[len(chain)-1]
@ -652,7 +669,7 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) {
// diff takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them
// to be part of the new canonical chain.
func (self *ChainManager) diff(oldBlock, newBlock *types.Block) types.Blocks {
func (self *ChainManager) diff(oldBlock, newBlock *types.Block) (types.Blocks, error) {
var (
newChain types.Blocks
commonBlock *types.Block
@ -663,14 +680,20 @@ func (self *ChainManager) diff(oldBlock, newBlock *types.Block) types.Blocks {
// first reduce whoever is higher bound
if oldBlock.NumberU64() > newBlock.NumberU64() {
// reduce old chain
for oldBlock = oldBlock; oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = self.GetBlock(oldBlock.ParentHash()) {
for oldBlock = oldBlock; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = self.GetBlock(oldBlock.ParentHash()) {
}
} else {
// reduce new chain and append new chain blocks for inserting later on
for newBlock = newBlock; newBlock.NumberU64() != oldBlock.NumberU64(); newBlock = self.GetBlock(newBlock.ParentHash()) {
for newBlock = newBlock; newBlock != nil && newBlock.NumberU64() != oldBlock.NumberU64(); newBlock = self.GetBlock(newBlock.ParentHash()) {
newChain = append(newChain, newBlock)
}
}
if oldBlock == nil {
return nil, fmt.Errorf("Invalid old chain")
}
if newBlock == nil {
return nil, fmt.Errorf("Invalid new chain")
}
numSplit := newBlock.Number()
for {
@ -681,6 +704,12 @@ func (self *ChainManager) diff(oldBlock, newBlock *types.Block) types.Blocks {
newChain = append(newChain, newBlock)
oldBlock, newBlock = self.GetBlock(oldBlock.ParentHash()), self.GetBlock(newBlock.ParentHash())
if oldBlock == nil {
return nil, fmt.Errorf("Invalid old chain")
}
if newBlock == nil {
return nil, fmt.Errorf("Invalid new chain")
}
}
if glog.V(logger.Info) {
@ -688,17 +717,22 @@ func (self *ChainManager) diff(oldBlock, newBlock *types.Block) types.Blocks {
glog.Infof("Fork detected @ %x. Reorganising chain from #%v %x to %x", commonHash[:4], numSplit, oldStart.Hash().Bytes()[:4], newStart.Hash().Bytes()[:4])
}
return newChain
return newChain, nil
}
// merge merges two different chain to the new canonical chain
func (self *ChainManager) merge(oldBlock, newBlock *types.Block) {
newChain := self.diff(oldBlock, newBlock)
func (self *ChainManager) merge(oldBlock, newBlock *types.Block) error {
newChain, err := self.diff(oldBlock, newBlock)
if err != nil {
return fmt.Errorf("chain reorg failed: %v", err)
}
// insert blocks. Order does not matter. Last block will be written in ImportChain itself which creates the new head properly
for _, block := range newChain {
self.insert(block)
}
return nil
}
func (self *ChainManager) update() {
@ -740,7 +774,7 @@ out:
func blockErr(block *types.Block, err error) {
h := block.Header()
glog.V(logger.Error).Infof("INVALID block #%v (%x)\n", h.Number, h.Hash().Bytes())
glog.V(logger.Error).Infof("Bad block #%v (%x)\n", h.Number, h.Hash().Bytes())
glog.V(logger.Error).Infoln(err)
glog.V(logger.Debug).Infoln(block)
}
@ -791,9 +825,17 @@ func verifyNonces(pow pow.PoW, blocks []*types.Block) error {
func verifyNonce(pow pow.PoW, in <-chan *types.Block, done chan<- error) {
for block := range in {
if !pow.Verify(block) {
done <- ValidationError("Block(#%v) nonce is invalid (= %x)", block.Number(), block.Nonce)
done <- ValidationError("Block (#%v / %x) nonce is invalid (= %x)", block.Number(), block.Hash(), block.Nonce)
} else {
done <- nil
}
}
}
func verifyBlockNonce(pow pow.PoW, block *types.Block, done chan<- error) {
if !pow.Verify(block) {
done <- ValidationError("Block (#%v / %x) nonce is invalid (= %x)", block.Number(), block.Hash(), block.Nonce)
} else {
done <- nil
}
}

View File

@ -3,9 +3,7 @@ package core
import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p"
)
// TODO move this to types?
@ -14,11 +12,7 @@ type Backend interface {
BlockProcessor() *BlockProcessor
ChainManager() *ChainManager
TxPool() *TxPool
PeerCount() int
IsListening() bool
Peers() []*p2p.Peer
BlockDb() common.Database
StateDb() common.Database
EventMux() *event.TypeMux
Downloader() *downloader.Downloader
}

View File

@ -29,15 +29,22 @@ func (self *Log) EncodeRLP(w io.Writer) error {
}
func (self *Log) String() string {
return fmt.Sprintf(`log: %x %x %x`, self.Address, self.Topics, self.Data)
return fmt.Sprintf(`log: %x %x %x %x %d %x %d`, self.Address, self.Topics, self.Data, self.TxHash, self.TxIndex, self.BlockHash, self.Index)
}
type Logs []*Log
func (self Logs) String() (ret string) {
for _, log := range self {
ret += fmt.Sprintf("%v", log)
}
type LogForStorage Log
return "[" + ret + "]"
func (self *LogForStorage) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, []interface{}{
self.Address,
self.Topics,
self.Data,
self.Number,
self.TxHash,
self.TxIndex,
self.BlockHash,
self.Index,
})
}

View File

@ -25,6 +25,7 @@ var (
ErrInsufficientFunds = errors.New("Insufficient funds for gas * price + value")
ErrIntrinsicGas = errors.New("Intrinsic gas too low")
ErrGasLimit = errors.New("Exceeds block gas limit")
ErrNegativeValue = errors.New("Negative value")
)
const txPoolQueueSize = 50
@ -125,6 +126,10 @@ func (pool *TxPool) ValidateTransaction(tx *types.Transaction) error {
return ErrGasLimit
}
if tx.Amount.Cmp(common.Big0) < 0 {
return ErrNegativeValue
}
total := new(big.Int).Mul(tx.Price, tx.GasLimit)
total.Add(total, tx.Value())
if pool.currentState().GetBalance(from).Cmp(total) < 0 {

View File

@ -138,3 +138,17 @@ func TestRemoveTx(t *testing.T) {
t.Error("expected txs to be 0, got", len(pool.txs))
}
}
func TestNegativeValue(t *testing.T) {
pool, key := setupTxPool()
tx := transaction()
tx.Value().Set(big.NewInt(-1))
tx.SignECDSA(key)
from, _ := tx.From()
pool.currentState().AddBalance(from, big.NewInt(1))
err := pool.Add(tx)
if err != ErrNegativeValue {
t.Error("expected", ErrNegativeValue, "got", err)
}
}

View File

@ -26,10 +26,39 @@ func (self *Receipt) SetLogs(logs state.Logs) {
self.logs = logs
}
func (self *Receipt) Logs() state.Logs {
return self.logs
}
func (self *Receipt) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, []interface{}{self.PostState, self.CumulativeGasUsed, self.Bloom, self.logs})
}
func (self *Receipt) DecodeRLP(s *rlp.Stream) error {
var r struct {
PostState []byte
CumulativeGasUsed *big.Int
Bloom Bloom
Logs state.Logs
}
if err := s.Decode(&r); err != nil {
return err
}
self.PostState, self.CumulativeGasUsed, self.Bloom, self.logs = r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs
return nil
}
type ReceiptForStorage Receipt
func (self *ReceiptForStorage) EncodeRLP(w io.Writer) error {
storageLogs := make([]*state.LogForStorage, len(self.logs))
for i, log := range self.logs {
storageLogs[i] = (*state.LogForStorage)(log)
}
return rlp.Encode(w, []interface{}{self.PostState, self.CumulativeGasUsed, self.Bloom, storageLogs})
}
func (self *Receipt) RlpEncode() []byte {
bytes, err := rlp.EncodeToBytes(self)
if err != nil {

View File

@ -72,6 +72,7 @@ type Config struct {
MaxPeers int
MaxPendingPeers int
Discovery bool
Port string
// Space-separated list of discovery node URLs
@ -311,6 +312,7 @@ func New(config *Config) (*Ethereum, error) {
Name: config.Name,
MaxPeers: config.MaxPeers,
MaxPendingPeers: config.MaxPendingPeers,
Discovery: config.Discovery,
Protocols: protocols,
NAT: config.NAT,
NoDial: !config.Dial,
@ -449,14 +451,10 @@ func (s *Ethereum) Start() error {
ClientString: s.net.Name,
ProtocolVersion: ProtocolVersion,
})
if s.net.MaxPeers > 0 {
err := s.net.Start()
if err != nil {
return err
}
err := s.net.Start()
if err != nil {
return err
}
// periodically flush databases
go s.syncDatabases()

View File

@ -7,7 +7,10 @@ import (
"sync/atomic"
"time"
"gopkg.in/fatih/set.v0"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/logger"
@ -75,6 +78,7 @@ type Downloader struct {
queue *queue // Scheduler for selecting the hashes to download
peers *peerSet // Set of active peers from which download can proceed
checks map[common.Hash]*crossCheck // Pending cross checks to verify a hash chain
banned *set.SetNonTS // Set of hashes we've received and banned
// Callbacks
hasBlock hashCheckFn
@ -100,6 +104,7 @@ type Block struct {
}
func New(mux *event.TypeMux, hasBlock hashCheckFn, getBlock getBlockFn) *Downloader {
// Create the base downloader
downloader := &Downloader{
mux: mux,
queue: newQueue(),
@ -110,6 +115,11 @@ func New(mux *event.TypeMux, hasBlock hashCheckFn, getBlock getBlockFn) *Downloa
hashCh: make(chan hashPack, 1),
blockCh: make(chan blockPack, 1),
}
// Inject all the known bad hashes
downloader.banned = set.NewNonTS()
for hash, _ := range core.BadHashes {
downloader.banned.Add(hash)
}
return downloader
}
@ -280,6 +290,12 @@ func (d *Downloader) fetchHashes(p *peer, h common.Hash) error {
glog.V(logger.Debug).Infof("Peer (%s) responded with empty hash set\n", active.id)
return errEmptyHashSet
}
for _, hash := range hashPack.hashes {
if d.banned.Has(hash) {
glog.V(logger.Debug).Infof("Peer (%s) sent a known invalid chain\n", active.id)
return ErrInvalidChain
}
}
// Determine if we're done fetching hashes (queue up all pending), and continue if not done
done, index := false, 0
for index, head = range hashPack.hashes {
@ -415,7 +431,7 @@ out:
peer.Demote()
break
}
if glog.V(logger.Debug) {
if glog.V(logger.Debug) && len(blockPack.blocks) > 0 {
glog.Infof("Added %d blocks from: %s\n", len(blockPack.blocks), blockPack.peerId)
}
// Promote the peer and update it's idle state

View File

@ -2,7 +2,6 @@ package eth
import (
"fmt"
"math"
"math/big"
"sync"
"time"
@ -93,14 +92,22 @@ func NewProtocolManager(protocolVersion, networkId int, mux *event.TypeMux, txpo
}
func (pm *ProtocolManager) removePeer(id string) {
// Unregister the peer from the downloader
pm.downloader.UnregisterPeer(id)
// Short circuit if the peer was already removed
peer := pm.peers.Peer(id)
if peer == nil {
return
}
glog.V(logger.Debug).Infoln("Removing peer", id)
// Remove the peer from the Ethereum peer set too
glog.V(logger.Detail).Infoln("Removing peer", id)
// Unregister the peer from the downloader and Ethereum peer set
pm.downloader.UnregisterPeer(id)
if err := pm.peers.Unregister(id); err != nil {
glog.V(logger.Error).Infoln("Removal failed:", err)
}
// Hard disconnect at the networking layer
if peer != nil {
peer.Peer.Disconnect(p2p.DiscUselessPeer)
}
}
func (pm *ProtocolManager) Start() {
@ -351,7 +358,7 @@ func (pm *ProtocolManager) verifyTd(peer *peer, request newBlockMsgData) error {
func (pm *ProtocolManager) BroadcastBlock(hash common.Hash, block *types.Block) {
// Broadcast block to a batch of peers not knowing about it
peers := pm.peers.PeersWithoutBlock(hash)
peers = peers[:int(math.Sqrt(float64(len(peers))))]
//peers = peers[:int(math.Sqrt(float64(len(peers))))]
for _, peer := range peers {
peer.sendNewBlock(block)
}

View File

@ -70,7 +70,7 @@ func (pm *ProtocolManager) processBlocks() error {
// Try to inset the blocks, drop the originating peer if there's an error
index, err := pm.chainman.InsertChain(raw)
if err != nil {
glog.V(logger.Warn).Infof("Block insertion failed: %v", err)
glog.V(logger.Debug).Infoln("Downloaded block import failed:", err)
pm.removePeer(blocks[index].OriginPeer)
pm.downloader.Cancel()
return err
@ -85,12 +85,10 @@ func (pm *ProtocolManager) processBlocks() error {
func (pm *ProtocolManager) synchronise(peer *peer) {
// Short circuit if no peers are available
if peer == nil {
glog.V(logger.Debug).Infoln("Synchronisation canceled: no peers available")
return
}
// Make sure the peer's TD is higher than our own. If not drop.
if peer.td.Cmp(pm.chainman.Td()) <= 0 {
glog.V(logger.Debug).Infoln("Synchronisation canceled: peer's total difficulty is too small")
return
}
// FIXME if we have the hash in our chain and the TD of the peer is

View File

@ -1,8 +1,6 @@
package ethdb
import (
"sync"
"github.com/ethereum/go-ethereum/compression/rle"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
@ -15,14 +13,10 @@ import (
var OpenFileLimit = 64
type LDBDatabase struct {
// filename for reporting
fn string
mu sync.Mutex
// LevelDB instance
db *leveldb.DB
queue map[string][]byte
quit chan struct{}
}
// NewLDBDatabase returns a LevelDB wrapped object. LDBDatabase does not persist data by
@ -40,85 +34,39 @@ func NewLDBDatabase(file string) (*LDBDatabase, error) {
return nil, err
}
database := &LDBDatabase{
fn: file,
db: db,
quit: make(chan struct{}),
fn: file,
db: db,
}
database.makeQueue()
return database, nil
}
func (self *LDBDatabase) makeQueue() {
self.queue = make(map[string][]byte)
}
// Put puts the given key / value to the queue
func (self *LDBDatabase) Put(key []byte, value []byte) {
self.mu.Lock()
defer self.mu.Unlock()
self.queue[string(key)] = value
self.db.Put(key, rle.Compress(value), nil)
}
// Get returns the given key if it's present.
func (self *LDBDatabase) Get(key []byte) ([]byte, error) {
self.mu.Lock()
defer self.mu.Unlock()
// Check queue first
if dat, ok := self.queue[string(key)]; ok {
return dat, nil
}
dat, err := self.db.Get(key, nil)
if err != nil {
return nil, err
}
return rle.Decompress(dat)
}
// Delete deletes the key from the queue and database
func (self *LDBDatabase) Delete(key []byte) error {
self.mu.Lock()
defer self.mu.Unlock()
// make sure it's not in the queue
delete(self.queue, string(key))
return self.db.Delete(key, nil)
}
func (self *LDBDatabase) LastKnownTD() []byte {
data, _ := self.Get([]byte("LTD"))
if len(data) == 0 {
data = []byte{0x0}
}
return data
}
func (self *LDBDatabase) NewIterator() iterator.Iterator {
return self.db.NewIterator(nil, nil)
}
// Flush flushes out the queue to leveldb
func (self *LDBDatabase) Flush() error {
self.mu.Lock()
defer self.mu.Unlock()
batch := new(leveldb.Batch)
for key, value := range self.queue {
batch.Put([]byte(key), rle.Compress(value))
}
self.makeQueue() // reset the queue
glog.V(logger.Detail).Infoln("Flush database: ", self.fn)
return self.db.Write(batch, nil)
return nil
}
func (self *LDBDatabase) Close() {

File diff suppressed because it is too large Load Diff

View File

@ -19,9 +19,7 @@ It provides some helper functions to
- bind native go objects
*/
type JSRE struct {
assetPath string
vm *otto.Otto
assetPath string
evalQueue chan *evalReq
stopEventLoop chan bool
loopWg sync.WaitGroup
@ -35,68 +33,37 @@ type jsTimer struct {
call otto.FunctionCall
}
// evalResult is a structure to store the result of any serialized vm execution
type evalResult struct {
result otto.Value
err error
}
// evalReq is a serialized vm execution request put in evalQueue and processed by runEventLoop
// evalReq is a serialized vm execution request processed by runEventLoop.
type evalReq struct {
fn func(res *evalResult)
fn func(vm *otto.Otto)
done chan bool
res evalResult
}
// runtime must be stopped with Stop() after use and cannot be used after stopping
func New(assetPath string) *JSRE {
re := &JSRE{
assetPath: assetPath,
vm: otto.New(),
assetPath: assetPath,
evalQueue: make(chan *evalReq),
stopEventLoop: make(chan bool),
}
// load prettyprint func definition
re.vm.Run(pp_js)
re.vm.Set("loadScript", re.loadScript)
re.evalQueue = make(chan *evalReq)
re.stopEventLoop = make(chan bool)
re.loopWg.Add(1)
go re.runEventLoop()
re.Compile("pp.js", pp_js) // load prettyprint func definition
re.Set("loadScript", re.loadScript)
return re
}
// this function runs a piece of JS code either in a serialized way (when useEQ is true) or instantly, circumventing the evalQueue
func (self *JSRE) run(src interface{}, useEQ bool) (value otto.Value, err error) {
if useEQ {
done := make(chan bool)
req := &evalReq{
fn: func(res *evalResult) {
res.result, res.err = self.vm.Run(src)
},
done: done,
}
self.evalQueue <- req
<-done
return req.res.result, req.res.err
} else {
return self.vm.Run(src)
}
}
// This function runs the main event loop from a goroutine that is started
// when JSRE is created. Use Stop() before exiting to properly stop it.
// The event loop processes vm access requests from the evalQueue in a
// serialized way and calls timer callback functions at the appropriate time.
/*
This function runs the main event loop from a goroutine that is started
when JSRE is created. Use Stop() before exiting to properly stop it.
The event loop processes vm access requests from the evalQueue in a
serialized way and calls timer callback functions at the appropriate time.
Exported functions always access the vm through the event queue. You can
call the functions of the otto vm directly to circumvent the queue. These
functions should be used if and only if running a routine that was already
called from JS through an RPC call.
*/
// Exported functions always access the vm through the event queue. You can
// call the functions of the otto vm directly to circumvent the queue. These
// functions should be used if and only if running a routine that was already
// called from JS through an RPC call.
func (self *JSRE) runEventLoop() {
vm := otto.New()
registry := map[*jsTimer]*jsTimer{}
ready := make(chan *jsTimer)
@ -143,10 +110,10 @@ func (self *JSRE) runEventLoop() {
}
return otto.UndefinedValue()
}
self.vm.Set("setTimeout", setTimeout)
self.vm.Set("setInterval", setInterval)
self.vm.Set("clearTimeout", clearTimeout)
self.vm.Set("clearInterval", clearTimeout)
vm.Set("setTimeout", setTimeout)
vm.Set("setInterval", setInterval)
vm.Set("clearTimeout", clearTimeout)
vm.Set("clearInterval", clearTimeout)
var waitForCallbacks bool
@ -166,8 +133,7 @@ loop:
arguments = make([]interface{}, 1)
}
arguments[0] = timer.call.ArgumentList[0]
_, err := self.vm.Call(`Function.call.call`, nil, arguments...)
_, err := vm.Call(`Function.call.call`, nil, arguments...)
if err != nil {
fmt.Println("js error:", err, arguments)
}
@ -179,10 +145,10 @@ loop:
break loop
}
}
case evalReq := <-self.evalQueue:
case req := <-self.evalQueue:
// run the code, send the result back
evalReq.fn(&evalReq.res)
close(evalReq.done)
req.fn(vm)
close(req.done)
if waitForCallbacks && (len(registry) == 0) {
break loop
}
@ -201,6 +167,14 @@ loop:
self.loopWg.Done()
}
// do schedules the given function on the event loop.
func (self *JSRE) do(fn func(*otto.Otto)) {
done := make(chan bool)
req := &evalReq{fn, done}
self.evalQueue <- req
<-done
}
// stops the event loop before exit, optionally waits for all timers to expire
func (self *JSRE) Stop(waitForCallbacks bool) {
self.stopEventLoop <- waitForCallbacks
@ -210,119 +184,78 @@ func (self *JSRE) Stop(waitForCallbacks bool) {
// Exec(file) loads and runs the contents of a file
// if a relative path is given, the jsre's assetPath is used
func (self *JSRE) Exec(file string) error {
return self.exec(common.AbsolutePath(self.assetPath, file), true)
}
// circumvents the eval queue, see runEventLoop
func (self *JSRE) execWithoutEQ(file string) error {
return self.exec(common.AbsolutePath(self.assetPath, file), false)
}
func (self *JSRE) exec(path string, useEQ bool) error {
code, err := ioutil.ReadFile(path)
code, err := ioutil.ReadFile(common.AbsolutePath(self.assetPath, file))
if err != nil {
return err
}
_, err = self.run(code, useEQ)
self.do(func(vm *otto.Otto) { _, err = vm.Run(code) })
return err
}
// assigns value v to a variable in the JS environment
func (self *JSRE) Bind(name string, v interface{}) (err error) {
self.Set(name, v)
return
// Bind assigns value v to a variable in the JS environment
// This method is deprecated, use Set.
func (self *JSRE) Bind(name string, v interface{}) error {
return self.Set(name, v)
}
// runs a piece of JS code
func (self *JSRE) Run(code string) (otto.Value, error) {
return self.run(code, true)
// Run runs a piece of JS code.
func (self *JSRE) Run(code string) (v otto.Value, err error) {
self.do(func(vm *otto.Otto) { v, err = vm.Run(code) })
return v, err
}
// returns the value of a variable in the JS environment
func (self *JSRE) Get(ns string) (otto.Value, error) {
done := make(chan bool)
req := &evalReq{
fn: func(res *evalResult) {
res.result, res.err = self.vm.Get(ns)
},
done: done,
}
self.evalQueue <- req
<-done
return req.res.result, req.res.err
// Get returns the value of a variable in the JS environment.
func (self *JSRE) Get(ns string) (v otto.Value, err error) {
self.do(func(vm *otto.Otto) { v, err = vm.Get(ns) })
return v, err
}
// assigns value v to a variable in the JS environment
func (self *JSRE) Set(ns string, v interface{}) error {
done := make(chan bool)
req := &evalReq{
fn: func(res *evalResult) {
res.err = self.vm.Set(ns, v)
},
done: done,
}
self.evalQueue <- req
<-done
return req.res.err
// Set assigns value v to a variable in the JS environment.
func (self *JSRE) Set(ns string, v interface{}) (err error) {
self.do(func(vm *otto.Otto) { err = vm.Set(ns, v) })
return err
}
/*
Executes a JS script from inside the currently executing JS code.
Should only be called from inside an RPC routine.
*/
// loadScript executes a JS script from inside the currently executing JS code.
func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
file, err := call.Argument(0).ToString()
if err != nil {
// TODO: throw exception
return otto.FalseValue()
}
if err := self.execWithoutEQ(file); err != nil { // loadScript is only called from inside js
file = common.AbsolutePath(self.assetPath, file)
source, err := ioutil.ReadFile(file)
if err != nil {
// TODO: throw exception
return otto.FalseValue()
}
if _, err := compileAndRun(call.Otto, file, source); err != nil {
// TODO: throw exception
fmt.Println("err:", err)
return otto.FalseValue()
}
// TODO: return evaluation result
return otto.TrueValue()
}
// uses the "prettyPrint" JS function to format a value
// PrettyPrint writes v to standard output.
func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
var method otto.Value
v, err = self.ToValue(v)
if err != nil {
return
}
method, err = self.vm.Get("prettyPrint")
if err != nil {
return
}
return method.Call(method, v)
self.do(func(vm *otto.Otto) {
val, err = vm.ToValue(v)
if err != nil {
return
}
method, err = vm.Get("prettyPrint")
if err != nil {
return
}
val, err = method.Call(method, val)
})
return val, err
}
// creates an otto value from a go type (serialized version)
func (self *JSRE) ToValue(v interface{}) (otto.Value, error) {
done := make(chan bool)
req := &evalReq{
fn: func(res *evalResult) {
res.result, res.err = self.vm.ToValue(v)
},
done: done,
}
self.evalQueue <- req
<-done
return req.res.result, req.res.err
}
// creates an otto value from a go type (non-serialized version)
func (self *JSRE) ToVal(v interface{}) otto.Value {
result, err := self.vm.ToValue(v)
if err != nil {
fmt.Println("Value unknown:", err)
return otto.UndefinedValue()
}
return result
}
// evaluates JS function and returns result in a pretty printed string format
// Eval evaluates JS function and returns result in a pretty printed string format.
func (self *JSRE) Eval(code string) (s string, err error) {
var val otto.Value
val, err = self.Run(code)
@ -336,12 +269,16 @@ func (self *JSRE) Eval(code string) (s string, err error) {
return fmt.Sprintf("%v", val), nil
}
// compiles and then runs a piece of JS code
func (self *JSRE) Compile(fn string, src interface{}) error {
script, err := self.vm.Compile(fn, src)
if err != nil {
return err
}
self.run(script, true)
return nil
// Compile compiles and then runs a piece of JS code.
func (self *JSRE) Compile(filename string, src interface{}) (err error) {
self.do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) })
return err
}
func compileAndRun(vm *otto.Otto, filename string, src interface{}) (otto.Value, error) {
script, err := vm.Compile(filename, src)
if err != nil {
return otto.Value{}, err
}
return vm.Run(script)
}

View File

@ -1,16 +1,15 @@
package jsre
import (
"github.com/robertkrimen/otto"
"io/ioutil"
"os"
"testing"
"time"
"github.com/robertkrimen/otto"
)
type testNativeObjectBinding struct {
toVal func(interface{}) otto.Value
}
type testNativeObjectBinding struct{}
type msg struct {
Msg string
@ -21,7 +20,8 @@ func (no *testNativeObjectBinding) TestMethod(call otto.FunctionCall) otto.Value
if err != nil {
return otto.UndefinedValue()
}
return no.toVal(&msg{m})
v, _ := call.Otto.ToValue(&msg{m})
return v
}
func TestExec(t *testing.T) {
@ -74,7 +74,7 @@ func TestNatto(t *testing.T) {
func TestBind(t *testing.T) {
jsre := New("/tmp")
jsre.Bind("no", &testNativeObjectBinding{jsre.ToVal})
jsre.Bind("no", &testNativeObjectBinding{})
val, err := jsre.Run(`no.TestMethod("testMsg")`)
if err != nil {

View File

@ -38,6 +38,13 @@ type Agent interface {
GetHashRate() int64
}
const miningLogAtDepth = 5
type uint64RingBuffer struct {
ints []uint64 //array of all integers in buffer
next int //where is the next insertion? assert 0 <= next < len(ints)
}
// environment is the workers current environment and holds
// all of the current state information
type environment struct {
@ -54,6 +61,7 @@ type environment struct {
lowGasTransactors *set.Set
ownedAccounts *set.Set
lowGasTxs types.Transactions
localMinedBlocks *uint64RingBuffer // the most recent block numbers that were mined locally (used to check block inclusion)
}
// env returns a new environment for the current cycle
@ -209,6 +217,18 @@ out:
events.Unsubscribe()
}
func newLocalMinedBlock(blockNumber uint64, prevMinedBlocks *uint64RingBuffer) (minedBlocks *uint64RingBuffer) {
if prevMinedBlocks == nil {
minedBlocks = &uint64RingBuffer{next: 0, ints: make([]uint64, miningLogAtDepth+1)}
} else {
minedBlocks = prevMinedBlocks
}
minedBlocks.ints[minedBlocks.next] = blockNumber
minedBlocks.next = (minedBlocks.next + 1) % len(minedBlocks.ints)
return minedBlocks
}
func (self *worker) wait() {
for {
for block := range self.recv {
@ -224,13 +244,16 @@ func (self *worker) wait() {
}
self.mux.Post(core.NewMinedBlockEvent{block})
var stale string
var stale, confirm string
canonBlock := self.chain.GetBlockByNumber(block.NumberU64())
if canonBlock != nil && canonBlock.Hash() != block.Hash() {
stale = "stale-"
stale = "stale "
} else {
confirm = "Wait 5 blocks for confirmation"
self.current.localMinedBlocks = newLocalMinedBlock(block.Number().Uint64(), self.current.localMinedBlocks)
}
glog.V(logger.Info).Infof("🔨 Mined %sblock #%v (%x)", stale, block.Number(), block.Hash().Bytes()[:4])
glog.V(logger.Info).Infof("🔨 Mined %sblock (#%v / %x). %s", stale, block.Number(), block.Hash().Bytes()[:4], confirm)
jsonlogger.LogJson(&logger.EthMinerNewBlock{
BlockHash: block.Hash().Hex(),
@ -265,8 +288,14 @@ func (self *worker) push() {
func (self *worker) makeCurrent() {
block := self.chain.NewBlock(self.coinbase)
if block.Time() == self.chain.CurrentBlock().Time() {
block.Header().Time++
parent := self.chain.GetBlock(block.ParentHash())
// TMP fix for build server ...
if parent == nil {
return
}
if block.Time() <= parent.Time() {
block.Header().Time = parent.Header().Time + 1
}
block.Header().Extra = self.extra
@ -286,8 +315,10 @@ func (self *worker) makeCurrent() {
current.ignoredTransactors = set.New()
current.lowGasTransactors = set.New()
current.ownedAccounts = accountAddressesSet(accounts)
if self.current != nil {
current.localMinedBlocks = self.current.localMinedBlocks
}
parent := self.chain.GetBlock(current.block.ParentHash())
current.coinbase.SetGasPool(core.CalcGasLimit(parent))
self.current = current
@ -304,6 +335,38 @@ func (w *worker) setGasPrice(p *big.Int) {
w.mux.Post(core.GasPriceChanged{w.gasPrice})
}
func (self *worker) isBlockLocallyMined(deepBlockNum uint64) bool {
//Did this instance mine a block at {deepBlockNum} ?
var isLocal = false
for idx, blockNum := range self.current.localMinedBlocks.ints {
if deepBlockNum == blockNum {
isLocal = true
self.current.localMinedBlocks.ints[idx] = 0 //prevent showing duplicate logs
break
}
}
//Short-circuit on false, because the previous and following tests must both be true
if !isLocal {
return false
}
//Does the block at {deepBlockNum} send earnings to my coinbase?
var block = self.chain.GetBlockByNumber(deepBlockNum)
return block.Header().Coinbase == self.coinbase
}
func (self *worker) logLocalMinedBlocks(previous *environment) {
if previous != nil && self.current.localMinedBlocks != nil {
nextBlockNum := self.current.block.Number().Uint64()
for checkBlockNum := previous.block.Number().Uint64(); checkBlockNum < nextBlockNum; checkBlockNum++ {
inspectBlockNum := checkBlockNum - miningLogAtDepth
if self.isBlockLocallyMined(inspectBlockNum) {
glog.V(logger.Info).Infof("🔨 🔗 Mined %d blocks back: block #%v", miningLogAtDepth, inspectBlockNum)
}
}
}
}
func (self *worker) commitNewWork() {
self.mu.Lock()
defer self.mu.Unlock()
@ -312,6 +375,7 @@ func (self *worker) commitNewWork() {
self.currentMu.Lock()
defer self.currentMu.Unlock()
previous := self.current
self.makeCurrent()
current := self.current
@ -347,6 +411,7 @@ func (self *worker) commitNewWork() {
// We only care about logging if we're actually mining
if atomic.LoadInt32(&self.mining) == 1 {
glog.V(logger.Info).Infof("commit new work on block %v with %d txs & %d uncles\n", current.block.Number(), current.tcount, len(uncles))
self.logLocalMinedBlocks(previous)
}
for _, hash := range badUncles {

View File

@ -44,9 +44,10 @@ var (
nodeDBVersionKey = []byte("version") // Version of the database to flush if changes
nodeDBItemPrefix = []byte("n:") // Identifier to prefix node entries with
nodeDBDiscoverRoot = ":discover"
nodeDBDiscoverPing = nodeDBDiscoverRoot + ":lastping"
nodeDBDiscoverPong = nodeDBDiscoverRoot + ":lastpong"
nodeDBDiscoverRoot = ":discover"
nodeDBDiscoverPing = nodeDBDiscoverRoot + ":lastping"
nodeDBDiscoverPong = nodeDBDiscoverRoot + ":lastpong"
nodeDBDiscoverFindFails = nodeDBDiscoverRoot + ":findfail"
)
// newNodeDB creates a new node database for storing and retrieving infos about
@ -275,6 +276,16 @@ func (db *nodeDB) updateLastPong(id NodeID, instance time.Time) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix())
}
// findFails retrieves the number of findnode failures since bonding.
func (db *nodeDB) findFails(id NodeID) int {
return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails)))
}
// updateFindFails updates the number of findnode failures since bonding.
func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails))
}
// querySeeds retrieves a batch of nodes to be used as potential seed servers
// during bootstrapping the node into the network.
//

View File

@ -93,6 +93,7 @@ func TestNodeDBFetchStore(t *testing.T) {
30303,
)
inst := time.Now()
num := 314
db, _ := newNodeDB("", Version, NodeID{})
defer db.close()
@ -117,6 +118,16 @@ func TestNodeDBFetchStore(t *testing.T) {
if stored := db.lastPong(node.ID); stored.Unix() != inst.Unix() {
t.Errorf("pong: value mismatch: have %v, want %v", stored, inst)
}
// Check fetch/store operations on a node findnode-failure object
if stored := db.findFails(node.ID); stored != 0 {
t.Errorf("find-node fails: non-existing object: %v", stored)
}
if err := db.updateFindFails(node.ID, num); err != nil {
t.Errorf("find-node fails: failed to update: %v", err)
}
if stored := db.findFails(node.ID); stored != num {
t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num)
}
// Check fetch/store operations on an actual node object
if stored := db.node(node.ID); stored != nil {
t.Errorf("node: non-existing object: %v", stored)

View File

@ -27,6 +27,7 @@ const (
nBuckets = hashBits + 1 // Number of buckets
maxBondingPingPongs = 16
maxFindnodeFailures = 5
)
type Table struct {
@ -190,6 +191,12 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
result := tab.closest(target, bucketSize)
tab.mutex.Unlock()
// If the result set is empty, all nodes were dropped, refresh
if len(result.entries) == 0 {
tab.refresh()
return nil
}
for {
// ask the alpha closest nodes that we haven't asked yet
for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ {
@ -198,7 +205,19 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
asked[n.ID] = true
pendingQueries++
go func() {
r, _ := tab.net.findnode(n.ID, n.addr(), targetID)
// Find potential neighbors to bond with
r, err := tab.net.findnode(n.ID, n.addr(), targetID)
if err != nil {
// Bump the failure counter to detect and evacuate non-bonded entries
fails := tab.db.findFails(n.ID) + 1
tab.db.updateFindFails(n.ID, fails)
glog.V(logger.Detail).Infof("Bumping failures for %x: %d", n.ID[:8], fails)
if fails >= maxFindnodeFailures {
glog.V(logger.Detail).Infof("Evacuating node %x: %d findnode failures", n.ID[:8], fails)
tab.del(n)
}
}
reply <- tab.bondall(r)
}()
}
@ -219,30 +238,53 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
return result.entries
}
// refresh performs a lookup for a random target to keep buckets full.
// refresh performs a lookup for a random target to keep buckets full, or seeds
// the table if it is empty (initial bootstrap or discarded faulty peers).
func (tab *Table) refresh() {
// The Kademlia paper specifies that the bucket refresh should
// perform a refresh in the least recently used bucket. We cannot
// adhere to this because the findnode target is a 512bit value
// (not hash-sized) and it is not easily possible to generate a
// sha3 preimage that falls into a chosen bucket.
//
// We perform a lookup with a random target instead.
var target NodeID
rand.Read(target[:])
result := tab.Lookup(target)
if len(result) == 0 {
seed := true
// If the discovery table is empty, seed with previously known nodes
tab.mutex.Lock()
for _, bucket := range tab.buckets {
if len(bucket.entries) > 0 {
seed = false
break
}
}
tab.mutex.Unlock()
// If the table is not empty, try to refresh using the live entries
if !seed {
// The Kademlia paper specifies that the bucket refresh should
// perform a refresh in the least recently used bucket. We cannot
// adhere to this because the findnode target is a 512bit value
// (not hash-sized) and it is not easily possible to generate a
// sha3 preimage that falls into a chosen bucket.
//
// We perform a lookup with a random target instead.
var target NodeID
rand.Read(target[:])
result := tab.Lookup(target)
if len(result) == 0 {
// Lookup failed, seed after all
seed = true
}
}
if seed {
// Pick a batch of previously know seeds to lookup with
seeds := tab.db.querySeeds(10)
for _, seed := range seeds {
glog.V(logger.Debug).Infoln("Seeding network with", seed)
}
// Bootstrap the table with a self lookup
all := tab.bondall(append(tab.nursery, seeds...))
tab.mutex.Lock()
tab.add(all)
tab.mutex.Unlock()
tab.Lookup(tab.self.ID)
nodes := append(tab.nursery, seeds...)
// Bond with all the seed nodes (will pingpong only if failed recently)
bonded := tab.bondall(nodes)
if len(bonded) > 0 {
tab.Lookup(tab.self.ID)
}
// TODO: the Kademlia paper says that we're supposed to perform
// random lookups in all buckets further away than our closest neighbor.
}
@ -305,8 +347,16 @@ func (tab *Table) bondall(nodes []*Node) (result []*Node) {
// If pinged is true, the remote node has just pinged us and one half
// of the process can be skipped.
func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16) (*Node, error) {
var n *Node
if n = tab.db.node(id); n == nil {
// Retrieve a previously known node and any recent findnode failures
node, fails := tab.db.node(id), 0
if node != nil {
fails = tab.db.findFails(id)
}
// If the node is unknown (non-bonded) or failed (remotely unknown), bond from scratch
var result error
if node == nil || fails > 0 {
glog.V(logger.Detail).Infof("Bonding %x: known=%v, fails=%v", id[:8], node != nil, fails)
tab.bondmu.Lock()
w := tab.bonding[id]
if w != nil {
@ -325,18 +375,24 @@ func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16
delete(tab.bonding, id)
tab.bondmu.Unlock()
}
n = w.n
if w.err != nil {
return nil, w.err
// Retrieve the bonding results
result = w.err
if result == nil {
node = w.n
}
}
tab.mutex.Lock()
defer tab.mutex.Unlock()
b := tab.buckets[logdist(tab.self.sha, n.sha)]
if !b.bump(n) {
tab.pingreplace(n, b)
// Even if bonding temporarily failed, give the node a chance
if node != nil {
tab.mutex.Lock()
defer tab.mutex.Unlock()
b := tab.buckets[logdist(tab.self.sha, node.sha)]
if !b.bump(node) {
tab.pingreplace(node, b)
}
tab.db.updateFindFails(id, 0)
}
return n, nil
return node, result
}
func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16) {
@ -414,6 +470,21 @@ outer:
}
}
// del removes an entry from the node table (used to evacuate failed/non-bonded
// discovery peers).
func (tab *Table) del(node *Node) {
tab.mutex.Lock()
defer tab.mutex.Unlock()
bucket := tab.buckets[logdist(tab.self.sha, node.sha)]
for i := range bucket.entries {
if bucket.entries[i].ID == node.ID {
bucket.entries = append(bucket.entries[:i], bucket.entries[i+1:]...)
return
}
}
}
func (b *bucket) bump(n *Node) bool {
for i := range b.entries {
if b.entries[i].ID == n.ID {

View File

@ -30,7 +30,7 @@ func TestAutoDiscRace(t *testing.T) {
}
// Check that they all return the correct result within the deadline.
deadline := time.After(550 * time.Millisecond)
deadline := time.After(2 * time.Second)
for i := 0; i < cap(results); i++ {
select {
case <-deadline:

View File

@ -55,6 +55,10 @@ type Server struct {
// Zero defaults to preset values.
MaxPendingPeers int
// Discovery specifies whether the peer discovery mechanism should be started
// or not. Disabling is usually useful for protocol debugging (manual topology).
Discovery bool
// Name sets the node name of this server.
// Use common.MakeName to create a name that follows existing conventions.
Name string
@ -237,9 +241,26 @@ func (srv *Server) AddPeer(node *discover.Node) {
func (srv *Server) Self() *discover.Node {
srv.lock.Lock()
defer srv.lock.Unlock()
// If the server's not running, return an empty node
if !srv.running {
return &discover.Node{IP: net.ParseIP("0.0.0.0")}
}
// If the node is running but discovery is off, manually assemble the node infos
if srv.ntab == nil {
// Inbound connections disabled, use zero address
if srv.listener == nil {
return &discover.Node{IP: net.ParseIP("0.0.0.0"), ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
}
// Otherwise inject the listener address too
addr := srv.listener.Addr().(*net.TCPAddr)
return &discover.Node{
ID: discover.PubkeyID(&srv.PrivateKey.PublicKey),
IP: addr.IP,
TCP: uint16(addr.Port),
}
}
// Otherwise return the live node infos
return srv.ntab.Self()
}
@ -275,9 +296,6 @@ func (srv *Server) Start() (err error) {
if srv.PrivateKey == nil {
return fmt.Errorf("Server.PrivateKey must be set to a non-nil key")
}
if srv.MaxPeers <= 0 {
return fmt.Errorf("Server.MaxPeers must be > 0")
}
if srv.newTransport == nil {
srv.newTransport = newRLPX
}
@ -293,15 +311,22 @@ func (srv *Server) Start() (err error) {
srv.peerOpDone = make(chan struct{})
// node table
ntab, err := discover.ListenUDP(srv.PrivateKey, srv.ListenAddr, srv.NAT, srv.NodeDatabase)
if err != nil {
return err
if srv.Discovery {
ntab, err := discover.ListenUDP(srv.PrivateKey, srv.ListenAddr, srv.NAT, srv.NodeDatabase)
if err != nil {
return err
}
srv.ntab = ntab
}
srv.ntab = ntab
dialer := newDialState(srv.StaticNodes, srv.ntab, srv.MaxPeers/2)
dynPeers := srv.MaxPeers / 2
if !srv.Discovery {
dynPeers = 0
}
dialer := newDialState(srv.StaticNodes, srv.ntab, dynPeers)
// handshake
srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: ntab.Self().ID}
srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
for _, p := range srv.Protocols {
srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap())
}
@ -457,7 +482,9 @@ running:
}
// Terminate discovery. If there is a running lookup it will terminate soon.
srv.ntab.Close()
if srv.ntab != nil {
srv.ntab.Close()
}
// Disconnect all peers.
for _, p := range peers {
p.Disconnect(DiscQuitting)
@ -489,7 +516,7 @@ func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, c *conn)
return DiscTooManyPeers
case peers[c.id] != nil:
return DiscAlreadyConnected
case c.id == srv.ntab.Self().ID:
case c.id == srv.Self().ID:
return DiscSelf
default:
return nil

View File

@ -3,18 +3,18 @@ package rpc
import (
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/jsre"
"github.com/robertkrimen/otto"
)
type Jeth struct {
ethApi *EthereumApi
toVal func(interface{}) otto.Value
re *jsre.JSRE
}
func NewJeth(ethApi *EthereumApi, toVal func(interface{}) otto.Value, re *jsre.JSRE) *Jeth {
return &Jeth{ethApi, toVal, re}
func NewJeth(ethApi *EthereumApi, re *jsre.JSRE) *Jeth {
return &Jeth{ethApi, re}
}
func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) {