diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index bd342a8cb9..fe7dea4da1 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -428,10 +428,23 @@ func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumb
}
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
}
+
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil
}
+func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
+ receipts := core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash))
+ if receipts == nil {
+ return nil, nil
+ }
+ logs := make([][]*types.Log, len(receipts))
+ for i, receipt := range receipts {
+ logs[i] = receipt.Logs
+ }
+ return logs, nil
+}
+
func (fb *filterBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription {
return event.NewSubscription(func(quit <-chan struct{}) error {
<-quit
diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go
index 99527f9d1e..095668c868 100644
--- a/cmd/faucet/faucet.go
+++ b/cmd/faucet/faucet.go
@@ -686,8 +686,6 @@ func authTwitter(url string) (string, string, common.Address, error) {
if len(parts) < 4 || parts[len(parts)-2] != "status" {
return "", "", common.Address{}, errors.New("Invalid Twitter status URL")
}
- username := parts[len(parts)-3]
-
// Twitter's API isn't really friendly with direct links. Still, we don't
// want to do ask read permissions from users, so just load the public posts and
// scrape it for the Ethereum address and profile URL.
@@ -697,6 +695,13 @@ func authTwitter(url string) (string, string, common.Address, error) {
}
defer res.Body.Close()
+ // Resolve the username from the final redirect, no intermediate junk
+ parts = strings.Split(res.Request.URL.String(), "/")
+ if len(parts) < 4 || parts[len(parts)-2] != "status" {
+ return "", "", common.Address{}, errors.New("Invalid Twitter status URL")
+ }
+ username := parts[len(parts)-3]
+
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", common.Address{}, err
diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go
index 7eca4d59f6..2500a969cf 100644
--- a/cmd/geth/consolecmd.go
+++ b/cmd/geth/consolecmd.go
@@ -43,7 +43,7 @@ var (
Description: `
The Geth console is an interactive shell for the JavaScript runtime environment
which exposes a node admin interface as well as the Ðapp JavaScript API.
-See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.`,
+See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.`,
}
attachCommand = cli.Command{
@@ -56,7 +56,7 @@ See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.`,
Description: `
The Geth console is an interactive shell for the JavaScript runtime environment
which exposes a node admin interface as well as the Ðapp JavaScript API.
-See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.
+See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.
This command allows to open a console on a running geth node.`,
}
@@ -69,7 +69,7 @@ This command allows to open a console on a running geth node.`,
Category: "CONSOLE COMMANDS",
Description: `
The JavaScript VM exposes a node admin interface as well as the Ðapp
-JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console`,
+JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console`,
}
)
diff --git a/cmd/puppeth/module_dashboard.go b/cmd/puppeth/module_dashboard.go
index 1cb2d45491..3832b247f8 100644
--- a/cmd/puppeth/module_dashboard.go
+++ b/cmd/puppeth/module_dashboard.go
@@ -631,6 +631,7 @@ func deployDashboard(client *sshClient, network string, conf *config, config *da
"Tangerine": conf.Genesis.Config.EIP150Block,
"Spurious": conf.Genesis.Config.EIP155Block,
"Byzantium": conf.Genesis.Config.ByzantiumBlock,
+ "Constantinople": conf.Genesis.Config.ConstantinopleBlock,
})
files[filepath.Join(workdir, "index.html")] = indexfile.Bytes()
diff --git a/cmd/swarm/config.go b/cmd/swarm/config.go
index c2209a3204..2bafe97228 100644
--- a/cmd/swarm/config.go
+++ b/cmd/swarm/config.go
@@ -23,6 +23,7 @@ import (
"os"
"reflect"
"strconv"
+ "strings"
"unicode"
cli "gopkg.in/urfave/cli.v1"
@@ -102,10 +103,15 @@ func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
config = bzzapi.NewConfig()
//first load settings from config file (if provided)
config, err = configFileOverride(config, ctx)
+ if err != nil {
+ return nil, err
+ }
//override settings provided by environment variables
config = envVarsOverride(config)
//override settings provided by command line
config = cmdLineOverride(config, ctx)
+ //validate configuration parameters
+ err = validateConfig(config)
return
}
@@ -199,12 +205,16 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
}
- //EnsApi can be set to "", so can't check for empty string, as it is allowed!
if ctx.GlobalIsSet(EnsAPIFlag.Name) {
- currentConfig.EnsApi = ctx.GlobalString(EnsAPIFlag.Name)
+ ensAPIs := ctx.GlobalStringSlice(EnsAPIFlag.Name)
+ // preserve backward compatibility to disable ENS with --ens-api=""
+ if len(ensAPIs) == 1 && ensAPIs[0] == "" {
+ ensAPIs = nil
+ }
+ currentConfig.EnsAPIs = ensAPIs
}
- if ensaddr := ctx.GlobalString(EnsAddrFlag.Name); ensaddr != "" {
+ if ensaddr := ctx.GlobalString(DeprecatedEnsAddrFlag.Name); ensaddr != "" {
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
}
@@ -291,9 +301,8 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
}
- //EnsApi can be set to "", so can't check for empty string, as it is allowed
- if ensapi, exists := os.LookupEnv(SWARM_ENV_ENS_API); exists {
- currentConfig.EnsApi = ensapi
+ if ensapi := os.Getenv(SWARM_ENV_ENS_API); ensapi != "" {
+ currentConfig.EnsAPIs = strings.Split(ensapi, ",")
}
if ensaddr := os.Getenv(SWARM_ENV_ENS_ADDR); ensaddr != "" {
@@ -340,6 +349,43 @@ func checkDeprecated(ctx *cli.Context) {
if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" {
utils.Fatalf("--ethapi is no longer a valid command line flag, please use --ens-api and/or --swap-api.")
}
+ // warn if --ens-api flag is set
+ if ctx.GlobalString(DeprecatedEnsAddrFlag.Name) != "" {
+ log.Warn("--ens-addr is no longer a valid command line flag, please use --ens-api to specify contract address.")
+ }
+}
+
+//validate configuration parameters
+func validateConfig(cfg *bzzapi.Config) (err error) {
+ for _, ensAPI := range cfg.EnsAPIs {
+ if ensAPI != "" {
+ if err := validateEnsAPIs(ensAPI); err != nil {
+ return fmt.Errorf("invalid format [tld:][contract-addr@]url for ENS API endpoint configuration %q: %v", ensAPI, err)
+ }
+ }
+ }
+ return nil
+}
+
+//validate EnsAPIs configuration parameter
+func validateEnsAPIs(s string) (err error) {
+ // missing contract address
+ if strings.HasPrefix(s, "@") {
+ return errors.New("missing contract address")
+ }
+ // missing url
+ if strings.HasSuffix(s, "@") {
+ return errors.New("missing url")
+ }
+ // missing tld
+ if strings.HasPrefix(s, ":") {
+ return errors.New("missing tld")
+ }
+ // missing url
+ if strings.HasSuffix(s, ":") {
+ return errors.New("missing url")
+ }
+ return nil
}
//print a Config as string
diff --git a/cmd/swarm/config_test.go b/cmd/swarm/config_test.go
index 81d1df585d..57396d1408 100644
--- a/cmd/swarm/config_test.go
+++ b/cmd/swarm/config_test.go
@@ -478,3 +478,98 @@ func TestConfigCmdLineOverridesFile(t *testing.T) {
node.Shutdown()
}
+
+func TestValidateConfig(t *testing.T) {
+ for _, c := range []struct {
+ cfg *api.Config
+ err string
+ }{
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "/data/testnet/geth.ipc",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "http://127.0.0.1:1234",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "ws://127.0.0.1:1234",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "test:/data/testnet/geth.ipc",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "test:ws://127.0.0.1:1234",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/geth.ipc",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:1234",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "test:314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/geth.ipc",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "eth:314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "eth:314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:12344",
+ }},
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "eth:",
+ }},
+ err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"eth:\": missing url",
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "314159265dD8dbb310642f98f50C066173C1259b@",
+ }},
+ err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"314159265dD8dbb310642f98f50C066173C1259b@\": missing url",
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ ":314159265dD8dbb310642f98f50C066173C1259",
+ }},
+ err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \":314159265dD8dbb310642f98f50C066173C1259\": missing tld",
+ },
+ {
+ cfg: &api.Config{EnsAPIs: []string{
+ "@/data/testnet/geth.ipc",
+ }},
+ err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"@/data/testnet/geth.ipc\": missing contract address",
+ },
+ } {
+ err := validateConfig(c.cfg)
+ if c.err != "" && err.Error() != c.err {
+ t.Errorf("expected error %q, got %q", c.err, err)
+ }
+ if c.err == "" && err != nil {
+ t.Errorf("unexpected error %q", err)
+ }
+ }
+}
diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go
index 3ae45d4c2f..6c5693d4d2 100644
--- a/cmd/swarm/main.go
+++ b/cmd/swarm/main.go
@@ -17,11 +17,9 @@
package main
import (
- "context"
"crypto/ecdsa"
"fmt"
"io/ioutil"
- "math/big"
"os"
"os/signal"
"runtime"
@@ -29,14 +27,12 @@ import (
"strconv"
"strings"
"syscall"
- "time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console"
- "github.com/ethereum/go-ethereum/contracts/ens"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug"
@@ -45,9 +41,9 @@ import (
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm"
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
+ swarmmetrics "github.com/ethereum/go-ethereum/swarm/metrics"
"gopkg.in/urfave/cli.v1"
)
@@ -110,16 +106,11 @@ var (
Usage: "Swarm Syncing enabled (default true)",
EnvVar: SWARM_ENV_SYNC_ENABLE,
}
- EnsAPIFlag = cli.StringFlag{
+ EnsAPIFlag = cli.StringSliceFlag{
Name: "ens-api",
- Usage: "URL of the Ethereum API provider to use for ENS record lookups",
+ Usage: "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url",
EnvVar: SWARM_ENV_ENS_API,
}
- EnsAddrFlag = cli.StringFlag{
- Name: "ens-addr",
- Usage: "ENS contract address (default is detected as testnet or mainnet using --ens-api)",
- EnvVar: SWARM_ENV_ENS_ADDR,
- }
SwarmApiFlag = cli.StringFlag{
Name: "bzzapi",
Usage: "Swarm HTTP endpoint",
@@ -180,6 +171,10 @@ var (
Name: "ethapi",
Usage: "DEPRECATED: please use --ens-api and --swap-api",
}
+ DeprecatedEnsAddrFlag = cli.StringFlag{
+ Name: "ens-addr",
+ Usage: "DEPRECATED: ENS contract address, please use --ens-api with contract address according to its format",
+ }
)
//declare a few constant error messages, useful for later error check comparisons in test
@@ -367,7 +362,6 @@ DEPRECATED: use 'swarm db clean'.
// bzzd-specific flags
CorsStringFlag,
EnsAPIFlag,
- EnsAddrFlag,
SwarmTomlConfigPathFlag,
SwarmConfigPathFlag,
SwarmSwapEnabledFlag,
@@ -394,6 +388,7 @@ DEPRECATED: use 'swarm db clean'.
SwarmStoreRadius,
//deprecated flags
DeprecatedEthAPIFlag,
+ DeprecatedEnsAddrFlag,
}
rpcFlags := []cli.Flag{
utils.WSEnabledFlag,
@@ -404,9 +399,14 @@ DEPRECATED: use 'swarm db clean'.
}
app.Flags = append(app.Flags, rpcFlags...)
app.Flags = append(app.Flags, debug.Flags...)
+ app.Flags = append(app.Flags, swarmmetrics.Flags...)
app.Before = func(ctx *cli.Context) error {
runtime.GOMAXPROCS(runtime.NumCPU())
- return debug.Setup(ctx)
+ if err := debug.Setup(ctx); err != nil {
+ return err
+ }
+ swarmmetrics.Setup(ctx)
+ return nil
}
app.After = func(ctx *cli.Context) error {
debug.Exit()
@@ -493,38 +493,6 @@ func bzzd(ctx *cli.Context) error {
return nil
}
-// detectEnsAddr determines the ENS contract address by getting both the
-// version and genesis hash using the client and matching them to either
-// mainnet or testnet addresses
-func detectEnsAddr(client *rpc.Client) (common.Address, error) {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- var version string
- if err := client.CallContext(ctx, &version, "net_version"); err != nil {
- return common.Address{}, err
- }
-
- block, err := ethclient.NewClient(client).BlockByNumber(ctx, big.NewInt(0))
- if err != nil {
- return common.Address{}, err
- }
-
- switch {
-
- case version == "1" && block.Hash() == params.MainnetGenesisHash:
- log.Info("using Mainnet ENS contract address", "addr", ens.MainNetAddress)
- return ens.MainNetAddress, nil
-
- case version == "3" && block.Hash() == params.TestnetGenesisHash:
- log.Info("using Testnet ENS contract address", "addr", ens.TestNetAddress)
- return ens.TestNetAddress, nil
-
- default:
- return common.Address{}, fmt.Errorf("unknown version and genesis hash: %s %s", version, block.Hash())
- }
-}
-
func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.Node) {
//define the swarm service boot function
@@ -539,28 +507,8 @@ func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.
}
}
- var ensClient *ethclient.Client
- if bzzconfig.EnsApi != "" {
- log.Info("connecting to ENS API", "url", bzzconfig.EnsApi)
- client, err := rpc.Dial(bzzconfig.EnsApi)
- if err != nil {
- return nil, fmt.Errorf("error connecting to ENS API %s: %s", bzzconfig.EnsApi, err)
- }
- ensClient = ethclient.NewClient(client)
-
- //no ENS root address set yet
- if bzzconfig.EnsRoot == (common.Address{}) {
- ensAddr, err := detectEnsAddr(client)
- if err == nil {
- bzzconfig.EnsRoot = ensAddr
- } else {
- log.Warn(fmt.Sprintf("could not determine ENS contract address, using default %s", bzzconfig.EnsRoot), "err", err)
- }
- }
- }
-
// In production, mockStore must be always nil.
- return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, nil)
+ return swarm.NewSwarm(ctx, swapClient, bzzconfig, nil)
}
//register within the ethereum node
if err := stack.Register(boot); err != nil {
diff --git a/common/big.go b/common/big.go
index b552608bc7..65d4377bf7 100644
--- a/common/big.go
+++ b/common/big.go
@@ -25,6 +25,6 @@ var (
Big3 = big.NewInt(3)
Big0 = big.NewInt(0)
Big32 = big.NewInt(32)
- Big256 = big.NewInt(0xff)
+ Big256 = big.NewInt(256)
Big257 = big.NewInt(257)
)
diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go
index 91e20112ae..1b3dcee302 100644
--- a/consensus/ethash/ethash.go
+++ b/consensus/ethash/ethash.go
@@ -35,9 +35,9 @@ import (
mmap "github.com/edsrzf/mmap-go"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rpc"
"github.com/hashicorp/golang-lru/simplelru"
- metrics "github.com/rcrowley/go-metrics"
)
var ErrInvalidDumpMagic = errors.New("invalid dump magic")
diff --git a/core/blockchain.go b/core/blockchain.go
index 4ae0e4f4ec..6006e66740 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -46,7 +46,7 @@ import (
)
var (
- blockInsertTimer = metrics.NewTimer("chain/inserts")
+ blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil)
ErrNoGenesis = errors.New("Genesis not found in chain")
)
@@ -107,8 +107,8 @@ type BlockChain struct {
procmu sync.RWMutex // block processor lock
checkpoint int // checkpoint counts towards the new checkpoint
- currentBlock *types.Block // Current head of the block chain
- currentFastBlock *types.Block // Current head of the fast-sync chain (may be above the block chain!)
+ currentBlock atomic.Value // Current head of the block chain
+ currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!)
stateCache state.Database // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies
@@ -224,10 +224,10 @@ func (bc *BlockChain) loadLastState() error {
}
}
// Everything seems to be fine, set as the head block
- bc.currentBlock = currentBlock
+ bc.currentBlock.Store(currentBlock)
// Restore the last known head header
- currentHeader := bc.currentBlock.Header()
+ currentHeader := currentBlock.Header()
if head := GetHeadHeaderHash(bc.db); head != (common.Hash{}) {
if header := bc.GetHeaderByHash(head); header != nil {
currentHeader = header
@@ -236,21 +236,23 @@ func (bc *BlockChain) loadLastState() error {
bc.hc.SetCurrentHeader(currentHeader)
// Restore the last known head fast block
- bc.currentFastBlock = bc.currentBlock
+ bc.currentFastBlock.Store(currentBlock)
if head := GetHeadFastBlockHash(bc.db); head != (common.Hash{}) {
if block := bc.GetBlockByHash(head); block != nil {
- bc.currentFastBlock = block
+ bc.currentFastBlock.Store(block)
}
}
// Issue a status log for the user
+ currentFastBlock := bc.CurrentFastBlock()
+
headerTd := bc.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64())
- blockTd := bc.GetTd(bc.currentBlock.Hash(), bc.currentBlock.NumberU64())
- fastTd := bc.GetTd(bc.currentFastBlock.Hash(), bc.currentFastBlock.NumberU64())
+ blockTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
+ fastTd := bc.GetTd(currentFastBlock.Hash(), currentFastBlock.NumberU64())
log.Info("Loaded most recent local header", "number", currentHeader.Number, "hash", currentHeader.Hash(), "td", headerTd)
- log.Info("Loaded most recent local full block", "number", bc.currentBlock.Number(), "hash", bc.currentBlock.Hash(), "td", blockTd)
- log.Info("Loaded most recent local fast block", "number", bc.currentFastBlock.Number(), "hash", bc.currentFastBlock.Hash(), "td", fastTd)
+ log.Info("Loaded most recent local full block", "number", currentBlock.Number(), "hash", currentBlock.Hash(), "td", blockTd)
+ log.Info("Loaded most recent local fast block", "number", currentFastBlock.Number(), "hash", currentFastBlock.Hash(), "td", fastTd)
return nil
}
@@ -279,30 +281,32 @@ func (bc *BlockChain) SetHead(head uint64) error {
bc.futureBlocks.Purge()
// Rewind the block chain, ensuring we don't end up with a stateless head block
- if bc.currentBlock != nil && currentHeader.Number.Uint64() < bc.currentBlock.NumberU64() {
- bc.currentBlock = bc.GetBlock(currentHeader.Hash(), currentHeader.Number.Uint64())
+ if currentBlock := bc.CurrentBlock(); currentBlock != nil && currentHeader.Number.Uint64() < currentBlock.NumberU64() {
+ bc.currentBlock.Store(bc.GetBlock(currentHeader.Hash(), currentHeader.Number.Uint64()))
}
- if bc.currentBlock != nil {
- if _, err := state.New(bc.currentBlock.Root(), bc.stateCache); err != nil {
+ if currentBlock := bc.CurrentBlock(); currentBlock != nil {
+ if _, err := state.New(currentBlock.Root(), bc.stateCache); err != nil {
// Rewound state missing, rolled back to before pivot, reset to genesis
- bc.currentBlock = nil
+ bc.currentBlock.Store(bc.genesisBlock)
}
}
// Rewind the fast block in a simpleton way to the target head
- if bc.currentFastBlock != nil && currentHeader.Number.Uint64() < bc.currentFastBlock.NumberU64() {
- bc.currentFastBlock = bc.GetBlock(currentHeader.Hash(), currentHeader.Number.Uint64())
+ if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock != nil && currentHeader.Number.Uint64() < currentFastBlock.NumberU64() {
+ bc.currentFastBlock.Store(bc.GetBlock(currentHeader.Hash(), currentHeader.Number.Uint64()))
}
// If either blocks reached nil, reset to the genesis state
- if bc.currentBlock == nil {
- bc.currentBlock = bc.genesisBlock
+ if currentBlock := bc.CurrentBlock(); currentBlock == nil {
+ bc.currentBlock.Store(bc.genesisBlock)
}
- if bc.currentFastBlock == nil {
- bc.currentFastBlock = bc.genesisBlock
+ if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock == nil {
+ bc.currentFastBlock.Store(bc.genesisBlock)
}
- if err := WriteHeadBlockHash(bc.db, bc.currentBlock.Hash()); err != nil {
+ currentBlock := bc.CurrentBlock()
+ currentFastBlock := bc.CurrentFastBlock()
+ if err := WriteHeadBlockHash(bc.db, currentBlock.Hash()); err != nil {
log.Crit("Failed to reset head full block", "err", err)
}
- if err := WriteHeadFastBlockHash(bc.db, bc.currentFastBlock.Hash()); err != nil {
+ if err := WriteHeadFastBlockHash(bc.db, currentFastBlock.Hash()); err != nil {
log.Crit("Failed to reset head fast block", "err", err)
}
return bc.loadLastState()
@@ -321,7 +325,7 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {
}
// If all checks out, manually set the head block
bc.mu.Lock()
- bc.currentBlock = block
+ bc.currentBlock.Store(block)
bc.mu.Unlock()
log.Info("Committed new head block", "number", block.Number(), "hash", hash)
@@ -330,28 +334,19 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {
// GasLimit returns the gas limit of the current HEAD block.
func (bc *BlockChain) GasLimit() uint64 {
- bc.mu.RLock()
- defer bc.mu.RUnlock()
-
- return bc.currentBlock.GasLimit()
+ return bc.CurrentBlock().GasLimit()
}
// CurrentBlock retrieves the current head block of the canonical chain. The
// block is retrieved from the blockchain's internal cache.
func (bc *BlockChain) CurrentBlock() *types.Block {
- bc.mu.RLock()
- defer bc.mu.RUnlock()
-
- return bc.currentBlock
+ return bc.currentBlock.Load().(*types.Block)
}
// CurrentFastBlock retrieves the current fast-sync head block of the canonical
// chain. The block is retrieved from the blockchain's internal cache.
func (bc *BlockChain) CurrentFastBlock() *types.Block {
- bc.mu.RLock()
- defer bc.mu.RUnlock()
-
- return bc.currentFastBlock
+ return bc.currentFastBlock.Load().(*types.Block)
}
// SetProcessor sets the processor required for making state modifications.
@@ -416,10 +411,10 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
}
bc.genesisBlock = genesis
bc.insert(bc.genesisBlock)
- bc.currentBlock = bc.genesisBlock
+ bc.currentBlock.Store(bc.genesisBlock)
bc.hc.SetGenesis(bc.genesisBlock.Header())
bc.hc.SetCurrentHeader(bc.genesisBlock.Header())
- bc.currentFastBlock = bc.genesisBlock
+ bc.currentFastBlock.Store(bc.genesisBlock)
return nil
}
@@ -444,7 +439,7 @@ func (bc *BlockChain) repair(head **types.Block) error {
// Export writes the active chain to the given writer.
func (bc *BlockChain) Export(w io.Writer) error {
- return bc.ExportN(w, uint64(0), bc.currentBlock.NumberU64())
+ return bc.ExportN(w, uint64(0), bc.CurrentBlock().NumberU64())
}
// ExportN writes a subset of the active chain to the given writer.
@@ -488,7 +483,7 @@ func (bc *BlockChain) insert(block *types.Block) {
if err := WriteHeadBlockHash(bc.db, block.Hash()); err != nil {
log.Crit("Failed to insert head block hash", "err", err)
}
- bc.currentBlock = block
+ bc.currentBlock.Store(block)
// If the block is better than our head or is on a different chain, force update heads
if updateHeads {
@@ -497,7 +492,7 @@ func (bc *BlockChain) insert(block *types.Block) {
if err := WriteHeadFastBlockHash(bc.db, block.Hash()); err != nil {
log.Crit("Failed to insert head fast block hash", "err", err)
}
- bc.currentFastBlock = block
+ bc.currentFastBlock.Store(block)
}
}
@@ -648,22 +643,21 @@ func (bc *BlockChain) Stop() {
bc.wg.Wait()
// Ensure the state of a recent block is also stored to disk before exiting.
- // It is fine if this state does not exist (fast start/stop cycle), but it is
- // advisable to leave an N block gap from the head so 1) a restart loads up
- // the last N blocks as sync assistance to remote nodes; 2) a restart during
- // a (small) reorg doesn't require deep reprocesses; 3) chain "repair" from
- // missing states are constantly tested.
- //
- // This may be tuned a bit on mainnet if its too annoying to reprocess the last
- // N blocks.
+ // We're writing three different states to catch different restart scenarios:
+ // - HEAD: So we don't need to reprocess any blocks in the general case
+ // - HEAD-1: So we don't do large reorgs if our HEAD becomes an uncle
+ // - HEAD-127: So we have a hard limit on the number of blocks reexecuted
if !bc.cacheConfig.Disabled {
triedb := bc.stateCache.TrieDB()
- if number := bc.CurrentBlock().NumberU64(); number >= triesInMemory {
- recent := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - triesInMemory + 1)
- log.Info("Writing cached state to disk", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root())
- if err := triedb.Commit(recent.Root(), true); err != nil {
- log.Error("Failed to commit recent state trie", "err", err)
+ for _, offset := range []uint64{0, 1, triesInMemory - 1} {
+ if number := bc.CurrentBlock().NumberU64(); number > offset {
+ recent := bc.GetBlockByNumber(number - offset)
+
+ log.Info("Writing cached state to disk", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root())
+ if err := triedb.Commit(recent.Root(), true); err != nil {
+ log.Error("Failed to commit recent state trie", "err", err)
+ }
}
}
for !bc.triegc.Empty() {
@@ -715,13 +709,15 @@ func (bc *BlockChain) Rollback(chain []common.Hash) {
if currentHeader.Hash() == hash {
bc.hc.SetCurrentHeader(bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1))
}
- if bc.currentFastBlock.Hash() == hash {
- bc.currentFastBlock = bc.GetBlock(bc.currentFastBlock.ParentHash(), bc.currentFastBlock.NumberU64()-1)
- WriteHeadFastBlockHash(bc.db, bc.currentFastBlock.Hash())
+ if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock.Hash() == hash {
+ newFastBlock := bc.GetBlock(currentFastBlock.ParentHash(), currentFastBlock.NumberU64()-1)
+ bc.currentFastBlock.Store(newFastBlock)
+ WriteHeadFastBlockHash(bc.db, newFastBlock.Hash())
}
- if bc.currentBlock.Hash() == hash {
- bc.currentBlock = bc.GetBlock(bc.currentBlock.ParentHash(), bc.currentBlock.NumberU64()-1)
- WriteHeadBlockHash(bc.db, bc.currentBlock.Hash())
+ if currentBlock := bc.CurrentBlock(); currentBlock.Hash() == hash {
+ newBlock := bc.GetBlock(currentBlock.ParentHash(), currentBlock.NumberU64()-1)
+ bc.currentBlock.Store(newBlock)
+ WriteHeadBlockHash(bc.db, newBlock.Hash())
}
}
}
@@ -830,11 +826,12 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
bc.mu.Lock()
head := blockChain[len(blockChain)-1]
if td := bc.GetTd(head.Hash(), head.NumberU64()); td != nil { // Rewind may have occurred, skip in that case
- if bc.GetTd(bc.currentFastBlock.Hash(), bc.currentFastBlock.NumberU64()).Cmp(td) < 0 {
+ currentFastBlock := bc.CurrentFastBlock()
+ if bc.GetTd(currentFastBlock.Hash(), currentFastBlock.NumberU64()).Cmp(td) < 0 {
if err := WriteHeadFastBlockHash(bc.db, head.Hash()); err != nil {
log.Crit("Failed to update head fast block hash", "err", err)
}
- bc.currentFastBlock = head
+ bc.currentFastBlock.Store(head)
}
}
bc.mu.Unlock()
@@ -881,7 +878,8 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
bc.mu.Lock()
defer bc.mu.Unlock()
- localTd := bc.GetTd(bc.currentBlock.Hash(), bc.currentBlock.NumberU64())
+ currentBlock := bc.CurrentBlock()
+ localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
externTd := new(big.Int).Add(block.Difficulty(), ptd)
// Irrelevant of the canonical status, write the block itself to the database
@@ -956,14 +954,15 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
// Second clause in the if statement reduces the vulnerability to selfish mining.
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
reorg := externTd.Cmp(localTd) > 0
+ currentBlock = bc.CurrentBlock()
if !reorg && externTd.Cmp(localTd) == 0 {
// Split same-difficulty blocks by number, then at random
- reorg = block.NumberU64() < bc.currentBlock.NumberU64() || (block.NumberU64() == bc.currentBlock.NumberU64() && mrand.Float64() < 0.5)
+ reorg = block.NumberU64() < currentBlock.NumberU64() || (block.NumberU64() == currentBlock.NumberU64() && mrand.Float64() < 0.5)
}
if reorg {
// Reorganise the chain if the parent is not the head block
- if block.ParentHash() != bc.currentBlock.Hash() {
- if err := bc.reorg(bc.currentBlock, block); err != nil {
+ if block.ParentHash() != currentBlock.Hash() {
+ if err := bc.reorg(currentBlock, block); err != nil {
return NonStatTy, err
}
}
@@ -1092,7 +1091,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
case err == consensus.ErrPrunedAncestor:
// Block competing with the canonical chain, store in the db, but don't process
// until the competitor TD goes above the canonical TD
- localTd := bc.GetTd(bc.currentBlock.Hash(), bc.currentBlock.NumberU64())
+ currentBlock := bc.CurrentBlock()
+ localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
externTd := new(big.Int).Add(bc.GetTd(block.ParentHash(), block.NumberU64()-1), block.Difficulty())
if localTd.Cmp(externTd) > 0 {
if err = bc.WriteBlockWithoutState(block, externTd); err != nil {
@@ -1481,9 +1481,6 @@ func (bc *BlockChain) writeHeader(header *types.Header) error {
// CurrentHeader retrieves the current head header of the canonical chain. The
// header is retrieved from the HeaderChain's internal cache.
func (bc *BlockChain) CurrentHeader() *types.Header {
- bc.mu.RLock()
- defer bc.mu.RUnlock()
-
return bc.hc.CurrentHeader()
}
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index 635379161c..748cdc5c71 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -34,26 +34,6 @@ import (
"github.com/ethereum/go-ethereum/params"
)
-// newTestBlockChain creates a blockchain without validation.
-func newTestBlockChain(fake bool) *BlockChain {
- db, _ := ethdb.NewMemDatabase()
- gspec := &Genesis{
- Config: params.TestChainConfig,
- Difficulty: big.NewInt(1),
- }
- gspec.MustCommit(db)
- engine := ethash.NewFullFaker()
- if !fake {
- engine = ethash.NewTester()
- }
- blockchain, err := NewBlockChain(db, nil, gspec.Config, engine, vm.Config{})
- if err != nil {
- panic(err)
- }
- blockchain.SetValidator(bproc{})
- return blockchain
-}
-
// Test fork of length N starting from block i
func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
// Copy old chain up to #i into a new db
@@ -183,13 +163,18 @@ func insertChain(done chan bool, blockchain *BlockChain, chain types.Blocks, t *
}
func TestLastBlock(t *testing.T) {
- bchain := newTestBlockChain(false)
- defer bchain.Stop()
+ _, blockchain, err := newCanonical(ethash.NewFaker(), 0, true)
+ if err != nil {
+ t.Fatalf("failed to create pristine chain: %v", err)
+ }
+ defer blockchain.Stop()
- block := makeBlockChain(bchain.CurrentBlock(), 1, ethash.NewFaker(), bchain.db, 0)[0]
- bchain.insert(block)
- if block.Hash() != GetHeadBlockHash(bchain.db) {
- t.Errorf("Write/Get HeadBlockHash failed")
+ blocks := makeBlockChain(blockchain.CurrentBlock(), 1, ethash.NewFullFaker(), blockchain.db, 0)
+ if _, err := blockchain.InsertChain(blocks); err != nil {
+ t.Fatalf("Failed to insert block: %v", err)
+ }
+ if blocks[len(blocks)-1].Hash() != GetHeadBlockHash(blockchain.db) {
+ t.Fatalf("Write/Get HeadBlockHash failed")
}
}
@@ -337,55 +322,13 @@ func testBrokenChain(t *testing.T, full bool) {
}
}
-type bproc struct{}
-
-func (bproc) ValidateBody(*types.Block) error { return nil }
-func (bproc) ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64) error {
- return nil
-}
-func (bproc) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
- return nil, nil, 0, nil
-}
-
-func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header {
- blocks := makeBlockChainWithDiff(genesis, d, seed)
- headers := make([]*types.Header, len(blocks))
- for i, block := range blocks {
- headers[i] = block.Header()
- }
- return headers
-}
-
-func makeBlockChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block {
- var chain []*types.Block
- for i, difficulty := range d {
- header := &types.Header{
- Coinbase: common.Address{seed},
- Number: big.NewInt(int64(i + 1)),
- Difficulty: big.NewInt(int64(difficulty)),
- UncleHash: types.EmptyUncleHash,
- TxHash: types.EmptyRootHash,
- ReceiptHash: types.EmptyRootHash,
- Time: big.NewInt(int64(i) + 1),
- }
- if i == 0 {
- header.ParentHash = genesis.Hash()
- } else {
- header.ParentHash = chain[i-1].Hash()
- }
- block := types.NewBlockWithHeader(header)
- chain = append(chain, block)
- }
- return chain
-}
-
// Tests that reorganising a long difficult chain after a short easy one
// overwrites the canonical numbers and links in the database.
func TestReorgLongHeaders(t *testing.T) { testReorgLong(t, false) }
func TestReorgLongBlocks(t *testing.T) { testReorgLong(t, true) }
func testReorgLong(t *testing.T, full bool) {
- testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10, full)
+ testReorg(t, []int64{0, 0, -9}, []int64{0, 0, 0, -9}, 393280, full)
}
// Tests that reorganising a short difficult chain after a long easy one
@@ -394,45 +337,82 @@ func TestReorgShortHeaders(t *testing.T) { testReorgShort(t, false) }
func TestReorgShortBlocks(t *testing.T) { testReorgShort(t, true) }
func testReorgShort(t *testing.T, full bool) {
- testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11, full)
+ // Create a long easy chain vs. a short heavy one. Due to difficulty adjustment
+ // we need a fairly long chain of blocks with different difficulties for a short
+ // one to become heavyer than a long one. The 96 is an empirical value.
+ easy := make([]int64, 96)
+ for i := 0; i < len(easy); i++ {
+ easy[i] = 60
+ }
+ diff := make([]int64, len(easy)-1)
+ for i := 0; i < len(diff); i++ {
+ diff[i] = -9
+ }
+ testReorg(t, easy, diff, 12615120, full)
}
-func testReorg(t *testing.T, first, second []int, td int64, full bool) {
- bc := newTestBlockChain(true)
- defer bc.Stop()
+func testReorg(t *testing.T, first, second []int64, td int64, full bool) {
+ // Create a pristine chain and database
+ db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
+ if err != nil {
+ t.Fatalf("failed to create pristine chain: %v", err)
+ }
+ defer blockchain.Stop()
// Insert an easy and a difficult chain afterwards
+ easyBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.CurrentBlock(), ethash.NewFaker(), db, len(first), func(i int, b *BlockGen) {
+ b.OffsetTime(first[i])
+ })
+ diffBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.CurrentBlock(), ethash.NewFaker(), db, len(second), func(i int, b *BlockGen) {
+ b.OffsetTime(second[i])
+ })
if full {
- bc.InsertChain(makeBlockChainWithDiff(bc.genesisBlock, first, 11))
- bc.InsertChain(makeBlockChainWithDiff(bc.genesisBlock, second, 22))
+ if _, err := blockchain.InsertChain(easyBlocks); err != nil {
+ t.Fatalf("failed to insert easy chain: %v", err)
+ }
+ if _, err := blockchain.InsertChain(diffBlocks); err != nil {
+ t.Fatalf("failed to insert difficult chain: %v", err)
+ }
} else {
- bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, first, 11), 1)
- bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, second, 22), 1)
+ easyHeaders := make([]*types.Header, len(easyBlocks))
+ for i, block := range easyBlocks {
+ easyHeaders[i] = block.Header()
+ }
+ diffHeaders := make([]*types.Header, len(diffBlocks))
+ for i, block := range diffBlocks {
+ diffHeaders[i] = block.Header()
+ }
+ if _, err := blockchain.InsertHeaderChain(easyHeaders, 1); err != nil {
+ t.Fatalf("failed to insert easy chain: %v", err)
+ }
+ if _, err := blockchain.InsertHeaderChain(diffHeaders, 1); err != nil {
+ t.Fatalf("failed to insert difficult chain: %v", err)
+ }
}
// Check that the chain is valid number and link wise
if full {
- prev := bc.CurrentBlock()
- for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) {
+ prev := blockchain.CurrentBlock()
+ for block := blockchain.GetBlockByNumber(blockchain.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, blockchain.GetBlockByNumber(block.NumberU64()-1) {
if prev.ParentHash() != block.Hash() {
t.Errorf("parent block hash mismatch: have %x, want %x", prev.ParentHash(), block.Hash())
}
}
} else {
- prev := bc.CurrentHeader()
- for header := bc.GetHeaderByNumber(bc.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, bc.GetHeaderByNumber(header.Number.Uint64()-1) {
+ prev := blockchain.CurrentHeader()
+ for header := blockchain.GetHeaderByNumber(blockchain.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, blockchain.GetHeaderByNumber(header.Number.Uint64()-1) {
if prev.ParentHash != header.Hash() {
t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash())
}
}
}
// Make sure the chain total difficulty is the correct one
- want := new(big.Int).Add(bc.genesisBlock.Difficulty(), big.NewInt(td))
+ want := new(big.Int).Add(blockchain.genesisBlock.Difficulty(), big.NewInt(td))
if full {
- if have := bc.GetTdByHash(bc.CurrentBlock().Hash()); have.Cmp(want) != 0 {
+ if have := blockchain.GetTdByHash(blockchain.CurrentBlock().Hash()); have.Cmp(want) != 0 {
t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
}
} else {
- if have := bc.GetTdByHash(bc.CurrentHeader().Hash()); have.Cmp(want) != 0 {
+ if have := blockchain.GetTdByHash(blockchain.CurrentHeader().Hash()); have.Cmp(want) != 0 {
t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
}
}
@@ -443,19 +423,28 @@ func TestBadHeaderHashes(t *testing.T) { testBadHashes(t, false) }
func TestBadBlockHashes(t *testing.T) { testBadHashes(t, true) }
func testBadHashes(t *testing.T, full bool) {
- bc := newTestBlockChain(true)
- defer bc.Stop()
+ // Create a pristine chain and database
+ db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
+ if err != nil {
+ t.Fatalf("failed to create pristine chain: %v", err)
+ }
+ defer blockchain.Stop()
// Create a chain, ban a hash and try to import
- var err error
if full {
- blocks := makeBlockChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10)
+ blocks := makeBlockChain(blockchain.CurrentBlock(), 3, ethash.NewFaker(), db, 10)
+
BadHashes[blocks[2].Header().Hash()] = true
- _, err = bc.InsertChain(blocks)
+ defer func() { delete(BadHashes, blocks[2].Header().Hash()) }()
+
+ _, err = blockchain.InsertChain(blocks)
} else {
- headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10)
+ headers := makeHeaderChain(blockchain.CurrentHeader(), 3, ethash.NewFaker(), db, 10)
+
BadHashes[headers[2].Hash()] = true
- _, err = bc.InsertHeaderChain(headers, 1)
+ defer func() { delete(BadHashes, headers[2].Hash()) }()
+
+ _, err = blockchain.InsertHeaderChain(headers, 1)
}
if err != ErrBlacklistedHash {
t.Errorf("error mismatch: have: %v, want: %v", err, ErrBlacklistedHash)
@@ -468,40 +457,41 @@ func TestReorgBadHeaderHashes(t *testing.T) { testReorgBadHashes(t, false) }
func TestReorgBadBlockHashes(t *testing.T) { testReorgBadHashes(t, true) }
func testReorgBadHashes(t *testing.T, full bool) {
- bc := newTestBlockChain(true)
- defer bc.Stop()
-
+ // Create a pristine chain and database
+ db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
+ if err != nil {
+ t.Fatalf("failed to create pristine chain: %v", err)
+ }
// Create a chain, import and ban afterwards
- headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 3, 4}, 10)
- blocks := makeBlockChainWithDiff(bc.genesisBlock, []int{1, 2, 3, 4}, 10)
+ headers := makeHeaderChain(blockchain.CurrentHeader(), 4, ethash.NewFaker(), db, 10)
+ blocks := makeBlockChain(blockchain.CurrentBlock(), 4, ethash.NewFaker(), db, 10)
if full {
- if _, err := bc.InsertChain(blocks); err != nil {
- t.Fatalf("failed to import blocks: %v", err)
+ if _, err = blockchain.InsertChain(blocks); err != nil {
+ t.Errorf("failed to import blocks: %v", err)
}
- if bc.CurrentBlock().Hash() != blocks[3].Hash() {
- t.Errorf("last block hash mismatch: have: %x, want %x", bc.CurrentBlock().Hash(), blocks[3].Header().Hash())
+ if blockchain.CurrentBlock().Hash() != blocks[3].Hash() {
+ t.Errorf("last block hash mismatch: have: %x, want %x", blockchain.CurrentBlock().Hash(), blocks[3].Header().Hash())
}
BadHashes[blocks[3].Header().Hash()] = true
defer func() { delete(BadHashes, blocks[3].Header().Hash()) }()
} else {
- if _, err := bc.InsertHeaderChain(headers, 1); err != nil {
- t.Fatalf("failed to import headers: %v", err)
+ if _, err = blockchain.InsertHeaderChain(headers, 1); err != nil {
+ t.Errorf("failed to import headers: %v", err)
}
- if bc.CurrentHeader().Hash() != headers[3].Hash() {
- t.Errorf("last header hash mismatch: have: %x, want %x", bc.CurrentHeader().Hash(), headers[3].Hash())
+ if blockchain.CurrentHeader().Hash() != headers[3].Hash() {
+ t.Errorf("last header hash mismatch: have: %x, want %x", blockchain.CurrentHeader().Hash(), headers[3].Hash())
}
BadHashes[headers[3].Hash()] = true
defer func() { delete(BadHashes, headers[3].Hash()) }()
}
+ blockchain.Stop()
// Create a new BlockChain and check that it rolled back the state.
- ncm, err := NewBlockChain(bc.db, nil, bc.chainConfig, ethash.NewFaker(), vm.Config{})
+ ncm, err := NewBlockChain(blockchain.db, nil, blockchain.chainConfig, ethash.NewFaker(), vm.Config{})
if err != nil {
t.Fatalf("failed to create new chain manager: %v", err)
}
- defer ncm.Stop()
-
if full {
if ncm.CurrentBlock().Hash() != blocks[2].Header().Hash() {
t.Errorf("last block hash mismatch: have: %x, want %x", ncm.CurrentBlock().Hash(), blocks[2].Header().Hash())
@@ -514,6 +504,7 @@ func testReorgBadHashes(t *testing.T, full bool) {
t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash())
}
}
+ ncm.Stop()
}
// Tests chain insertions in the face of one entity containing an invalid nonce.
@@ -989,10 +980,13 @@ done:
// Tests if the canonical block can be fetched from the database during chain insertion.
func TestCanonicalBlockRetrieval(t *testing.T) {
- bc := newTestBlockChain(true)
- defer bc.Stop()
+ _, blockchain, err := newCanonical(ethash.NewFaker(), 0, true)
+ if err != nil {
+ t.Fatalf("failed to create pristine chain: %v", err)
+ }
+ defer blockchain.Stop()
- chain, _ := GenerateChain(bc.chainConfig, bc.genesisBlock, ethash.NewFaker(), bc.db, 10, func(i int, gen *BlockGen) {})
+ chain, _ := GenerateChain(blockchain.chainConfig, blockchain.genesisBlock, ethash.NewFaker(), blockchain.db, 10, func(i int, gen *BlockGen) {})
var pend sync.WaitGroup
pend.Add(len(chain))
@@ -1003,14 +997,14 @@ func TestCanonicalBlockRetrieval(t *testing.T) {
// try to retrieve a block by its canonical hash and see if the block data can be retrieved.
for {
- ch := GetCanonicalHash(bc.db, block.NumberU64())
+ ch := GetCanonicalHash(blockchain.db, block.NumberU64())
if ch == (common.Hash{}) {
continue // busy wait for canonical hash to be written
}
if ch != block.Hash() {
t.Fatalf("unknown canonical hash, want %s, got %s", block.Hash().Hex(), ch.Hex())
}
- fb := GetBlock(bc.db, ch, block.NumberU64())
+ fb := GetBlock(blockchain.db, ch, block.NumberU64())
if fb == nil {
t.Fatalf("unable to retrieve block %d for canonical hash: %s", block.NumberU64(), ch.Hex())
}
@@ -1021,7 +1015,7 @@ func TestCanonicalBlockRetrieval(t *testing.T) {
}
}(chain[i])
- if _, err := bc.InsertChain(types.Blocks{chain[i]}); err != nil {
+ if _, err := blockchain.InsertChain(types.Blocks{chain[i]}); err != nil {
t.Fatalf("failed to insert block %d: %v", i, err)
}
}
diff --git a/core/database_util.go b/core/database_util.go
index c6b125dae9..61ab701349 100644
--- a/core/database_util.go
+++ b/core/database_util.go
@@ -70,8 +70,8 @@ var (
ErrChainConfigNotFound = errors.New("ChainConfig not found") // general config not found error
- preimageCounter = metrics.NewCounter("db/preimage/total")
- preimageHitCounter = metrics.NewCounter("db/preimage/hits")
+ preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
+ preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
)
// TxLookupEntry is a positional metadata to help looking up the data content of
diff --git a/core/genesis_test.go b/core/genesis_test.go
index cd548d4b1d..052ded6991 100644
--- a/core/genesis_test.go
+++ b/core/genesis_test.go
@@ -118,10 +118,12 @@ func TestSetupGenesis(t *testing.T) {
// Commit the 'old' genesis block with Homestead transition at #2.
// Advance to block #4, past the homestead transition block of customg.
genesis := oldcustomg.MustCommit(db)
+
bc, _ := NewBlockChain(db, nil, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{})
defer bc.Stop()
- bc.SetValidator(bproc{})
- bc.InsertChain(makeBlockChainWithDiff(genesis, []int{2, 3, 4, 5}, 0))
+
+ blocks, _ := GenerateChain(oldcustomg.Config, genesis, ethash.NewFaker(), db, 4, nil)
+ bc.InsertChain(blocks)
bc.CurrentBlock()
// This should return a compatibility error.
return SetupGenesisBlock(db, &customg)
diff --git a/core/headerchain.go b/core/headerchain.go
index 0e5215293d..73cd5d2c41 100644
--- a/core/headerchain.go
+++ b/core/headerchain.go
@@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/hashicorp/golang-lru"
+ "sync/atomic"
)
const (
@@ -51,8 +52,8 @@ type HeaderChain struct {
chainDb ethdb.Database
genesisHeader *types.Header
- currentHeader *types.Header // Current head of the header chain (may be above the block chain!)
- currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time)
+ currentHeader atomic.Value // Current head of the header chain (may be above the block chain!)
+ currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time)
headerCache *lru.Cache // Cache for the most recent block headers
tdCache *lru.Cache // Cache for the most recent block total difficulties
@@ -95,13 +96,13 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
return nil, ErrNoGenesis
}
- hc.currentHeader = hc.genesisHeader
+ hc.currentHeader.Store(hc.genesisHeader)
if head := GetHeadBlockHash(chainDb); head != (common.Hash{}) {
if chead := hc.GetHeaderByHash(head); chead != nil {
- hc.currentHeader = chead
+ hc.currentHeader.Store(chead)
}
}
- hc.currentHeaderHash = hc.currentHeader.Hash()
+ hc.currentHeaderHash = hc.CurrentHeader().Hash()
return hc, nil
}
@@ -139,7 +140,7 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
if ptd == nil {
return NonStatTy, consensus.ErrUnknownAncestor
}
- localTd := hc.GetTd(hc.currentHeaderHash, hc.currentHeader.Number.Uint64())
+ localTd := hc.GetTd(hc.currentHeaderHash, hc.CurrentHeader().Number.Uint64())
externTd := new(big.Int).Add(header.Difficulty, ptd)
// Irrelevant of the canonical status, write the td and header to the database
@@ -181,7 +182,8 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
if err := WriteHeadHeaderHash(hc.chainDb, hash); err != nil {
log.Crit("Failed to insert head header hash", "err", err)
}
- hc.currentHeaderHash, hc.currentHeader = hash, types.CopyHeader(header)
+ hc.currentHeaderHash = hash
+ hc.currentHeader.Store(types.CopyHeader(header))
status = CanonStatTy
} else {
@@ -383,7 +385,7 @@ func (hc *HeaderChain) GetHeaderByNumber(number uint64) *types.Header {
// CurrentHeader retrieves the current head header of the canonical chain. The
// header is retrieved from the HeaderChain's internal cache.
func (hc *HeaderChain) CurrentHeader() *types.Header {
- return hc.currentHeader
+ return hc.currentHeader.Load().(*types.Header)
}
// SetCurrentHeader sets the current head header of the canonical chain.
@@ -391,7 +393,7 @@ func (hc *HeaderChain) SetCurrentHeader(head *types.Header) {
if err := WriteHeadHeaderHash(hc.chainDb, head.Hash()); err != nil {
log.Crit("Failed to insert head header hash", "err", err)
}
- hc.currentHeader = head
+ hc.currentHeader.Store(head)
hc.currentHeaderHash = head.Hash()
}
@@ -403,19 +405,20 @@ type DeleteCallback func(common.Hash, uint64)
// will be deleted and the new one set.
func (hc *HeaderChain) SetHead(head uint64, delFn DeleteCallback) {
height := uint64(0)
- if hc.currentHeader != nil {
- height = hc.currentHeader.Number.Uint64()
+
+ if hdr := hc.CurrentHeader(); hdr != nil {
+ height = hdr.Number.Uint64()
}
- for hc.currentHeader != nil && hc.currentHeader.Number.Uint64() > head {
- hash := hc.currentHeader.Hash()
- num := hc.currentHeader.Number.Uint64()
+ for hdr := hc.CurrentHeader(); hdr != nil && hdr.Number.Uint64() > head; hdr = hc.CurrentHeader() {
+ hash := hdr.Hash()
+ num := hdr.Number.Uint64()
if delFn != nil {
delFn(hash, num)
}
DeleteHeader(hc.chainDb, hash, num)
DeleteTd(hc.chainDb, hash, num)
- hc.currentHeader = hc.GetHeader(hc.currentHeader.ParentHash, hc.currentHeader.Number.Uint64()-1)
+ hc.currentHeader.Store(hc.GetHeader(hdr.ParentHash, hdr.Number.Uint64()-1))
}
// Roll back the canonical chain numbering
for i := height; i > head; i-- {
@@ -426,10 +429,10 @@ func (hc *HeaderChain) SetHead(head uint64, delFn DeleteCallback) {
hc.tdCache.Purge()
hc.numberCache.Purge()
- if hc.currentHeader == nil {
- hc.currentHeader = hc.genesisHeader
+ if hc.CurrentHeader() == nil {
+ hc.currentHeader.Store(hc.genesisHeader)
}
- hc.currentHeaderHash = hc.currentHeader.Hash()
+ hc.currentHeaderHash = hc.CurrentHeader().Hash()
if err := WriteHeadHeaderHash(hc.chainDb, hc.currentHeaderHash); err != nil {
log.Crit("Failed to reset head header hash", "err", err)
diff --git a/core/tx_pool.go b/core/tx_pool.go
index dc3ddc4232..0534fe57a1 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -87,20 +87,20 @@ var (
var (
// Metrics for the pending pool
- pendingDiscardCounter = metrics.NewCounter("txpool/pending/discard")
- pendingReplaceCounter = metrics.NewCounter("txpool/pending/replace")
- pendingRateLimitCounter = metrics.NewCounter("txpool/pending/ratelimit") // Dropped due to rate limiting
- pendingNofundsCounter = metrics.NewCounter("txpool/pending/nofunds") // Dropped due to out-of-funds
+ pendingDiscardCounter = metrics.NewRegisteredCounter("txpool/pending/discard", nil)
+ pendingReplaceCounter = metrics.NewRegisteredCounter("txpool/pending/replace", nil)
+ pendingRateLimitCounter = metrics.NewRegisteredCounter("txpool/pending/ratelimit", nil) // Dropped due to rate limiting
+ pendingNofundsCounter = metrics.NewRegisteredCounter("txpool/pending/nofunds", nil) // Dropped due to out-of-funds
// Metrics for the queued pool
- queuedDiscardCounter = metrics.NewCounter("txpool/queued/discard")
- queuedReplaceCounter = metrics.NewCounter("txpool/queued/replace")
- queuedRateLimitCounter = metrics.NewCounter("txpool/queued/ratelimit") // Dropped due to rate limiting
- queuedNofundsCounter = metrics.NewCounter("txpool/queued/nofunds") // Dropped due to out-of-funds
+ queuedDiscardCounter = metrics.NewRegisteredCounter("txpool/queued/discard", nil)
+ queuedReplaceCounter = metrics.NewRegisteredCounter("txpool/queued/replace", nil)
+ queuedRateLimitCounter = metrics.NewRegisteredCounter("txpool/queued/ratelimit", nil) // Dropped due to rate limiting
+ queuedNofundsCounter = metrics.NewRegisteredCounter("txpool/queued/nofunds", nil) // Dropped due to out-of-funds
// General tx metrics
- invalidTxCounter = metrics.NewCounter("txpool/invalid")
- underpricedTxCounter = metrics.NewCounter("txpool/underpriced")
+ invalidTxCounter = metrics.NewRegisteredCounter("txpool/invalid", nil)
+ underpricedTxCounter = metrics.NewRegisteredCounter("txpool/underpriced", nil)
)
// TxStatus is the current status of a transaction as seen by the pool.
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 7661725010..6daf4e10df 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -302,6 +302,66 @@ func opMulmod(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *S
return nil, nil
}
+// opSHL implements Shift Left
+// The SHL instruction (shift left) pops 2 values from the stack, first arg1 and then arg2,
+// and pushes on the stack arg2 shifted to the left by arg1 number of bits.
+func opSHL(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
+ // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
+ shift, value := math.U256(stack.pop()), math.U256(stack.peek())
+ defer evm.interpreter.intPool.put(shift) // First operand back into the pool
+
+ if shift.Cmp(common.Big256) >= 0 {
+ value.SetUint64(0)
+ return nil, nil
+ }
+ n := uint(shift.Uint64())
+ math.U256(value.Lsh(value, n))
+
+ return nil, nil
+}
+
+// opSHR implements Logical Shift Right
+// The SHR instruction (logical shift right) pops 2 values from the stack, first arg1 and then arg2,
+// and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill.
+func opSHR(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
+ // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
+ shift, value := math.U256(stack.pop()), math.U256(stack.peek())
+ defer evm.interpreter.intPool.put(shift) // First operand back into the pool
+
+ if shift.Cmp(common.Big256) >= 0 {
+ value.SetUint64(0)
+ return nil, nil
+ }
+ n := uint(shift.Uint64())
+ math.U256(value.Rsh(value, n))
+
+ return nil, nil
+}
+
+// opSAR implements Arithmetic Shift Right
+// The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2,
+// and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension.
+func opSAR(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
+ // Note, S256 returns (potentially) a new bigint, so we're popping, not peeking this one
+ shift, value := math.U256(stack.pop()), math.S256(stack.pop())
+ defer evm.interpreter.intPool.put(shift) // First operand back into the pool
+
+ if shift.Cmp(common.Big256) >= 0 {
+ if value.Sign() > 0 {
+ value.SetUint64(0)
+ } else {
+ value.SetInt64(-1)
+ }
+ stack.push(math.U256(value))
+ return nil, nil
+ }
+ n := uint(shift.Uint64())
+ value.Rsh(value, n)
+ stack.push(math.U256(value))
+
+ return nil, nil
+}
+
func opSha3(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
offset, size := stack.pop(), stack.pop()
data := memory.Get(offset.Int64(), size.Int64())
diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go
index 180433ea88..eef4328bde 100644
--- a/core/vm/instructions_test.go
+++ b/core/vm/instructions_test.go
@@ -24,6 +24,48 @@ import (
"github.com/ethereum/go-ethereum/params"
)
+type twoOperandTest struct {
+ x string
+ y string
+ expected string
+}
+
+func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)) {
+ var (
+ env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
+ stack = newstack()
+ pc = uint64(0)
+ )
+ for i, test := range tests {
+ x := new(big.Int).SetBytes(common.Hex2Bytes(test.x))
+ shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y))
+ expected := new(big.Int).SetBytes(common.Hex2Bytes(test.expected))
+ stack.push(x)
+ stack.push(shift)
+ opFn(&pc, env, nil, nil, stack)
+ actual := stack.pop()
+ if actual.Cmp(expected) != 0 {
+ t.Errorf("Testcase %d, expected %v, got %v", i, expected, actual)
+ }
+ // Check pool usage
+ // 1.pool is not allowed to contain anything on the stack
+ // 2.pool is not allowed to contain the same pointers twice
+ if env.interpreter.intPool.pool.len() > 0 {
+
+ poolvals := make(map[*big.Int]struct{})
+ poolvals[actual] = struct{}{}
+
+ for env.interpreter.intPool.pool.len() > 0 {
+ key := env.interpreter.intPool.get()
+ if _, exist := poolvals[key]; exist {
+ t.Errorf("Testcase %d, pool contains double-entry", i)
+ }
+ poolvals[key] = struct{}{}
+ }
+ }
+ }
+}
+
func TestByteOp(t *testing.T) {
var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
@@ -57,6 +99,98 @@ func TestByteOp(t *testing.T) {
}
}
+func TestSHL(t *testing.T) {
+ // Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shl-shift-left
+ tests := []twoOperandTest{
+ {"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000002"},
+ {"0000000000000000000000000000000000000000000000000000000000000001", "ff", "8000000000000000000000000000000000000000000000000000000000000000"},
+ {"0000000000000000000000000000000000000000000000000000000000000001", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"0000000000000000000000000000000000000000000000000000000000000001", "0101", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "8000000000000000000000000000000000000000000000000000000000000000"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},
+ }
+ testTwoOperandOp(t, tests, opSHL)
+}
+
+func TestSHR(t *testing.T) {
+ // Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shr-logical-shift-right
+ tests := []twoOperandTest{
+ {"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"8000000000000000000000000000000000000000000000000000000000000000", "01", "4000000000000000000000000000000000000000000000000000000000000000"},
+ {"8000000000000000000000000000000000000000000000000000000000000000", "ff", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"8000000000000000000000000000000000000000000000000000000000000000", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"8000000000000000000000000000000000000000000000000000000000000000", "0101", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
+ }
+ testTwoOperandOp(t, tests, opSHR)
+}
+
+func TestSAR(t *testing.T) {
+ // Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#sar-arithmetic-shift-right
+ tests := []twoOperandTest{
+ {"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"8000000000000000000000000000000000000000000000000000000000000000", "01", "c000000000000000000000000000000000000000000000000000000000000000"},
+ {"8000000000000000000000000000000000000000000000000000000000000000", "ff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
+ {"8000000000000000000000000000000000000000000000000000000000000000", "0100", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
+ {"8000000000000000000000000000000000000000000000000000000000000000", "0101", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},
+ {"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"4000000000000000000000000000000000000000000000000000000000000000", "fe", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "f8", "000000000000000000000000000000000000000000000000000000000000007f"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "fe", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"},
+ }
+
+ testTwoOperandOp(t, tests, opSAR)
+}
+
+func TestSGT(t *testing.T) {
+ tests := []twoOperandTest{
+ {"0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"0000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"0000000000000000000000000000000000000000000000000000000000000001", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"8000000000000000000000000000000000000000000000000000000000000001", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"8000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
+ }
+ testTwoOperandOp(t, tests, opSgt)
+}
+
+func TestSLT(t *testing.T) {
+ tests := []twoOperandTest{
+ {"0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"0000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"0000000000000000000000000000000000000000000000000000000000000001", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001"},
+ {"8000000000000000000000000000000000000000000000000000000000000001", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"8000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
+ {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"},
+ }
+ testTwoOperandOp(t, tests, opSlt)
+}
+
func opBenchmark(bench *testing.B, op func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) {
var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
@@ -259,3 +393,22 @@ func BenchmarkOpMulmod(b *testing.B) {
opBenchmark(b, opMulmod, x, y, z)
}
+
+func BenchmarkOpSHL(b *testing.B) {
+ x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
+ y := "ff"
+
+ opBenchmark(b, opSHL, x, y)
+}
+func BenchmarkOpSHR(b *testing.B) {
+ x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
+ y := "ff"
+
+ opBenchmark(b, opSHR, x, y)
+}
+func BenchmarkOpSAR(b *testing.B) {
+ x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
+ y := "ff"
+
+ opBenchmark(b, opSAR, x, y)
+}
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 82a6d3de6d..e12dd7e5a1 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -68,6 +68,8 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
// we'll set the default jump table.
if !cfg.JumpTable[STOP].valid {
switch {
+ case evm.ChainConfig().IsConstantinople(evm.BlockNumber):
+ cfg.JumpTable = constantinopleInstructionSet
case evm.ChainConfig().IsByzantium(evm.BlockNumber):
cfg.JumpTable = byzantiumInstructionSet
case evm.ChainConfig().IsHomestead(evm.BlockNumber):
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index a1c5ad9c67..3389941353 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -51,11 +51,38 @@ type operation struct {
}
var (
- frontierInstructionSet = NewFrontierInstructionSet()
- homesteadInstructionSet = NewHomesteadInstructionSet()
- byzantiumInstructionSet = NewByzantiumInstructionSet()
+ frontierInstructionSet = NewFrontierInstructionSet()
+ homesteadInstructionSet = NewHomesteadInstructionSet()
+ byzantiumInstructionSet = NewByzantiumInstructionSet()
+ constantinopleInstructionSet = NewConstantinopleInstructionSet()
)
+// NewConstantinopleInstructionSet returns the frontier, homestead
+// byzantium and contantinople instructions.
+func NewConstantinopleInstructionSet() [256]operation {
+ // instructions that can be executed during the byzantium phase.
+ instructionSet := NewByzantiumInstructionSet()
+ instructionSet[SHL] = operation{
+ execute: opSHL,
+ gasCost: constGasFunc(GasFastestStep),
+ validateStack: makeStackFunc(2, 1),
+ valid: true,
+ }
+ instructionSet[SHR] = operation{
+ execute: opSHR,
+ gasCost: constGasFunc(GasFastestStep),
+ validateStack: makeStackFunc(2, 1),
+ valid: true,
+ }
+ instructionSet[SAR] = operation{
+ execute: opSAR,
+ gasCost: constGasFunc(GasFastestStep),
+ validateStack: makeStackFunc(2, 1),
+ valid: true,
+ }
+ return instructionSet
+}
+
// NewByzantiumInstructionSet returns the frontier, homestead and
// byzantium instructions.
func NewByzantiumInstructionSet() [256]operation {
diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go
index 0c65507355..7fe55b72f6 100644
--- a/core/vm/opcodes.go
+++ b/core/vm/opcodes.go
@@ -63,6 +63,9 @@ const (
XOR
NOT
BYTE
+ SHL
+ SHR
+ SAR
SHA3 = 0x20
)
@@ -234,6 +237,9 @@ var opCodeToString = map[OpCode]string{
OR: "OR",
XOR: "XOR",
BYTE: "BYTE",
+ SHL: "SHL",
+ SHR: "SHR",
+ SAR: "SAR",
ADDMOD: "ADDMOD",
MULMOD: "MULMOD",
@@ -400,6 +406,9 @@ var stringToOp = map[string]OpCode{
"OR": OR,
"XOR": XOR,
"BYTE": BYTE,
+ "SHL": SHL,
+ "SHR": SHR,
+ "SAR": SAR,
"ADDMOD": ADDMOD,
"MULMOD": MULMOD,
"SHA3": SHA3,
diff --git a/dashboard/dashboard.go b/dashboard/dashboard.go
index fca38f6ae5..ce49701a5e 100644
--- a/dashboard/dashboard.go
+++ b/dashboard/dashboard.go
@@ -38,10 +38,10 @@ import (
"github.com/elastic/gosigar"
"github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
- "github.com/rcrowley/go-metrics"
"golang.org/x/net/websocket"
)
diff --git a/eth/api_backend.go b/eth/api_backend.go
index 91f392f94f..ecd5488a24 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -104,6 +104,18 @@ func (b *EthApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash)
return core.GetBlockReceipts(b.eth.chainDb, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash)), nil
}
+func (b *EthApiBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) {
+ receipts := core.GetBlockReceipts(b.eth.chainDb, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash))
+ if receipts == nil {
+ return nil, nil
+ }
+ logs := make([][]*types.Log, len(receipts))
+ for i, receipt := range receipts {
+ logs[i] = receipt.Logs
+ }
+ return logs, nil
+}
+
func (b *EthApiBackend) GetTd(blockHash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash)
}
diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go
index 7ede530a94..d13247766a 100644
--- a/eth/downloader/downloader.go
+++ b/eth/downloader/downloader.go
@@ -31,8 +31,8 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
- "github.com/rcrowley/go-metrics"
)
var (
diff --git a/eth/downloader/metrics.go b/eth/downloader/metrics.go
index 58764ccf06..d4eb337946 100644
--- a/eth/downloader/metrics.go
+++ b/eth/downloader/metrics.go
@@ -23,21 +23,21 @@ import (
)
var (
- headerInMeter = metrics.NewMeter("eth/downloader/headers/in")
- headerReqTimer = metrics.NewTimer("eth/downloader/headers/req")
- headerDropMeter = metrics.NewMeter("eth/downloader/headers/drop")
- headerTimeoutMeter = metrics.NewMeter("eth/downloader/headers/timeout")
+ headerInMeter = metrics.NewRegisteredMeter("eth/downloader/headers/in", nil)
+ headerReqTimer = metrics.NewRegisteredTimer("eth/downloader/headers/req", nil)
+ headerDropMeter = metrics.NewRegisteredMeter("eth/downloader/headers/drop", nil)
+ headerTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/headers/timeout", nil)
- bodyInMeter = metrics.NewMeter("eth/downloader/bodies/in")
- bodyReqTimer = metrics.NewTimer("eth/downloader/bodies/req")
- bodyDropMeter = metrics.NewMeter("eth/downloader/bodies/drop")
- bodyTimeoutMeter = metrics.NewMeter("eth/downloader/bodies/timeout")
+ bodyInMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/in", nil)
+ bodyReqTimer = metrics.NewRegisteredTimer("eth/downloader/bodies/req", nil)
+ bodyDropMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/drop", nil)
+ bodyTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/timeout", nil)
- receiptInMeter = metrics.NewMeter("eth/downloader/receipts/in")
- receiptReqTimer = metrics.NewTimer("eth/downloader/receipts/req")
- receiptDropMeter = metrics.NewMeter("eth/downloader/receipts/drop")
- receiptTimeoutMeter = metrics.NewMeter("eth/downloader/receipts/timeout")
+ receiptInMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/in", nil)
+ receiptReqTimer = metrics.NewRegisteredTimer("eth/downloader/receipts/req", nil)
+ receiptDropMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/drop", nil)
+ receiptTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/timeout", nil)
- stateInMeter = metrics.NewMeter("eth/downloader/states/in")
- stateDropMeter = metrics.NewMeter("eth/downloader/states/drop")
+ stateInMeter = metrics.NewRegisteredMeter("eth/downloader/states/in", nil)
+ stateDropMeter = metrics.NewRegisteredMeter("eth/downloader/states/drop", nil)
)
diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go
index a1a70e46ea..359cce54b5 100644
--- a/eth/downloader/queue.go
+++ b/eth/downloader/queue.go
@@ -28,7 +28,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
- "github.com/rcrowley/go-metrics"
+ "github.com/ethereum/go-ethereum/metrics"
"gopkg.in/karalabe/cookiejar.v2/collections/prque"
)
diff --git a/eth/fetcher/metrics.go b/eth/fetcher/metrics.go
index 1ed8075bf3..d68d12f000 100644
--- a/eth/fetcher/metrics.go
+++ b/eth/fetcher/metrics.go
@@ -23,21 +23,21 @@ import (
)
var (
- propAnnounceInMeter = metrics.NewMeter("eth/fetcher/prop/announces/in")
- propAnnounceOutTimer = metrics.NewTimer("eth/fetcher/prop/announces/out")
- propAnnounceDropMeter = metrics.NewMeter("eth/fetcher/prop/announces/drop")
- propAnnounceDOSMeter = metrics.NewMeter("eth/fetcher/prop/announces/dos")
+ propAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/in", nil)
+ propAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/prop/announces/out", nil)
+ propAnnounceDropMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/drop", nil)
+ propAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/dos", nil)
- propBroadcastInMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/in")
- propBroadcastOutTimer = metrics.NewTimer("eth/fetcher/prop/broadcasts/out")
- propBroadcastDropMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/drop")
- propBroadcastDOSMeter = metrics.NewMeter("eth/fetcher/prop/broadcasts/dos")
+ propBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/in", nil)
+ propBroadcastOutTimer = metrics.NewRegisteredTimer("eth/fetcher/prop/broadcasts/out", nil)
+ propBroadcastDropMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/drop", nil)
+ propBroadcastDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/dos", nil)
- headerFetchMeter = metrics.NewMeter("eth/fetcher/fetch/headers")
- bodyFetchMeter = metrics.NewMeter("eth/fetcher/fetch/bodies")
+ headerFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/fetch/headers", nil)
+ bodyFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/fetch/bodies", nil)
- headerFilterInMeter = metrics.NewMeter("eth/fetcher/filter/headers/in")
- headerFilterOutMeter = metrics.NewMeter("eth/fetcher/filter/headers/out")
- bodyFilterInMeter = metrics.NewMeter("eth/fetcher/filter/bodies/in")
- bodyFilterOutMeter = metrics.NewMeter("eth/fetcher/filter/bodies/out")
+ headerFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/headers/in", nil)
+ headerFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/headers/out", nil)
+ bodyFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/bodies/in", nil)
+ bodyFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/bodies/out", nil)
)
diff --git a/eth/filters/filter.go b/eth/filters/filter.go
index 43d7e2a812..5dfe60e772 100644
--- a/eth/filters/filter.go
+++ b/eth/filters/filter.go
@@ -34,6 +34,7 @@ type Backend interface {
EventMux() *event.TypeMux
HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
+ GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error)
SubscribeTxPreEvent(chan<- core.TxPreEvent) event.Subscription
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
@@ -201,16 +202,28 @@ func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, e
// match the filter criteria. This function is called when the bloom filter signals a potential match.
func (f *Filter) checkMatches(ctx context.Context, header *types.Header) (logs []*types.Log, err error) {
// Get the logs of the block
- receipts, err := f.backend.GetReceipts(ctx, header.Hash())
+ logsList, err := f.backend.GetLogs(ctx, header.Hash())
if err != nil {
return nil, err
}
var unfiltered []*types.Log
- for _, receipt := range receipts {
- unfiltered = append(unfiltered, receipt.Logs...)
+ for _, logs := range logsList {
+ unfiltered = append(unfiltered, logs...)
}
logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
if len(logs) > 0 {
+ // We have matching logs, check if we need to resolve full logs via the light client
+ if logs[0].TxHash == (common.Hash{}) {
+ receipts, err := f.backend.GetReceipts(ctx, header.Hash())
+ if err != nil {
+ return nil, err
+ }
+ unfiltered = unfiltered[:0]
+ for _, receipt := range receipts {
+ unfiltered = append(unfiltered, receipt.Logs...)
+ }
+ logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
+ }
return logs, nil
}
return nil, nil
diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go
index b09998f9c7..f8097c7b95 100644
--- a/eth/filters/filter_system.go
+++ b/eth/filters/filter_system.go
@@ -375,19 +375,35 @@ func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.
// Get the logs of the block
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
- receipts, err := es.backend.GetReceipts(ctx, header.Hash())
+ logsList, err := es.backend.GetLogs(ctx, header.Hash())
if err != nil {
return nil
}
var unfiltered []*types.Log
- for _, receipt := range receipts {
- for _, log := range receipt.Logs {
+ for _, logs := range logsList {
+ for _, log := range logs {
logcopy := *log
logcopy.Removed = remove
unfiltered = append(unfiltered, &logcopy)
}
}
logs := filterLogs(unfiltered, nil, nil, addresses, topics)
+ if len(logs) > 0 && logs[0].TxHash == (common.Hash{}) {
+ // We have matching but non-derived logs
+ receipts, err := es.backend.GetReceipts(ctx, header.Hash())
+ if err != nil {
+ return nil
+ }
+ unfiltered = unfiltered[:0]
+ for _, receipt := range receipts {
+ for _, log := range receipt.Logs {
+ logcopy := *log
+ logcopy.Removed = remove
+ unfiltered = append(unfiltered, &logcopy)
+ }
+ }
+ logs = filterLogs(unfiltered, nil, nil, addresses, topics)
+ }
return logs
}
return nil
diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go
index 7ec3b4be7f..61761151a2 100644
--- a/eth/filters/filter_system_test.go
+++ b/eth/filters/filter_system_test.go
@@ -69,8 +69,19 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe
}
func (b *testBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) {
- num := core.GetBlockNumber(b.db, blockHash)
- return core.GetBlockReceipts(b.db, blockHash, num), nil
+ number := core.GetBlockNumber(b.db, blockHash)
+ return core.GetBlockReceipts(b.db, blockHash, number), nil
+}
+
+func (b *testBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) {
+ number := core.GetBlockNumber(b.db, blockHash)
+ receipts := core.GetBlockReceipts(b.db, blockHash, number)
+
+ logs := make([][]*types.Log, len(receipts))
+ for i, receipt := range receipts {
+ logs[i] = receipt.Logs
+ }
+ return logs, nil
}
func (b *testBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription {
diff --git a/eth/metrics.go b/eth/metrics.go
index 5fa2597d4d..0533a2a875 100644
--- a/eth/metrics.go
+++ b/eth/metrics.go
@@ -22,38 +22,38 @@ import (
)
var (
- propTxnInPacketsMeter = metrics.NewMeter("eth/prop/txns/in/packets")
- propTxnInTrafficMeter = metrics.NewMeter("eth/prop/txns/in/traffic")
- propTxnOutPacketsMeter = metrics.NewMeter("eth/prop/txns/out/packets")
- propTxnOutTrafficMeter = metrics.NewMeter("eth/prop/txns/out/traffic")
- propHashInPacketsMeter = metrics.NewMeter("eth/prop/hashes/in/packets")
- propHashInTrafficMeter = metrics.NewMeter("eth/prop/hashes/in/traffic")
- propHashOutPacketsMeter = metrics.NewMeter("eth/prop/hashes/out/packets")
- propHashOutTrafficMeter = metrics.NewMeter("eth/prop/hashes/out/traffic")
- propBlockInPacketsMeter = metrics.NewMeter("eth/prop/blocks/in/packets")
- propBlockInTrafficMeter = metrics.NewMeter("eth/prop/blocks/in/traffic")
- propBlockOutPacketsMeter = metrics.NewMeter("eth/prop/blocks/out/packets")
- propBlockOutTrafficMeter = metrics.NewMeter("eth/prop/blocks/out/traffic")
- reqHeaderInPacketsMeter = metrics.NewMeter("eth/req/headers/in/packets")
- reqHeaderInTrafficMeter = metrics.NewMeter("eth/req/headers/in/traffic")
- reqHeaderOutPacketsMeter = metrics.NewMeter("eth/req/headers/out/packets")
- reqHeaderOutTrafficMeter = metrics.NewMeter("eth/req/headers/out/traffic")
- reqBodyInPacketsMeter = metrics.NewMeter("eth/req/bodies/in/packets")
- reqBodyInTrafficMeter = metrics.NewMeter("eth/req/bodies/in/traffic")
- reqBodyOutPacketsMeter = metrics.NewMeter("eth/req/bodies/out/packets")
- reqBodyOutTrafficMeter = metrics.NewMeter("eth/req/bodies/out/traffic")
- reqStateInPacketsMeter = metrics.NewMeter("eth/req/states/in/packets")
- reqStateInTrafficMeter = metrics.NewMeter("eth/req/states/in/traffic")
- reqStateOutPacketsMeter = metrics.NewMeter("eth/req/states/out/packets")
- reqStateOutTrafficMeter = metrics.NewMeter("eth/req/states/out/traffic")
- reqReceiptInPacketsMeter = metrics.NewMeter("eth/req/receipts/in/packets")
- reqReceiptInTrafficMeter = metrics.NewMeter("eth/req/receipts/in/traffic")
- reqReceiptOutPacketsMeter = metrics.NewMeter("eth/req/receipts/out/packets")
- reqReceiptOutTrafficMeter = metrics.NewMeter("eth/req/receipts/out/traffic")
- miscInPacketsMeter = metrics.NewMeter("eth/misc/in/packets")
- miscInTrafficMeter = metrics.NewMeter("eth/misc/in/traffic")
- miscOutPacketsMeter = metrics.NewMeter("eth/misc/out/packets")
- miscOutTrafficMeter = metrics.NewMeter("eth/misc/out/traffic")
+ propTxnInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/txns/in/packets", nil)
+ propTxnInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/txns/in/traffic", nil)
+ propTxnOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/txns/out/packets", nil)
+ propTxnOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/txns/out/traffic", nil)
+ propHashInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/hashes/in/packets", nil)
+ propHashInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/hashes/in/traffic", nil)
+ propHashOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/hashes/out/packets", nil)
+ propHashOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/hashes/out/traffic", nil)
+ propBlockInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/blocks/in/packets", nil)
+ propBlockInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/blocks/in/traffic", nil)
+ propBlockOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/blocks/out/packets", nil)
+ propBlockOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/blocks/out/traffic", nil)
+ reqHeaderInPacketsMeter = metrics.NewRegisteredMeter("eth/req/headers/in/packets", nil)
+ reqHeaderInTrafficMeter = metrics.NewRegisteredMeter("eth/req/headers/in/traffic", nil)
+ reqHeaderOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/headers/out/packets", nil)
+ reqHeaderOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/headers/out/traffic", nil)
+ reqBodyInPacketsMeter = metrics.NewRegisteredMeter("eth/req/bodies/in/packets", nil)
+ reqBodyInTrafficMeter = metrics.NewRegisteredMeter("eth/req/bodies/in/traffic", nil)
+ reqBodyOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/bodies/out/packets", nil)
+ reqBodyOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/bodies/out/traffic", nil)
+ reqStateInPacketsMeter = metrics.NewRegisteredMeter("eth/req/states/in/packets", nil)
+ reqStateInTrafficMeter = metrics.NewRegisteredMeter("eth/req/states/in/traffic", nil)
+ reqStateOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/states/out/packets", nil)
+ reqStateOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/states/out/traffic", nil)
+ reqReceiptInPacketsMeter = metrics.NewRegisteredMeter("eth/req/receipts/in/packets", nil)
+ reqReceiptInTrafficMeter = metrics.NewRegisteredMeter("eth/req/receipts/in/traffic", nil)
+ reqReceiptOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/receipts/out/packets", nil)
+ reqReceiptOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/receipts/out/traffic", nil)
+ miscInPacketsMeter = metrics.NewRegisteredMeter("eth/misc/in/packets", nil)
+ miscInTrafficMeter = metrics.NewRegisteredMeter("eth/misc/in/traffic", nil)
+ miscOutPacketsMeter = metrics.NewRegisteredMeter("eth/misc/out/packets", nil)
+ miscOutTrafficMeter = metrics.NewRegisteredMeter("eth/misc/out/traffic", nil)
)
// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of
diff --git a/ethdb/database.go b/ethdb/database.go
index d86585f079..57d38f7f5f 100644
--- a/ethdb/database.go
+++ b/ethdb/database.go
@@ -29,8 +29,6 @@ import (
"github.com/syndtr/goleveldb/leveldb/filter"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/opt"
-
- gometrics "github.com/rcrowley/go-metrics"
)
var OpenFileLimit = 64
@@ -39,15 +37,15 @@ type LDBDatabase struct {
fn string // filename for reporting
db *leveldb.DB // LevelDB instance
- getTimer gometrics.Timer // Timer for measuring the database get request counts and latencies
- putTimer gometrics.Timer // Timer for measuring the database put request counts and latencies
- delTimer gometrics.Timer // Timer for measuring the database delete request counts and latencies
- missMeter gometrics.Meter // Meter for measuring the missed database get requests
- readMeter gometrics.Meter // Meter for measuring the database get request data usage
- writeMeter gometrics.Meter // Meter for measuring the database put request data usage
- compTimeMeter gometrics.Meter // Meter for measuring the total time spent in database compaction
- compReadMeter gometrics.Meter // Meter for measuring the data read during compaction
- compWriteMeter gometrics.Meter // Meter for measuring the data written during compaction
+ getTimer metrics.Timer // Timer for measuring the database get request counts and latencies
+ putTimer metrics.Timer // Timer for measuring the database put request counts and latencies
+ delTimer metrics.Timer // Timer for measuring the database delete request counts and latencies
+ missMeter metrics.Meter // Meter for measuring the missed database get requests
+ readMeter metrics.Meter // Meter for measuring the database get request data usage
+ writeMeter metrics.Meter // Meter for measuring the database put request data usage
+ compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction
+ compReadMeter metrics.Meter // Meter for measuring the data read during compaction
+ compWriteMeter metrics.Meter // Meter for measuring the data written during compaction
quitLock sync.Mutex // Mutex protecting the quit channel access
quitChan chan chan error // Quit channel to stop the metrics collection before closing the database
@@ -180,15 +178,15 @@ func (db *LDBDatabase) Meter(prefix string) {
return
}
// Initialize all the metrics collector at the requested prefix
- db.getTimer = metrics.NewTimer(prefix + "user/gets")
- db.putTimer = metrics.NewTimer(prefix + "user/puts")
- db.delTimer = metrics.NewTimer(prefix + "user/dels")
- db.missMeter = metrics.NewMeter(prefix + "user/misses")
- db.readMeter = metrics.NewMeter(prefix + "user/reads")
- db.writeMeter = metrics.NewMeter(prefix + "user/writes")
- db.compTimeMeter = metrics.NewMeter(prefix + "compact/time")
- db.compReadMeter = metrics.NewMeter(prefix + "compact/input")
- db.compWriteMeter = metrics.NewMeter(prefix + "compact/output")
+ db.getTimer = metrics.NewRegisteredTimer(prefix+"user/gets", nil)
+ db.putTimer = metrics.NewRegisteredTimer(prefix+"user/puts", nil)
+ db.delTimer = metrics.NewRegisteredTimer(prefix+"user/dels", nil)
+ db.missMeter = metrics.NewRegisteredMeter(prefix+"user/misses", nil)
+ db.readMeter = metrics.NewRegisteredMeter(prefix+"user/reads", nil)
+ db.writeMeter = metrics.NewRegisteredMeter(prefix+"user/writes", nil)
+ db.compTimeMeter = metrics.NewRegisteredMeter(prefix+"compact/time", nil)
+ db.compReadMeter = metrics.NewRegisteredMeter(prefix+"compact/input", nil)
+ db.compWriteMeter = metrics.NewRegisteredMeter(prefix+"compact/output", nil)
// Create a quit channel for the periodic collector and run it
db.quitLock.Lock()
diff --git a/internal/debug/flags.go b/internal/debug/flags.go
index 6247cc7dc0..1f181bf8b0 100644
--- a/internal/debug/flags.go
+++ b/internal/debug/flags.go
@@ -26,6 +26,8 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log/term"
+ "github.com/ethereum/go-ethereum/metrics"
+ "github.com/ethereum/go-ethereum/metrics/exp"
colorable "github.com/mattn/go-colorable"
"gopkg.in/urfave/cli.v1"
)
@@ -127,6 +129,10 @@ func Setup(ctx *cli.Context) error {
// pprof server
if ctx.GlobalBool(pprofFlag.Name) {
+ // Hook go-metrics into expvar on any /debug/metrics request, load all vars
+ // from the registry into expvar, and execute regular expvar handler.
+ exp.Exp(metrics.DefaultRegistry)
+
address := fmt.Sprintf("%s:%d", ctx.GlobalString(pprofAddrFlag.Name), ctx.GlobalInt(pprofPortFlag.Name))
go func() {
log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address))
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 636d0bfe26..d021b127c5 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -1032,15 +1032,19 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context,
}
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
-func (s *PublicTransactionPoolAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
+func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash)
if tx == nil {
return nil, errors.New("unknown transaction")
}
- receipt, _, _, _ := core.GetReceipt(s.b.ChainDb(), hash) // Old receipts don't have the lookup data available
- if receipt == nil {
+ receipts, err := s.b.GetReceipts(ctx, blockHash)
+ if err != nil {
+ return nil, err
+ }
+ if len(receipts) <= int(index) {
return nil, errors.New("unknown receipt")
}
+ receipt := receipts[index]
var signer types.Signer = types.FrontierSigner{}
if tx.Protected() {
diff --git a/les/api_backend.go b/les/api_backend.go
index 56f617a7db..3fc5c33a44 100644
--- a/les/api_backend.go
+++ b/les/api_backend.go
@@ -87,6 +87,10 @@ func (b *LesApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash)
return light.GetBlockReceipts(ctx, b.eth.odr, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash))
}
+func (b *LesApiBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) {
+ return light.GetBlockLogs(ctx, b.eth.odr, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash))
+}
+
func (b *LesApiBackend) GetTd(blockHash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash)
}
diff --git a/les/metrics.go b/les/metrics.go
index 0162a1d1ad..c282a62a1a 100644
--- a/les/metrics.go
+++ b/les/metrics.go
@@ -58,10 +58,10 @@ var (
reqReceiptInTrafficMeter = metrics.NewMeter("eth/req/receipts/in/traffic")
reqReceiptOutPacketsMeter = metrics.NewMeter("eth/req/receipts/out/packets")
reqReceiptOutTrafficMeter = metrics.NewMeter("eth/req/receipts/out/traffic")*/
- miscInPacketsMeter = metrics.NewMeter("les/misc/in/packets")
- miscInTrafficMeter = metrics.NewMeter("les/misc/in/traffic")
- miscOutPacketsMeter = metrics.NewMeter("les/misc/out/packets")
- miscOutTrafficMeter = metrics.NewMeter("les/misc/out/traffic")
+ miscInPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets", nil)
+ miscInTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic", nil)
+ miscOutPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets", nil)
+ miscOutTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic", nil)
)
// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of
diff --git a/light/lightchain.go b/light/lightchain.go
index 181a1c2a62..2784615d35 100644
--- a/light/lightchain.go
+++ b/light/lightchain.go
@@ -171,9 +171,6 @@ func (bc *LightChain) SetHead(head uint64) {
// GasLimit returns the gas limit of the current HEAD block.
func (self *LightChain) GasLimit() uint64 {
- self.mu.RLock()
- defer self.mu.RUnlock()
-
return self.hc.CurrentHeader().GasLimit
}
@@ -387,9 +384,6 @@ func (self *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int)
// CurrentHeader retrieves the current head header of the canonical chain. The
// header is retrieved from the HeaderChain's internal cache.
func (self *LightChain) CurrentHeader() *types.Header {
- self.mu.RLock()
- defer self.mu.RUnlock()
-
return self.hc.CurrentHeader()
}
diff --git a/light/odr_util.go b/light/odr_util.go
index 8f92d64426..97ba440ace 100644
--- a/light/odr_util.go
+++ b/light/odr_util.go
@@ -126,15 +126,48 @@ func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint
// GetBlockReceipts retrieves the receipts generated by the transactions included
// in a block given by its hash.
func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (types.Receipts, error) {
+ // Retrieve the potentially incomplete receipts from disk or network
receipts := core.GetBlockReceipts(odr.Database(), hash, number)
- if receipts != nil {
- return receipts, nil
+ if receipts == nil {
+ r := &ReceiptsRequest{Hash: hash, Number: number}
+ if err := odr.Retrieve(ctx, r); err != nil {
+ return nil, err
+ }
+ receipts = r.Receipts
}
- r := &ReceiptsRequest{Hash: hash, Number: number}
- if err := odr.Retrieve(ctx, r); err != nil {
- return nil, err
+ // If the receipts are incomplete, fill the derived fields
+ if len(receipts) > 0 && receipts[0].TxHash == (common.Hash{}) {
+ block, err := GetBlock(ctx, odr, hash, number)
+ if err != nil {
+ return nil, err
+ }
+ genesis := core.GetCanonicalHash(odr.Database(), 0)
+ config, _ := core.GetChainConfig(odr.Database(), genesis)
+
+ core.SetReceiptsData(config, block, receipts)
+ core.WriteBlockReceipts(odr.Database(), hash, number, receipts)
}
- return r.Receipts, nil
+ return receipts, nil
+}
+
+// GetBlockLogs retrieves the logs generated by the transactions included in a
+// block given by its hash.
+func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) ([][]*types.Log, error) {
+ // Retrieve the potentially incomplete receipts from disk or network
+ receipts := core.GetBlockReceipts(odr.Database(), hash, number)
+ if receipts == nil {
+ r := &ReceiptsRequest{Hash: hash, Number: number}
+ if err := odr.Retrieve(ctx, r); err != nil {
+ return nil, err
+ }
+ receipts = r.Receipts
+ }
+ // Return the logs without deriving any computed fields on the receipts
+ logs := make([][]*types.Log, len(receipts))
+ for i, receipt := range receipts {
+ logs[i] = receipt.Logs
+ }
+ return logs, nil
}
// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to the given bit index and section indexes
diff --git a/metrics/FORK.md b/metrics/FORK.md
new file mode 100644
index 0000000000..b19985bf56
--- /dev/null
+++ b/metrics/FORK.md
@@ -0,0 +1 @@
+This repo has been forked from https://github.com/rcrowley/go-metrics at commit e181e09
diff --git a/vendor/github.com/rcrowley/go-metrics/LICENSE b/metrics/LICENSE
similarity index 100%
rename from vendor/github.com/rcrowley/go-metrics/LICENSE
rename to metrics/LICENSE
diff --git a/vendor/github.com/rcrowley/go-metrics/README.md b/metrics/README.md
similarity index 81%
rename from vendor/github.com/rcrowley/go-metrics/README.md
rename to metrics/README.md
index 2d1a6dcfa4..bc2a45a838 100644
--- a/vendor/github.com/rcrowley/go-metrics/README.md
+++ b/metrics/README.md
@@ -42,12 +42,22 @@ t.Update(47)
Register() is not threadsafe. For threadsafe metric registration use
GetOrRegister:
-```
+```go
t := metrics.GetOrRegisterTimer("account.create.latency", nil)
t.Time(func() {})
t.Update(47)
```
+**NOTE:** Be sure to unregister short-lived meters and timers otherwise they will
+leak memory:
+
+```go
+// Will call Stop() on the Meter to allow for garbage collection
+metrics.Unregister("quux")
+// Or similarly for a Timer that embeds a Meter
+metrics.Unregister("bang")
+```
+
Periodically log every metric in human-readable form to standard error:
```go
@@ -81,12 +91,13 @@ issues [#121](https://github.com/rcrowley/go-metrics/issues/121) and
```go
import "github.com/vrischmann/go-metrics-influxdb"
-go influxdb.Influxdb(metrics.DefaultRegistry, 10e9, &influxdb.Config{
- Host: "127.0.0.1:8086",
- Database: "metrics",
- Username: "test",
- Password: "test",
-})
+go influxdb.InfluxDB(metrics.DefaultRegistry,
+ 10e9,
+ "127.0.0.1:8086",
+ "database-name",
+ "username",
+ "password"
+)
```
Periodically upload every metric to Librato using the [Librato client](https://github.com/mihasya/go-metrics-librato):
@@ -146,8 +157,10 @@ Publishing Metrics
Clients are available for the following destinations:
-* Librato - [https://github.com/mihasya/go-metrics-librato](https://github.com/mihasya/go-metrics-librato)
-* Graphite - [https://github.com/cyberdelia/go-metrics-graphite](https://github.com/cyberdelia/go-metrics-graphite)
-* InfluxDB - [https://github.com/vrischmann/go-metrics-influxdb](https://github.com/vrischmann/go-metrics-influxdb)
-* Ganglia - [https://github.com/appscode/metlia](https://github.com/appscode/metlia)
-* Prometheus - [https://github.com/deathowl/go-metrics-prometheus](https://github.com/deathowl/go-metrics-prometheus)
+* Librato - https://github.com/mihasya/go-metrics-librato
+* Graphite - https://github.com/cyberdelia/go-metrics-graphite
+* InfluxDB - https://github.com/vrischmann/go-metrics-influxdb
+* Ganglia - https://github.com/appscode/metlia
+* Prometheus - https://github.com/deathowl/go-metrics-prometheus
+* DataDog - https://github.com/syntaqx/go-metrics-datadog
+* SignalFX - https://github.com/pascallouisperez/go-metrics-signalfx
diff --git a/vendor/github.com/rcrowley/go-metrics/counter.go b/metrics/counter.go
similarity index 99%
rename from vendor/github.com/rcrowley/go-metrics/counter.go
rename to metrics/counter.go
index bb7b039cb5..c7f2b4bd3a 100644
--- a/vendor/github.com/rcrowley/go-metrics/counter.go
+++ b/metrics/counter.go
@@ -22,7 +22,7 @@ func GetOrRegisterCounter(name string, r Registry) Counter {
// NewCounter constructs a new StandardCounter.
func NewCounter() Counter {
- if UseNilMetrics {
+ if !Enabled {
return NilCounter{}
}
return &StandardCounter{0}
diff --git a/metrics/counter_test.go b/metrics/counter_test.go
new file mode 100644
index 0000000000..dfb03b4e88
--- /dev/null
+++ b/metrics/counter_test.go
@@ -0,0 +1,77 @@
+package metrics
+
+import "testing"
+
+func BenchmarkCounter(b *testing.B) {
+ c := NewCounter()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ c.Inc(1)
+ }
+}
+
+func TestCounterClear(t *testing.T) {
+ c := NewCounter()
+ c.Inc(1)
+ c.Clear()
+ if count := c.Count(); 0 != count {
+ t.Errorf("c.Count(): 0 != %v\n", count)
+ }
+}
+
+func TestCounterDec1(t *testing.T) {
+ c := NewCounter()
+ c.Dec(1)
+ if count := c.Count(); -1 != count {
+ t.Errorf("c.Count(): -1 != %v\n", count)
+ }
+}
+
+func TestCounterDec2(t *testing.T) {
+ c := NewCounter()
+ c.Dec(2)
+ if count := c.Count(); -2 != count {
+ t.Errorf("c.Count(): -2 != %v\n", count)
+ }
+}
+
+func TestCounterInc1(t *testing.T) {
+ c := NewCounter()
+ c.Inc(1)
+ if count := c.Count(); 1 != count {
+ t.Errorf("c.Count(): 1 != %v\n", count)
+ }
+}
+
+func TestCounterInc2(t *testing.T) {
+ c := NewCounter()
+ c.Inc(2)
+ if count := c.Count(); 2 != count {
+ t.Errorf("c.Count(): 2 != %v\n", count)
+ }
+}
+
+func TestCounterSnapshot(t *testing.T) {
+ c := NewCounter()
+ c.Inc(1)
+ snapshot := c.Snapshot()
+ c.Inc(1)
+ if count := snapshot.Count(); 1 != count {
+ t.Errorf("c.Count(): 1 != %v\n", count)
+ }
+}
+
+func TestCounterZero(t *testing.T) {
+ c := NewCounter()
+ if count := c.Count(); 0 != count {
+ t.Errorf("c.Count(): 0 != %v\n", count)
+ }
+}
+
+func TestGetOrRegisterCounter(t *testing.T) {
+ r := NewRegistry()
+ NewRegisteredCounter("foo", r).Inc(47)
+ if c := GetOrRegisterCounter("foo", r); 47 != c.Count() {
+ t.Fatal(c)
+ }
+}
diff --git a/vendor/github.com/rcrowley/go-metrics/debug.go b/metrics/debug.go
similarity index 94%
rename from vendor/github.com/rcrowley/go-metrics/debug.go
rename to metrics/debug.go
index 043ccefab6..de4a2739fe 100644
--- a/vendor/github.com/rcrowley/go-metrics/debug.go
+++ b/metrics/debug.go
@@ -22,7 +22,7 @@ var (
// Capture new values for the Go garbage collector statistics exported in
// debug.GCStats. This is designed to be called as a goroutine.
func CaptureDebugGCStats(r Registry, d time.Duration) {
- for _ = range time.Tick(d) {
+ for range time.Tick(d) {
CaptureDebugGCStatsOnce(r)
}
}
@@ -41,8 +41,8 @@ func CaptureDebugGCStatsOnce(r Registry) {
debug.ReadGCStats(&gcStats)
debugMetrics.ReadGCStats.UpdateSince(t)
- debugMetrics.GCStats.LastGC.Update(int64(gcStats.LastGC.UnixNano()))
- debugMetrics.GCStats.NumGC.Update(int64(gcStats.NumGC))
+ debugMetrics.GCStats.LastGC.Update(gcStats.LastGC.UnixNano())
+ debugMetrics.GCStats.NumGC.Update(gcStats.NumGC)
if lastGC != gcStats.LastGC && 0 < len(gcStats.Pause) {
debugMetrics.GCStats.Pause.Update(int64(gcStats.Pause[0]))
}
diff --git a/metrics/debug_test.go b/metrics/debug_test.go
new file mode 100644
index 0000000000..07eb867841
--- /dev/null
+++ b/metrics/debug_test.go
@@ -0,0 +1,48 @@
+package metrics
+
+import (
+ "runtime"
+ "runtime/debug"
+ "testing"
+ "time"
+)
+
+func BenchmarkDebugGCStats(b *testing.B) {
+ r := NewRegistry()
+ RegisterDebugGCStats(r)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ CaptureDebugGCStatsOnce(r)
+ }
+}
+
+func TestDebugGCStatsBlocking(t *testing.T) {
+ if g := runtime.GOMAXPROCS(0); g < 2 {
+ t.Skipf("skipping TestDebugGCMemStatsBlocking with GOMAXPROCS=%d\n", g)
+ return
+ }
+ ch := make(chan int)
+ go testDebugGCStatsBlocking(ch)
+ var gcStats debug.GCStats
+ t0 := time.Now()
+ debug.ReadGCStats(&gcStats)
+ t1 := time.Now()
+ t.Log("i++ during debug.ReadGCStats:", <-ch)
+ go testDebugGCStatsBlocking(ch)
+ d := t1.Sub(t0)
+ t.Log(d)
+ time.Sleep(d)
+ t.Log("i++ during time.Sleep:", <-ch)
+}
+
+func testDebugGCStatsBlocking(ch chan int) {
+ i := 0
+ for {
+ select {
+ case ch <- i:
+ return
+ default:
+ i++
+ }
+ }
+}
diff --git a/vendor/github.com/rcrowley/go-metrics/ewma.go b/metrics/ewma.go
similarity index 99%
rename from vendor/github.com/rcrowley/go-metrics/ewma.go
rename to metrics/ewma.go
index 694a1d0330..3aecd4fa35 100644
--- a/vendor/github.com/rcrowley/go-metrics/ewma.go
+++ b/metrics/ewma.go
@@ -17,7 +17,7 @@ type EWMA interface {
// NewEWMA constructs a new EWMA with the given alpha.
func NewEWMA(alpha float64) EWMA {
- if UseNilMetrics {
+ if !Enabled {
return NilEWMA{}
}
return &StandardEWMA{alpha: alpha}
diff --git a/metrics/ewma_test.go b/metrics/ewma_test.go
new file mode 100644
index 0000000000..0430fbd247
--- /dev/null
+++ b/metrics/ewma_test.go
@@ -0,0 +1,225 @@
+package metrics
+
+import "testing"
+
+func BenchmarkEWMA(b *testing.B) {
+ a := NewEWMA1()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ a.Update(1)
+ a.Tick()
+ }
+}
+
+func TestEWMA1(t *testing.T) {
+ a := NewEWMA1()
+ a.Update(3)
+ a.Tick()
+ if rate := a.Rate(); 0.6 != rate {
+ t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.22072766470286553 != rate {
+ t.Errorf("1 minute a.Rate(): 0.22072766470286553 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.08120116994196772 != rate {
+ t.Errorf("2 minute a.Rate(): 0.08120116994196772 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.029872241020718428 != rate {
+ t.Errorf("3 minute a.Rate(): 0.029872241020718428 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.01098938333324054 != rate {
+ t.Errorf("4 minute a.Rate(): 0.01098938333324054 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.004042768199451294 != rate {
+ t.Errorf("5 minute a.Rate(): 0.004042768199451294 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.0014872513059998212 != rate {
+ t.Errorf("6 minute a.Rate(): 0.0014872513059998212 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.0005471291793327122 != rate {
+ t.Errorf("7 minute a.Rate(): 0.0005471291793327122 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.00020127757674150815 != rate {
+ t.Errorf("8 minute a.Rate(): 0.00020127757674150815 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 7.404588245200814e-05 != rate {
+ t.Errorf("9 minute a.Rate(): 7.404588245200814e-05 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 2.7239957857491083e-05 != rate {
+ t.Errorf("10 minute a.Rate(): 2.7239957857491083e-05 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 1.0021020474147462e-05 != rate {
+ t.Errorf("11 minute a.Rate(): 1.0021020474147462e-05 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 3.6865274119969525e-06 != rate {
+ t.Errorf("12 minute a.Rate(): 3.6865274119969525e-06 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 1.3561976441886433e-06 != rate {
+ t.Errorf("13 minute a.Rate(): 1.3561976441886433e-06 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 4.989172314621449e-07 != rate {
+ t.Errorf("14 minute a.Rate(): 4.989172314621449e-07 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 1.8354139230109722e-07 != rate {
+ t.Errorf("15 minute a.Rate(): 1.8354139230109722e-07 != %v\n", rate)
+ }
+}
+
+func TestEWMA5(t *testing.T) {
+ a := NewEWMA5()
+ a.Update(3)
+ a.Tick()
+ if rate := a.Rate(); 0.6 != rate {
+ t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.49123845184678905 != rate {
+ t.Errorf("1 minute a.Rate(): 0.49123845184678905 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.4021920276213837 != rate {
+ t.Errorf("2 minute a.Rate(): 0.4021920276213837 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.32928698165641596 != rate {
+ t.Errorf("3 minute a.Rate(): 0.32928698165641596 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.269597378470333 != rate {
+ t.Errorf("4 minute a.Rate(): 0.269597378470333 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.2207276647028654 != rate {
+ t.Errorf("5 minute a.Rate(): 0.2207276647028654 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.18071652714732128 != rate {
+ t.Errorf("6 minute a.Rate(): 0.18071652714732128 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.14795817836496392 != rate {
+ t.Errorf("7 minute a.Rate(): 0.14795817836496392 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.12113791079679326 != rate {
+ t.Errorf("8 minute a.Rate(): 0.12113791079679326 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.09917933293295193 != rate {
+ t.Errorf("9 minute a.Rate(): 0.09917933293295193 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.08120116994196763 != rate {
+ t.Errorf("10 minute a.Rate(): 0.08120116994196763 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.06648189501740036 != rate {
+ t.Errorf("11 minute a.Rate(): 0.06648189501740036 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.05443077197364752 != rate {
+ t.Errorf("12 minute a.Rate(): 0.05443077197364752 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.04456414692860035 != rate {
+ t.Errorf("13 minute a.Rate(): 0.04456414692860035 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.03648603757513079 != rate {
+ t.Errorf("14 minute a.Rate(): 0.03648603757513079 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.0298722410207183831020718428 != rate {
+ t.Errorf("15 minute a.Rate(): 0.0298722410207183831020718428 != %v\n", rate)
+ }
+}
+
+func TestEWMA15(t *testing.T) {
+ a := NewEWMA15()
+ a.Update(3)
+ a.Tick()
+ if rate := a.Rate(); 0.6 != rate {
+ t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.5613041910189706 != rate {
+ t.Errorf("1 minute a.Rate(): 0.5613041910189706 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.5251039914257684 != rate {
+ t.Errorf("2 minute a.Rate(): 0.5251039914257684 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.4912384518467888184678905 != rate {
+ t.Errorf("3 minute a.Rate(): 0.4912384518467888184678905 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.459557003018789 != rate {
+ t.Errorf("4 minute a.Rate(): 0.459557003018789 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.4299187863442732 != rate {
+ t.Errorf("5 minute a.Rate(): 0.4299187863442732 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.4021920276213831 != rate {
+ t.Errorf("6 minute a.Rate(): 0.4021920276213831 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.37625345116383313 != rate {
+ t.Errorf("7 minute a.Rate(): 0.37625345116383313 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.3519877317060185 != rate {
+ t.Errorf("8 minute a.Rate(): 0.3519877317060185 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.3292869816564153165641596 != rate {
+ t.Errorf("9 minute a.Rate(): 0.3292869816564153165641596 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.3080502714195546 != rate {
+ t.Errorf("10 minute a.Rate(): 0.3080502714195546 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.2881831806538789 != rate {
+ t.Errorf("11 minute a.Rate(): 0.2881831806538789 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.26959737847033216 != rate {
+ t.Errorf("12 minute a.Rate(): 0.26959737847033216 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.2522102307052083 != rate {
+ t.Errorf("13 minute a.Rate(): 0.2522102307052083 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.23594443252115815 != rate {
+ t.Errorf("14 minute a.Rate(): 0.23594443252115815 != %v\n", rate)
+ }
+ elapseMinute(a)
+ if rate := a.Rate(); 0.2207276647028646247028654470286553 != rate {
+ t.Errorf("15 minute a.Rate(): 0.2207276647028646247028654470286553 != %v\n", rate)
+ }
+}
+
+func elapseMinute(a EWMA) {
+ for i := 0; i < 12; i++ {
+ a.Tick()
+ }
+}
diff --git a/vendor/github.com/rcrowley/go-metrics/exp/exp.go b/metrics/exp/exp.go
similarity index 73%
rename from vendor/github.com/rcrowley/go-metrics/exp/exp.go
rename to metrics/exp/exp.go
index 11dd3f898a..c19d00a94d 100644
--- a/vendor/github.com/rcrowley/go-metrics/exp/exp.go
+++ b/metrics/exp/exp.go
@@ -8,7 +8,7 @@ import (
"net/http"
"sync"
- "github.com/rcrowley/go-metrics"
+ "github.com/ethereum/go-ethereum/metrics"
)
type exp struct {
@@ -97,22 +97,22 @@ func (exp *exp) publishHistogram(name string, metric metrics.Histogram) {
exp.getInt(name + ".count").Set(h.Count())
exp.getFloat(name + ".min").Set(float64(h.Min()))
exp.getFloat(name + ".max").Set(float64(h.Max()))
- exp.getFloat(name + ".mean").Set(float64(h.Mean()))
- exp.getFloat(name + ".std-dev").Set(float64(h.StdDev()))
- exp.getFloat(name + ".50-percentile").Set(float64(ps[0]))
- exp.getFloat(name + ".75-percentile").Set(float64(ps[1]))
- exp.getFloat(name + ".95-percentile").Set(float64(ps[2]))
- exp.getFloat(name + ".99-percentile").Set(float64(ps[3]))
- exp.getFloat(name + ".999-percentile").Set(float64(ps[4]))
+ exp.getFloat(name + ".mean").Set(h.Mean())
+ exp.getFloat(name + ".std-dev").Set(h.StdDev())
+ exp.getFloat(name + ".50-percentile").Set(ps[0])
+ exp.getFloat(name + ".75-percentile").Set(ps[1])
+ exp.getFloat(name + ".95-percentile").Set(ps[2])
+ exp.getFloat(name + ".99-percentile").Set(ps[3])
+ exp.getFloat(name + ".999-percentile").Set(ps[4])
}
func (exp *exp) publishMeter(name string, metric metrics.Meter) {
m := metric.Snapshot()
exp.getInt(name + ".count").Set(m.Count())
- exp.getFloat(name + ".one-minute").Set(float64(m.Rate1()))
- exp.getFloat(name + ".five-minute").Set(float64(m.Rate5()))
- exp.getFloat(name + ".fifteen-minute").Set(float64((m.Rate15())))
- exp.getFloat(name + ".mean").Set(float64(m.RateMean()))
+ exp.getFloat(name + ".one-minute").Set(m.Rate1())
+ exp.getFloat(name + ".five-minute").Set(m.Rate5())
+ exp.getFloat(name + ".fifteen-minute").Set((m.Rate15()))
+ exp.getFloat(name + ".mean").Set(m.RateMean())
}
func (exp *exp) publishTimer(name string, metric metrics.Timer) {
@@ -121,17 +121,17 @@ func (exp *exp) publishTimer(name string, metric metrics.Timer) {
exp.getInt(name + ".count").Set(t.Count())
exp.getFloat(name + ".min").Set(float64(t.Min()))
exp.getFloat(name + ".max").Set(float64(t.Max()))
- exp.getFloat(name + ".mean").Set(float64(t.Mean()))
- exp.getFloat(name + ".std-dev").Set(float64(t.StdDev()))
- exp.getFloat(name + ".50-percentile").Set(float64(ps[0]))
- exp.getFloat(name + ".75-percentile").Set(float64(ps[1]))
- exp.getFloat(name + ".95-percentile").Set(float64(ps[2]))
- exp.getFloat(name + ".99-percentile").Set(float64(ps[3]))
- exp.getFloat(name + ".999-percentile").Set(float64(ps[4]))
- exp.getFloat(name + ".one-minute").Set(float64(t.Rate1()))
- exp.getFloat(name + ".five-minute").Set(float64(t.Rate5()))
- exp.getFloat(name + ".fifteen-minute").Set(float64((t.Rate15())))
- exp.getFloat(name + ".mean-rate").Set(float64(t.RateMean()))
+ exp.getFloat(name + ".mean").Set(t.Mean())
+ exp.getFloat(name + ".std-dev").Set(t.StdDev())
+ exp.getFloat(name + ".50-percentile").Set(ps[0])
+ exp.getFloat(name + ".75-percentile").Set(ps[1])
+ exp.getFloat(name + ".95-percentile").Set(ps[2])
+ exp.getFloat(name + ".99-percentile").Set(ps[3])
+ exp.getFloat(name + ".999-percentile").Set(ps[4])
+ exp.getFloat(name + ".one-minute").Set(t.Rate1())
+ exp.getFloat(name + ".five-minute").Set(t.Rate5())
+ exp.getFloat(name + ".fifteen-minute").Set(t.Rate15())
+ exp.getFloat(name + ".mean-rate").Set(t.RateMean())
}
func (exp *exp) syncToExpvar() {
diff --git a/vendor/github.com/rcrowley/go-metrics/gauge.go b/metrics/gauge.go
similarity index 98%
rename from vendor/github.com/rcrowley/go-metrics/gauge.go
rename to metrics/gauge.go
index cb57a93889..0fbfdb8603 100644
--- a/vendor/github.com/rcrowley/go-metrics/gauge.go
+++ b/metrics/gauge.go
@@ -20,7 +20,7 @@ func GetOrRegisterGauge(name string, r Registry) Gauge {
// NewGauge constructs a new StandardGauge.
func NewGauge() Gauge {
- if UseNilMetrics {
+ if !Enabled {
return NilGauge{}
}
return &StandardGauge{0}
@@ -38,7 +38,7 @@ func NewRegisteredGauge(name string, r Registry) Gauge {
// NewFunctionalGauge constructs a new FunctionalGauge.
func NewFunctionalGauge(f func() int64) Gauge {
- if UseNilMetrics {
+ if !Enabled {
return NilGauge{}
}
return &FunctionalGauge{value: f}
diff --git a/vendor/github.com/rcrowley/go-metrics/gauge_float64.go b/metrics/gauge_float64.go
similarity index 98%
rename from vendor/github.com/rcrowley/go-metrics/gauge_float64.go
rename to metrics/gauge_float64.go
index 6f93920b2c..66819c9577 100644
--- a/vendor/github.com/rcrowley/go-metrics/gauge_float64.go
+++ b/metrics/gauge_float64.go
@@ -20,7 +20,7 @@ func GetOrRegisterGaugeFloat64(name string, r Registry) GaugeFloat64 {
// NewGaugeFloat64 constructs a new StandardGaugeFloat64.
func NewGaugeFloat64() GaugeFloat64 {
- if UseNilMetrics {
+ if !Enabled {
return NilGaugeFloat64{}
}
return &StandardGaugeFloat64{
@@ -40,7 +40,7 @@ func NewRegisteredGaugeFloat64(name string, r Registry) GaugeFloat64 {
// NewFunctionalGauge constructs a new FunctionalGauge.
func NewFunctionalGaugeFloat64(f func() float64) GaugeFloat64 {
- if UseNilMetrics {
+ if !Enabled {
return NilGaugeFloat64{}
}
return &FunctionalGaugeFloat64{value: f}
diff --git a/metrics/gauge_float64_test.go b/metrics/gauge_float64_test.go
new file mode 100644
index 0000000000..99e62a4030
--- /dev/null
+++ b/metrics/gauge_float64_test.go
@@ -0,0 +1,59 @@
+package metrics
+
+import "testing"
+
+func BenchmarkGuageFloat64(b *testing.B) {
+ g := NewGaugeFloat64()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ g.Update(float64(i))
+ }
+}
+
+func TestGaugeFloat64(t *testing.T) {
+ g := NewGaugeFloat64()
+ g.Update(float64(47.0))
+ if v := g.Value(); float64(47.0) != v {
+ t.Errorf("g.Value(): 47.0 != %v\n", v)
+ }
+}
+
+func TestGaugeFloat64Snapshot(t *testing.T) {
+ g := NewGaugeFloat64()
+ g.Update(float64(47.0))
+ snapshot := g.Snapshot()
+ g.Update(float64(0))
+ if v := snapshot.Value(); float64(47.0) != v {
+ t.Errorf("g.Value(): 47.0 != %v\n", v)
+ }
+}
+
+func TestGetOrRegisterGaugeFloat64(t *testing.T) {
+ r := NewRegistry()
+ NewRegisteredGaugeFloat64("foo", r).Update(float64(47.0))
+ t.Logf("registry: %v", r)
+ if g := GetOrRegisterGaugeFloat64("foo", r); float64(47.0) != g.Value() {
+ t.Fatal(g)
+ }
+}
+
+func TestFunctionalGaugeFloat64(t *testing.T) {
+ var counter float64
+ fg := NewFunctionalGaugeFloat64(func() float64 {
+ counter++
+ return counter
+ })
+ fg.Value()
+ fg.Value()
+ if counter != 2 {
+ t.Error("counter != 2")
+ }
+}
+
+func TestGetOrRegisterFunctionalGaugeFloat64(t *testing.T) {
+ r := NewRegistry()
+ NewRegisteredFunctionalGaugeFloat64("foo", r, func() float64 { return 47 })
+ if g := GetOrRegisterGaugeFloat64("foo", r); 47 != g.Value() {
+ t.Fatal(g)
+ }
+}
diff --git a/metrics/gauge_test.go b/metrics/gauge_test.go
new file mode 100644
index 0000000000..1f2603d339
--- /dev/null
+++ b/metrics/gauge_test.go
@@ -0,0 +1,68 @@
+package metrics
+
+import (
+ "fmt"
+ "testing"
+)
+
+func BenchmarkGuage(b *testing.B) {
+ g := NewGauge()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ g.Update(int64(i))
+ }
+}
+
+func TestGauge(t *testing.T) {
+ g := NewGauge()
+ g.Update(int64(47))
+ if v := g.Value(); 47 != v {
+ t.Errorf("g.Value(): 47 != %v\n", v)
+ }
+}
+
+func TestGaugeSnapshot(t *testing.T) {
+ g := NewGauge()
+ g.Update(int64(47))
+ snapshot := g.Snapshot()
+ g.Update(int64(0))
+ if v := snapshot.Value(); 47 != v {
+ t.Errorf("g.Value(): 47 != %v\n", v)
+ }
+}
+
+func TestGetOrRegisterGauge(t *testing.T) {
+ r := NewRegistry()
+ NewRegisteredGauge("foo", r).Update(47)
+ if g := GetOrRegisterGauge("foo", r); 47 != g.Value() {
+ t.Fatal(g)
+ }
+}
+
+func TestFunctionalGauge(t *testing.T) {
+ var counter int64
+ fg := NewFunctionalGauge(func() int64 {
+ counter++
+ return counter
+ })
+ fg.Value()
+ fg.Value()
+ if counter != 2 {
+ t.Error("counter != 2")
+ }
+}
+
+func TestGetOrRegisterFunctionalGauge(t *testing.T) {
+ r := NewRegistry()
+ NewRegisteredFunctionalGauge("foo", r, func() int64 { return 47 })
+ if g := GetOrRegisterGauge("foo", r); 47 != g.Value() {
+ t.Fatal(g)
+ }
+}
+
+func ExampleGetOrRegisterGauge() {
+ m := "server.bytes_sent"
+ g := GetOrRegisterGauge(m, nil)
+ g.Update(47)
+ fmt.Println(g.Value()) // Output: 47
+}
diff --git a/vendor/github.com/rcrowley/go-metrics/graphite.go b/metrics/graphite.go
similarity index 99%
rename from vendor/github.com/rcrowley/go-metrics/graphite.go
rename to metrics/graphite.go
index abd0a7d291..142eec86be 100644
--- a/vendor/github.com/rcrowley/go-metrics/graphite.go
+++ b/metrics/graphite.go
@@ -39,7 +39,7 @@ func Graphite(r Registry, d time.Duration, prefix string, addr *net.TCPAddr) {
// but it takes a GraphiteConfig instead.
func GraphiteWithConfig(c GraphiteConfig) {
log.Printf("WARNING: This go-metrics client has been DEPRECATED! It has been moved to https://github.com/cyberdelia/go-metrics-graphite and will be removed from rcrowley/go-metrics on August 12th 2015")
- for _ = range time.Tick(c.FlushInterval) {
+ for range time.Tick(c.FlushInterval) {
if err := graphite(&c); nil != err {
log.Println(err)
}
diff --git a/metrics/graphite_test.go b/metrics/graphite_test.go
new file mode 100644
index 0000000000..c797c781df
--- /dev/null
+++ b/metrics/graphite_test.go
@@ -0,0 +1,22 @@
+package metrics
+
+import (
+ "net"
+ "time"
+)
+
+func ExampleGraphite() {
+ addr, _ := net.ResolveTCPAddr("net", ":2003")
+ go Graphite(DefaultRegistry, 1*time.Second, "some.prefix", addr)
+}
+
+func ExampleGraphiteWithConfig() {
+ addr, _ := net.ResolveTCPAddr("net", ":2003")
+ go GraphiteWithConfig(GraphiteConfig{
+ Addr: addr,
+ Registry: DefaultRegistry,
+ FlushInterval: 1 * time.Second,
+ DurationUnit: time.Millisecond,
+ Percentiles: []float64{0.5, 0.75, 0.99, 0.999},
+ })
+}
diff --git a/vendor/github.com/rcrowley/go-metrics/healthcheck.go b/metrics/healthcheck.go
similarity index 98%
rename from vendor/github.com/rcrowley/go-metrics/healthcheck.go
rename to metrics/healthcheck.go
index 445131caee..f1ae31e34a 100644
--- a/vendor/github.com/rcrowley/go-metrics/healthcheck.go
+++ b/metrics/healthcheck.go
@@ -11,7 +11,7 @@ type Healthcheck interface {
// NewHealthcheck constructs a new Healthcheck which will use the given
// function to update its status.
func NewHealthcheck(f func(Healthcheck)) Healthcheck {
- if UseNilMetrics {
+ if !Enabled {
return NilHealthcheck{}
}
return &StandardHealthcheck{nil, f}
diff --git a/vendor/github.com/rcrowley/go-metrics/histogram.go b/metrics/histogram.go
similarity index 99%
rename from vendor/github.com/rcrowley/go-metrics/histogram.go
rename to metrics/histogram.go
index dbc837fe4d..46f3bbd2f1 100644
--- a/vendor/github.com/rcrowley/go-metrics/histogram.go
+++ b/metrics/histogram.go
@@ -28,7 +28,7 @@ func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram {
// NewHistogram constructs a new StandardHistogram from a Sample.
func NewHistogram(s Sample) Histogram {
- if UseNilMetrics {
+ if !Enabled {
return NilHistogram{}
}
return &StandardHistogram{sample: s}
diff --git a/metrics/histogram_test.go b/metrics/histogram_test.go
new file mode 100644
index 0000000000..d7f4f0171c
--- /dev/null
+++ b/metrics/histogram_test.go
@@ -0,0 +1,95 @@
+package metrics
+
+import "testing"
+
+func BenchmarkHistogram(b *testing.B) {
+ h := NewHistogram(NewUniformSample(100))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ h.Update(int64(i))
+ }
+}
+
+func TestGetOrRegisterHistogram(t *testing.T) {
+ r := NewRegistry()
+ s := NewUniformSample(100)
+ NewRegisteredHistogram("foo", r, s).Update(47)
+ if h := GetOrRegisterHistogram("foo", r, s); 1 != h.Count() {
+ t.Fatal(h)
+ }
+}
+
+func TestHistogram10000(t *testing.T) {
+ h := NewHistogram(NewUniformSample(100000))
+ for i := 1; i <= 10000; i++ {
+ h.Update(int64(i))
+ }
+ testHistogram10000(t, h)
+}
+
+func TestHistogramEmpty(t *testing.T) {
+ h := NewHistogram(NewUniformSample(100))
+ if count := h.Count(); 0 != count {
+ t.Errorf("h.Count(): 0 != %v\n", count)
+ }
+ if min := h.Min(); 0 != min {
+ t.Errorf("h.Min(): 0 != %v\n", min)
+ }
+ if max := h.Max(); 0 != max {
+ t.Errorf("h.Max(): 0 != %v\n", max)
+ }
+ if mean := h.Mean(); 0.0 != mean {
+ t.Errorf("h.Mean(): 0.0 != %v\n", mean)
+ }
+ if stdDev := h.StdDev(); 0.0 != stdDev {
+ t.Errorf("h.StdDev(): 0.0 != %v\n", stdDev)
+ }
+ ps := h.Percentiles([]float64{0.5, 0.75, 0.99})
+ if 0.0 != ps[0] {
+ t.Errorf("median: 0.0 != %v\n", ps[0])
+ }
+ if 0.0 != ps[1] {
+ t.Errorf("75th percentile: 0.0 != %v\n", ps[1])
+ }
+ if 0.0 != ps[2] {
+ t.Errorf("99th percentile: 0.0 != %v\n", ps[2])
+ }
+}
+
+func TestHistogramSnapshot(t *testing.T) {
+ h := NewHistogram(NewUniformSample(100000))
+ for i := 1; i <= 10000; i++ {
+ h.Update(int64(i))
+ }
+ snapshot := h.Snapshot()
+ h.Update(0)
+ testHistogram10000(t, snapshot)
+}
+
+func testHistogram10000(t *testing.T, h Histogram) {
+ if count := h.Count(); 10000 != count {
+ t.Errorf("h.Count(): 10000 != %v\n", count)
+ }
+ if min := h.Min(); 1 != min {
+ t.Errorf("h.Min(): 1 != %v\n", min)
+ }
+ if max := h.Max(); 10000 != max {
+ t.Errorf("h.Max(): 10000 != %v\n", max)
+ }
+ if mean := h.Mean(); 5000.5 != mean {
+ t.Errorf("h.Mean(): 5000.5 != %v\n", mean)
+ }
+ if stdDev := h.StdDev(); 2886.751331514372 != stdDev {
+ t.Errorf("h.StdDev(): 2886.751331514372 != %v\n", stdDev)
+ }
+ ps := h.Percentiles([]float64{0.5, 0.75, 0.99})
+ if 5000.5 != ps[0] {
+ t.Errorf("median: 5000.5 != %v\n", ps[0])
+ }
+ if 7500.75 != ps[1] {
+ t.Errorf("75th percentile: 7500.75 != %v\n", ps[1])
+ }
+ if 9900.99 != ps[2] {
+ t.Errorf("99th percentile: 9900.99 != %v\n", ps[2])
+ }
+}
diff --git a/metrics/influxdb/LICENSE b/metrics/influxdb/LICENSE
new file mode 100644
index 0000000000..e5bf20cdb5
--- /dev/null
+++ b/metrics/influxdb/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Vincent Rischmann
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/metrics/influxdb/README.md b/metrics/influxdb/README.md
new file mode 100644
index 0000000000..b76b1a3f99
--- /dev/null
+++ b/metrics/influxdb/README.md
@@ -0,0 +1,30 @@
+go-metrics-influxdb
+===================
+
+This is a reporter for the [go-metrics](https://github.com/rcrowley/go-metrics) library which will post the metrics to [InfluxDB](https://influxdb.com/).
+
+Note
+----
+
+This is only compatible with InfluxDB 0.9+.
+
+Usage
+-----
+
+```go
+import "github.com/vrischmann/go-metrics-influxdb"
+
+go influxdb.InfluxDB(
+ metrics.DefaultRegistry, // metrics registry
+ time.Second * 10, // interval
+ "http://localhost:8086", // the InfluxDB url
+ "mydb", // your InfluxDB database
+ "myuser", // your InfluxDB user
+ "mypassword", // your InfluxDB password
+)
+```
+
+License
+-------
+
+go-metrics-influxdb is licensed under the MIT license. See the LICENSE file for details.
diff --git a/metrics/influxdb/influxdb.go b/metrics/influxdb/influxdb.go
new file mode 100644
index 0000000000..d5cb4da66f
--- /dev/null
+++ b/metrics/influxdb/influxdb.go
@@ -0,0 +1,227 @@
+package influxdb
+
+import (
+ "fmt"
+ "log"
+ uurl "net/url"
+ "time"
+
+ "github.com/ethereum/go-ethereum/metrics"
+ "github.com/influxdata/influxdb/client"
+)
+
+type reporter struct {
+ reg metrics.Registry
+ interval time.Duration
+
+ url uurl.URL
+ database string
+ username string
+ password string
+ namespace string
+ tags map[string]string
+
+ client *client.Client
+
+ cache map[string]int64
+}
+
+// InfluxDB starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval.
+func InfluxDB(r metrics.Registry, d time.Duration, url, database, username, password, namespace string) {
+ InfluxDBWithTags(r, d, url, database, username, password, namespace, nil)
+}
+
+// InfluxDBWithTags starts a InfluxDB reporter which will post the from the given metrics.Registry at each d interval with the specified tags
+func InfluxDBWithTags(r metrics.Registry, d time.Duration, url, database, username, password, namespace string, tags map[string]string) {
+ u, err := uurl.Parse(url)
+ if err != nil {
+ log.Printf("unable to parse InfluxDB url %s. err=%v", url, err)
+ return
+ }
+
+ rep := &reporter{
+ reg: r,
+ interval: d,
+ url: *u,
+ database: database,
+ username: username,
+ password: password,
+ namespace: namespace,
+ tags: tags,
+ cache: make(map[string]int64),
+ }
+ if err := rep.makeClient(); err != nil {
+ log.Printf("unable to make InfluxDB client. err=%v", err)
+ return
+ }
+
+ rep.run()
+}
+
+func (r *reporter) makeClient() (err error) {
+ r.client, err = client.NewClient(client.Config{
+ URL: r.url,
+ Username: r.username,
+ Password: r.password,
+ })
+
+ return
+}
+
+func (r *reporter) run() {
+ intervalTicker := time.Tick(r.interval)
+ pingTicker := time.Tick(time.Second * 5)
+
+ for {
+ select {
+ case <-intervalTicker:
+ if err := r.send(); err != nil {
+ log.Printf("unable to send to InfluxDB. err=%v", err)
+ }
+ case <-pingTicker:
+ _, _, err := r.client.Ping()
+ if err != nil {
+ log.Printf("got error while sending a ping to InfluxDB, trying to recreate client. err=%v", err)
+
+ if err = r.makeClient(); err != nil {
+ log.Printf("unable to make InfluxDB client. err=%v", err)
+ }
+ }
+ }
+ }
+}
+
+func (r *reporter) send() error {
+ var pts []client.Point
+
+ r.reg.Each(func(name string, i interface{}) {
+ now := time.Now()
+ namespace := r.namespace
+
+ switch metric := i.(type) {
+ case metrics.Counter:
+ v := metric.Count()
+ l := r.cache[name]
+ pts = append(pts, client.Point{
+ Measurement: fmt.Sprintf("%s%s.count", namespace, name),
+ Tags: r.tags,
+ Fields: map[string]interface{}{
+ "value": v - l,
+ },
+ Time: now,
+ })
+ r.cache[name] = v
+ case metrics.Gauge:
+ ms := metric.Snapshot()
+ pts = append(pts, client.Point{
+ Measurement: fmt.Sprintf("%s%s.gauge", namespace, name),
+ Tags: r.tags,
+ Fields: map[string]interface{}{
+ "value": ms.Value(),
+ },
+ Time: now,
+ })
+ case metrics.GaugeFloat64:
+ ms := metric.Snapshot()
+ pts = append(pts, client.Point{
+ Measurement: fmt.Sprintf("%s%s.gauge", namespace, name),
+ Tags: r.tags,
+ Fields: map[string]interface{}{
+ "value": ms.Value(),
+ },
+ Time: now,
+ })
+ case metrics.Histogram:
+ ms := metric.Snapshot()
+ ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999})
+ pts = append(pts, client.Point{
+ Measurement: fmt.Sprintf("%s%s.histogram", namespace, name),
+ Tags: r.tags,
+ Fields: map[string]interface{}{
+ "count": ms.Count(),
+ "max": ms.Max(),
+ "mean": ms.Mean(),
+ "min": ms.Min(),
+ "stddev": ms.StdDev(),
+ "variance": ms.Variance(),
+ "p50": ps[0],
+ "p75": ps[1],
+ "p95": ps[2],
+ "p99": ps[3],
+ "p999": ps[4],
+ "p9999": ps[5],
+ },
+ Time: now,
+ })
+ case metrics.Meter:
+ ms := metric.Snapshot()
+ pts = append(pts, client.Point{
+ Measurement: fmt.Sprintf("%s%s.meter", namespace, name),
+ Tags: r.tags,
+ Fields: map[string]interface{}{
+ "count": ms.Count(),
+ "m1": ms.Rate1(),
+ "m5": ms.Rate5(),
+ "m15": ms.Rate15(),
+ "mean": ms.RateMean(),
+ },
+ Time: now,
+ })
+ case metrics.Timer:
+ ms := metric.Snapshot()
+ ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999})
+ pts = append(pts, client.Point{
+ Measurement: fmt.Sprintf("%s%s.timer", namespace, name),
+ Tags: r.tags,
+ Fields: map[string]interface{}{
+ "count": ms.Count(),
+ "max": ms.Max(),
+ "mean": ms.Mean(),
+ "min": ms.Min(),
+ "stddev": ms.StdDev(),
+ "variance": ms.Variance(),
+ "p50": ps[0],
+ "p75": ps[1],
+ "p95": ps[2],
+ "p99": ps[3],
+ "p999": ps[4],
+ "p9999": ps[5],
+ "m1": ms.Rate1(),
+ "m5": ms.Rate5(),
+ "m15": ms.Rate15(),
+ "meanrate": ms.RateMean(),
+ },
+ Time: now,
+ })
+ case metrics.ResettingTimer:
+ t := metric.Snapshot()
+
+ if len(t.Values()) > 0 {
+ ps := t.Percentiles([]float64{50, 95, 99})
+ val := t.Values()
+ pts = append(pts, client.Point{
+ Measurement: fmt.Sprintf("%s%s.span", namespace, name),
+ Tags: r.tags,
+ Fields: map[string]interface{}{
+ "count": len(val),
+ "max": val[len(val)-1],
+ "mean": t.Mean(),
+ "min": val[0],
+ "p50": ps[0],
+ "p95": ps[1],
+ "p99": ps[2],
+ },
+ Time: now,
+ })
+ }
+ }
+ })
+
+ bps := client.BatchPoints{
+ Points: pts,
+ Database: r.database,
+ }
+
+ _, err := r.client.Write(bps)
+ return err
+}
diff --git a/metrics/init_test.go b/metrics/init_test.go
new file mode 100644
index 0000000000..43401e833c
--- /dev/null
+++ b/metrics/init_test.go
@@ -0,0 +1,5 @@
+package metrics
+
+func init() {
+ Enabled = true
+}
diff --git a/metrics/json.go b/metrics/json.go
new file mode 100644
index 0000000000..2087d8211e
--- /dev/null
+++ b/metrics/json.go
@@ -0,0 +1,31 @@
+package metrics
+
+import (
+ "encoding/json"
+ "io"
+ "time"
+)
+
+// MarshalJSON returns a byte slice containing a JSON representation of all
+// the metrics in the Registry.
+func (r *StandardRegistry) MarshalJSON() ([]byte, error) {
+ return json.Marshal(r.GetAll())
+}
+
+// WriteJSON writes metrics from the given registry periodically to the
+// specified io.Writer as JSON.
+func WriteJSON(r Registry, d time.Duration, w io.Writer) {
+ for range time.Tick(d) {
+ WriteJSONOnce(r, w)
+ }
+}
+
+// WriteJSONOnce writes metrics from the given registry to the specified
+// io.Writer as JSON.
+func WriteJSONOnce(r Registry, w io.Writer) {
+ json.NewEncoder(w).Encode(r)
+}
+
+func (p *PrefixedRegistry) MarshalJSON() ([]byte, error) {
+ return json.Marshal(p.GetAll())
+}
diff --git a/metrics/json_test.go b/metrics/json_test.go
new file mode 100644
index 0000000000..cf70051f7a
--- /dev/null
+++ b/metrics/json_test.go
@@ -0,0 +1,28 @@
+package metrics
+
+import (
+ "bytes"
+ "encoding/json"
+ "testing"
+)
+
+func TestRegistryMarshallJSON(t *testing.T) {
+ b := &bytes.Buffer{}
+ enc := json.NewEncoder(b)
+ r := NewRegistry()
+ r.Register("counter", NewCounter())
+ enc.Encode(r)
+ if s := b.String(); "{\"counter\":{\"count\":0}}\n" != s {
+ t.Fatalf(s)
+ }
+}
+
+func TestRegistryWriteJSONOnce(t *testing.T) {
+ r := NewRegistry()
+ r.Register("counter", NewCounter())
+ b := &bytes.Buffer{}
+ WriteJSONOnce(r, b)
+ if s := b.String(); s != "{\"counter\":{\"count\":0}}\n" {
+ t.Fail()
+ }
+}
diff --git a/metrics/librato/client.go b/metrics/librato/client.go
new file mode 100644
index 0000000000..8c0c850e38
--- /dev/null
+++ b/metrics/librato/client.go
@@ -0,0 +1,102 @@
+package librato
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+)
+
+const Operations = "operations"
+const OperationsShort = "ops"
+
+type LibratoClient struct {
+ Email, Token string
+}
+
+// property strings
+const (
+ // display attributes
+ Color = "color"
+ DisplayMax = "display_max"
+ DisplayMin = "display_min"
+ DisplayUnitsLong = "display_units_long"
+ DisplayUnitsShort = "display_units_short"
+ DisplayStacked = "display_stacked"
+ DisplayTransform = "display_transform"
+ // special gauge display attributes
+ SummarizeFunction = "summarize_function"
+ Aggregate = "aggregate"
+
+ // metric keys
+ Name = "name"
+ Period = "period"
+ Description = "description"
+ DisplayName = "display_name"
+ Attributes = "attributes"
+
+ // measurement keys
+ MeasureTime = "measure_time"
+ Source = "source"
+ Value = "value"
+
+ // special gauge keys
+ Count = "count"
+ Sum = "sum"
+ Max = "max"
+ Min = "min"
+ SumSquares = "sum_squares"
+
+ // batch keys
+ Counters = "counters"
+ Gauges = "gauges"
+
+ MetricsPostUrl = "https://metrics-api.librato.com/v1/metrics"
+)
+
+type Measurement map[string]interface{}
+type Metric map[string]interface{}
+
+type Batch struct {
+ Gauges []Measurement `json:"gauges,omitempty"`
+ Counters []Measurement `json:"counters,omitempty"`
+ MeasureTime int64 `json:"measure_time"`
+ Source string `json:"source"`
+}
+
+func (self *LibratoClient) PostMetrics(batch Batch) (err error) {
+ var (
+ js []byte
+ req *http.Request
+ resp *http.Response
+ )
+
+ if len(batch.Counters) == 0 && len(batch.Gauges) == 0 {
+ return nil
+ }
+
+ if js, err = json.Marshal(batch); err != nil {
+ return
+ }
+
+ if req, err = http.NewRequest("POST", MetricsPostUrl, bytes.NewBuffer(js)); err != nil {
+ return
+ }
+
+ req.Header.Set("Content-Type", "application/json")
+ req.SetBasicAuth(self.Email, self.Token)
+
+ if resp, err = http.DefaultClient.Do(req); err != nil {
+ return
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ var body []byte
+ if body, err = ioutil.ReadAll(resp.Body); err != nil {
+ body = []byte(fmt.Sprintf("(could not fetch response body for error: %s)", err))
+ }
+ err = fmt.Errorf("Unable to post to Librato: %d %s %s", resp.StatusCode, resp.Status, string(body))
+ }
+ return
+}
diff --git a/metrics/librato/librato.go b/metrics/librato/librato.go
new file mode 100644
index 0000000000..f8c8c9ecb7
--- /dev/null
+++ b/metrics/librato/librato.go
@@ -0,0 +1,235 @@
+package librato
+
+import (
+ "fmt"
+ "log"
+ "math"
+ "regexp"
+ "time"
+
+ "github.com/ethereum/go-ethereum/metrics"
+)
+
+// a regexp for extracting the unit from time.Duration.String
+var unitRegexp = regexp.MustCompile(`[^\\d]+$`)
+
+// a helper that turns a time.Duration into librato display attributes for timer metrics
+func translateTimerAttributes(d time.Duration) (attrs map[string]interface{}) {
+ attrs = make(map[string]interface{})
+ attrs[DisplayTransform] = fmt.Sprintf("x/%d", int64(d))
+ attrs[DisplayUnitsShort] = string(unitRegexp.Find([]byte(d.String())))
+ return
+}
+
+type Reporter struct {
+ Email, Token string
+ Namespace string
+ Source string
+ Interval time.Duration
+ Registry metrics.Registry
+ Percentiles []float64 // percentiles to report on histogram metrics
+ TimerAttributes map[string]interface{} // units in which timers will be displayed
+ intervalSec int64
+}
+
+func NewReporter(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) *Reporter {
+ return &Reporter{e, t, "", s, d, r, p, translateTimerAttributes(u), int64(d / time.Second)}
+}
+
+func Librato(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) {
+ NewReporter(r, d, e, t, s, p, u).Run()
+}
+
+func (self *Reporter) Run() {
+ log.Printf("WARNING: This client has been DEPRECATED! It has been moved to https://github.com/mihasya/go-metrics-librato and will be removed from rcrowley/go-metrics on August 5th 2015")
+ ticker := time.Tick(self.Interval)
+ metricsApi := &LibratoClient{self.Email, self.Token}
+ for now := range ticker {
+ var metrics Batch
+ var err error
+ if metrics, err = self.BuildRequest(now, self.Registry); err != nil {
+ log.Printf("ERROR constructing librato request body %s", err)
+ continue
+ }
+ if err := metricsApi.PostMetrics(metrics); err != nil {
+ log.Printf("ERROR sending metrics to librato %s", err)
+ continue
+ }
+ }
+}
+
+// calculate sum of squares from data provided by metrics.Histogram
+// see http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
+func sumSquares(s metrics.Sample) float64 {
+ count := float64(s.Count())
+ sumSquared := math.Pow(count*s.Mean(), 2)
+ sumSquares := math.Pow(count*s.StdDev(), 2) + sumSquared/count
+ if math.IsNaN(sumSquares) {
+ return 0.0
+ }
+ return sumSquares
+}
+func sumSquaresTimer(t metrics.Timer) float64 {
+ count := float64(t.Count())
+ sumSquared := math.Pow(count*t.Mean(), 2)
+ sumSquares := math.Pow(count*t.StdDev(), 2) + sumSquared/count
+ if math.IsNaN(sumSquares) {
+ return 0.0
+ }
+ return sumSquares
+}
+
+func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Batch, err error) {
+ snapshot = Batch{
+ // coerce timestamps to a stepping fn so that they line up in Librato graphs
+ MeasureTime: (now.Unix() / self.intervalSec) * self.intervalSec,
+ Source: self.Source,
+ }
+ snapshot.Gauges = make([]Measurement, 0)
+ snapshot.Counters = make([]Measurement, 0)
+ histogramGaugeCount := 1 + len(self.Percentiles)
+ r.Each(func(name string, metric interface{}) {
+ if self.Namespace != "" {
+ name = fmt.Sprintf("%s.%s", self.Namespace, name)
+ }
+ measurement := Measurement{}
+ measurement[Period] = self.Interval.Seconds()
+ switch m := metric.(type) {
+ case metrics.Counter:
+ if m.Count() > 0 {
+ measurement[Name] = fmt.Sprintf("%s.%s", name, "count")
+ measurement[Value] = float64(m.Count())
+ measurement[Attributes] = map[string]interface{}{
+ DisplayUnitsLong: Operations,
+ DisplayUnitsShort: OperationsShort,
+ DisplayMin: "0",
+ }
+ snapshot.Counters = append(snapshot.Counters, measurement)
+ }
+ case metrics.Gauge:
+ measurement[Name] = name
+ measurement[Value] = float64(m.Value())
+ snapshot.Gauges = append(snapshot.Gauges, measurement)
+ case metrics.GaugeFloat64:
+ measurement[Name] = name
+ measurement[Value] = m.Value()
+ snapshot.Gauges = append(snapshot.Gauges, measurement)
+ case metrics.Histogram:
+ if m.Count() > 0 {
+ gauges := make([]Measurement, histogramGaugeCount)
+ s := m.Sample()
+ measurement[Name] = fmt.Sprintf("%s.%s", name, "hist")
+ measurement[Count] = uint64(s.Count())
+ measurement[Max] = float64(s.Max())
+ measurement[Min] = float64(s.Min())
+ measurement[Sum] = float64(s.Sum())
+ measurement[SumSquares] = sumSquares(s)
+ gauges[0] = measurement
+ for i, p := range self.Percentiles {
+ gauges[i+1] = Measurement{
+ Name: fmt.Sprintf("%s.%.2f", measurement[Name], p),
+ Value: s.Percentile(p),
+ Period: measurement[Period],
+ }
+ }
+ snapshot.Gauges = append(snapshot.Gauges, gauges...)
+ }
+ case metrics.Meter:
+ measurement[Name] = name
+ measurement[Value] = float64(m.Count())
+ snapshot.Counters = append(snapshot.Counters, measurement)
+ snapshot.Gauges = append(snapshot.Gauges,
+ Measurement{
+ Name: fmt.Sprintf("%s.%s", name, "1min"),
+ Value: m.Rate1(),
+ Period: int64(self.Interval.Seconds()),
+ Attributes: map[string]interface{}{
+ DisplayUnitsLong: Operations,
+ DisplayUnitsShort: OperationsShort,
+ DisplayMin: "0",
+ },
+ },
+ Measurement{
+ Name: fmt.Sprintf("%s.%s", name, "5min"),
+ Value: m.Rate5(),
+ Period: int64(self.Interval.Seconds()),
+ Attributes: map[string]interface{}{
+ DisplayUnitsLong: Operations,
+ DisplayUnitsShort: OperationsShort,
+ DisplayMin: "0",
+ },
+ },
+ Measurement{
+ Name: fmt.Sprintf("%s.%s", name, "15min"),
+ Value: m.Rate15(),
+ Period: int64(self.Interval.Seconds()),
+ Attributes: map[string]interface{}{
+ DisplayUnitsLong: Operations,
+ DisplayUnitsShort: OperationsShort,
+ DisplayMin: "0",
+ },
+ },
+ )
+ case metrics.Timer:
+ measurement[Name] = name
+ measurement[Value] = float64(m.Count())
+ snapshot.Counters = append(snapshot.Counters, measurement)
+ if m.Count() > 0 {
+ libratoName := fmt.Sprintf("%s.%s", name, "timer.mean")
+ gauges := make([]Measurement, histogramGaugeCount)
+ gauges[0] = Measurement{
+ Name: libratoName,
+ Count: uint64(m.Count()),
+ Sum: m.Mean() * float64(m.Count()),
+ Max: float64(m.Max()),
+ Min: float64(m.Min()),
+ SumSquares: sumSquaresTimer(m),
+ Period: int64(self.Interval.Seconds()),
+ Attributes: self.TimerAttributes,
+ }
+ for i, p := range self.Percentiles {
+ gauges[i+1] = Measurement{
+ Name: fmt.Sprintf("%s.timer.%2.0f", name, p*100),
+ Value: m.Percentile(p),
+ Period: int64(self.Interval.Seconds()),
+ Attributes: self.TimerAttributes,
+ }
+ }
+ snapshot.Gauges = append(snapshot.Gauges, gauges...)
+ snapshot.Gauges = append(snapshot.Gauges,
+ Measurement{
+ Name: fmt.Sprintf("%s.%s", name, "rate.1min"),
+ Value: m.Rate1(),
+ Period: int64(self.Interval.Seconds()),
+ Attributes: map[string]interface{}{
+ DisplayUnitsLong: Operations,
+ DisplayUnitsShort: OperationsShort,
+ DisplayMin: "0",
+ },
+ },
+ Measurement{
+ Name: fmt.Sprintf("%s.%s", name, "rate.5min"),
+ Value: m.Rate5(),
+ Period: int64(self.Interval.Seconds()),
+ Attributes: map[string]interface{}{
+ DisplayUnitsLong: Operations,
+ DisplayUnitsShort: OperationsShort,
+ DisplayMin: "0",
+ },
+ },
+ Measurement{
+ Name: fmt.Sprintf("%s.%s", name, "rate.15min"),
+ Value: m.Rate15(),
+ Period: int64(self.Interval.Seconds()),
+ Attributes: map[string]interface{}{
+ DisplayUnitsLong: Operations,
+ DisplayUnitsShort: OperationsShort,
+ DisplayMin: "0",
+ },
+ },
+ )
+ }
+ }
+ })
+ return
+}
diff --git a/vendor/github.com/rcrowley/go-metrics/log.go b/metrics/log.go
similarity index 98%
rename from vendor/github.com/rcrowley/go-metrics/log.go
rename to metrics/log.go
index f8074c0457..0c8ea7c971 100644
--- a/vendor/github.com/rcrowley/go-metrics/log.go
+++ b/metrics/log.go
@@ -18,7 +18,7 @@ func LogScaled(r Registry, freq time.Duration, scale time.Duration, l Logger) {
du := float64(scale)
duSuffix := scale.String()[1:]
- for _ = range time.Tick(freq) {
+ for range time.Tick(freq) {
r.Each(func(name string, i interface{}) {
switch metric := i.(type) {
case Counter:
diff --git a/vendor/github.com/rcrowley/go-metrics/memory.md b/metrics/memory.md
similarity index 100%
rename from vendor/github.com/rcrowley/go-metrics/memory.md
rename to metrics/memory.md
diff --git a/vendor/github.com/rcrowley/go-metrics/meter.go b/metrics/meter.go
similarity index 81%
rename from vendor/github.com/rcrowley/go-metrics/meter.go
rename to metrics/meter.go
index 0389ab0b8f..82b2141a62 100644
--- a/vendor/github.com/rcrowley/go-metrics/meter.go
+++ b/metrics/meter.go
@@ -15,10 +15,13 @@ type Meter interface {
Rate15() float64
RateMean() float64
Snapshot() Meter
+ Stop()
}
// GetOrRegisterMeter returns an existing Meter or constructs and registers a
// new StandardMeter.
+// Be sure to unregister the meter from the registry once it is of no use to
+// allow for garbage collection.
func GetOrRegisterMeter(name string, r Registry) Meter {
if nil == r {
r = DefaultRegistry
@@ -27,14 +30,15 @@ func GetOrRegisterMeter(name string, r Registry) Meter {
}
// NewMeter constructs a new StandardMeter and launches a goroutine.
+// Be sure to call Stop() once the meter is of no use to allow for garbage collection.
func NewMeter() Meter {
- if UseNilMetrics {
+ if !Enabled {
return NilMeter{}
}
m := newStandardMeter()
arbiter.Lock()
defer arbiter.Unlock()
- arbiter.meters = append(arbiter.meters, m)
+ arbiter.meters[m] = struct{}{}
if !arbiter.started {
arbiter.started = true
go arbiter.tick()
@@ -44,6 +48,8 @@ func NewMeter() Meter {
// NewMeter constructs and registers a new StandardMeter and launches a
// goroutine.
+// Be sure to unregister the meter from the registry once it is of no use to
+// allow for garbage collection.
func NewRegisteredMeter(name string, r Registry) Meter {
c := NewMeter()
if nil == r {
@@ -86,6 +92,9 @@ func (m *MeterSnapshot) RateMean() float64 { return m.rateMean }
// Snapshot returns the snapshot.
func (m *MeterSnapshot) Snapshot() Meter { return m }
+// Stop is a no-op.
+func (m *MeterSnapshot) Stop() {}
+
// NilMeter is a no-op Meter.
type NilMeter struct{}
@@ -110,12 +119,16 @@ func (NilMeter) RateMean() float64 { return 0.0 }
// Snapshot is a no-op.
func (NilMeter) Snapshot() Meter { return NilMeter{} }
+// Stop is a no-op.
+func (NilMeter) Stop() {}
+
// StandardMeter is the standard implementation of a Meter.
type StandardMeter struct {
lock sync.RWMutex
snapshot *MeterSnapshot
a1, a5, a15 EWMA
startTime time.Time
+ stopped bool
}
func newStandardMeter() *StandardMeter {
@@ -128,6 +141,19 @@ func newStandardMeter() *StandardMeter {
}
}
+// Stop stops the meter, Mark() will be a no-op if you use it after being stopped.
+func (m *StandardMeter) Stop() {
+ m.lock.Lock()
+ stopped := m.stopped
+ m.stopped = true
+ m.lock.Unlock()
+ if !stopped {
+ arbiter.Lock()
+ delete(arbiter.meters, m)
+ arbiter.Unlock()
+ }
+}
+
// Count returns the number of events recorded.
func (m *StandardMeter) Count() int64 {
m.lock.RLock()
@@ -136,10 +162,13 @@ func (m *StandardMeter) Count() int64 {
return count
}
-// Mark records the occurance of n events.
+// Mark records the occurrence of n events.
func (m *StandardMeter) Mark(n int64) {
m.lock.Lock()
defer m.lock.Unlock()
+ if m.stopped {
+ return
+ }
m.snapshot.count += n
m.a1.Update(n)
m.a5.Update(n)
@@ -205,29 +234,28 @@ func (m *StandardMeter) tick() {
m.updateSnapshot()
}
+// meterArbiter ticks meters every 5s from a single goroutine.
+// meters are references in a set for future stopping.
type meterArbiter struct {
sync.RWMutex
started bool
- meters []*StandardMeter
+ meters map[*StandardMeter]struct{}
ticker *time.Ticker
}
-var arbiter = meterArbiter{ticker: time.NewTicker(5e9)}
+var arbiter = meterArbiter{ticker: time.NewTicker(5e9), meters: make(map[*StandardMeter]struct{})}
// Ticks meters on the scheduled interval
func (ma *meterArbiter) tick() {
- for {
- select {
- case <-ma.ticker.C:
- ma.tickMeters()
- }
+ for range ma.ticker.C {
+ ma.tickMeters()
}
}
func (ma *meterArbiter) tickMeters() {
ma.RLock()
defer ma.RUnlock()
- for _, meter := range ma.meters {
+ for meter := range ma.meters {
meter.tick()
}
}
diff --git a/metrics/meter_test.go b/metrics/meter_test.go
new file mode 100644
index 0000000000..e889222601
--- /dev/null
+++ b/metrics/meter_test.go
@@ -0,0 +1,73 @@
+package metrics
+
+import (
+ "testing"
+ "time"
+)
+
+func BenchmarkMeter(b *testing.B) {
+ m := NewMeter()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ m.Mark(1)
+ }
+}
+
+func TestGetOrRegisterMeter(t *testing.T) {
+ r := NewRegistry()
+ NewRegisteredMeter("foo", r).Mark(47)
+ if m := GetOrRegisterMeter("foo", r); 47 != m.Count() {
+ t.Fatal(m)
+ }
+}
+
+func TestMeterDecay(t *testing.T) {
+ ma := meterArbiter{
+ ticker: time.NewTicker(time.Millisecond),
+ meters: make(map[*StandardMeter]struct{}),
+ }
+ m := newStandardMeter()
+ ma.meters[m] = struct{}{}
+ go ma.tick()
+ m.Mark(1)
+ rateMean := m.RateMean()
+ time.Sleep(100 * time.Millisecond)
+ if m.RateMean() >= rateMean {
+ t.Error("m.RateMean() didn't decrease")
+ }
+}
+
+func TestMeterNonzero(t *testing.T) {
+ m := NewMeter()
+ m.Mark(3)
+ if count := m.Count(); 3 != count {
+ t.Errorf("m.Count(): 3 != %v\n", count)
+ }
+}
+
+func TestMeterStop(t *testing.T) {
+ l := len(arbiter.meters)
+ m := NewMeter()
+ if len(arbiter.meters) != l+1 {
+ t.Errorf("arbiter.meters: %d != %d\n", l+1, len(arbiter.meters))
+ }
+ m.Stop()
+ if len(arbiter.meters) != l {
+ t.Errorf("arbiter.meters: %d != %d\n", l, len(arbiter.meters))
+ }
+}
+
+func TestMeterSnapshot(t *testing.T) {
+ m := NewMeter()
+ m.Mark(1)
+ if snapshot := m.Snapshot(); m.RateMean() != snapshot.RateMean() {
+ t.Fatal(snapshot)
+ }
+}
+
+func TestMeterZero(t *testing.T) {
+ m := NewMeter()
+ if count := m.Count(); 0 != count {
+ t.Errorf("m.Count(): 0 != %v\n", count)
+ }
+}
diff --git a/metrics/metrics.go b/metrics/metrics.go
index c82661d802..e24324814c 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -1,20 +1,8 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
+// Go port of Coda Hale's Metrics library
//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
+//
`[1:])) + +var landingPageTemplate = template.Must(template.New("landingPage").Parse(` + +
+ + + + + + +
+ +
+