Merge commit 'eae63c511ceafab14b92e274c1b18bf1700e2d3d'

This commit is contained in:
Wei Tang 2018-06-02 06:21:31 +08:00
commit d3b90bfa81
70 changed files with 886 additions and 530 deletions

View file

@ -1 +1 @@
1.8.9 1.8.10

View file

@ -454,7 +454,7 @@ func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*ty
return logs, nil return logs, nil
} }
func (fb *filterBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return event.NewSubscription(func(quit <-chan struct{}) error { return event.NewSubscription(func(quit <-chan struct{}) error {
<-quit <-quit
return nil return nil

View file

@ -19,15 +19,20 @@ package common
import "encoding/hex" import "encoding/hex"
// ToHex returns the hex representation of b, prefixed with '0x'.
// For empty slices, the return value is "0x0".
//
// Deprecated: use hexutil.Encode instead.
func ToHex(b []byte) string { func ToHex(b []byte) string {
hex := Bytes2Hex(b) hex := Bytes2Hex(b)
// Prefer output of "0x0" instead of "0x"
if len(hex) == 0 { if len(hex) == 0 {
hex = "0" hex = "0"
} }
return "0x" + hex return "0x" + hex
} }
// FromHex returns the bytes represented by the hexadecimal string s.
// s may be prefixed with "0x".
func FromHex(s string) []byte { func FromHex(s string) []byte {
if len(s) > 1 { if len(s) > 1 {
if s[0:2] == "0x" || s[0:2] == "0X" { if s[0:2] == "0x" || s[0:2] == "0X" {
@ -40,9 +45,7 @@ func FromHex(s string) []byte {
return Hex2Bytes(s) return Hex2Bytes(s)
} }
// Copy bytes // CopyBytes returns an exact copy of the provided bytes.
//
// Returns an exact copy of the provided bytes
func CopyBytes(b []byte) (copiedBytes []byte) { func CopyBytes(b []byte) (copiedBytes []byte) {
if b == nil { if b == nil {
return nil return nil
@ -53,14 +56,17 @@ func CopyBytes(b []byte) (copiedBytes []byte) {
return return
} }
// hasHexPrefix validates str begins with '0x' or '0X'.
func hasHexPrefix(str string) bool { func hasHexPrefix(str string) bool {
return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')
} }
// isHexCharacter returns bool of c being a valid hexadecimal.
func isHexCharacter(c byte) bool { func isHexCharacter(c byte) bool {
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F') return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
} }
// isHex validates whether each byte is valid hexadecimal string.
func isHex(str string) bool { func isHex(str string) bool {
if len(str)%2 != 0 { if len(str)%2 != 0 {
return false return false
@ -73,16 +79,18 @@ func isHex(str string) bool {
return true return true
} }
// Bytes2Hex returns the hexadecimal encoding of d.
func Bytes2Hex(d []byte) string { func Bytes2Hex(d []byte) string {
return hex.EncodeToString(d) return hex.EncodeToString(d)
} }
// Hex2Bytes returns the bytes represented by the hexadecimal string str.
func Hex2Bytes(str string) []byte { func Hex2Bytes(str string) []byte {
h, _ := hex.DecodeString(str) h, _ := hex.DecodeString(str)
return h return h
} }
// Hex2BytesFixed returns bytes of a specified fixed length flen.
func Hex2BytesFixed(str string, flen int) []byte { func Hex2BytesFixed(str string, flen int) []byte {
h, _ := hex.DecodeString(str) h, _ := hex.DecodeString(str)
if len(h) == flen { if len(h) == flen {
@ -96,6 +104,7 @@ func Hex2BytesFixed(str string, flen int) []byte {
return hh return hh
} }
// RightPadBytes zero-pads slice to the right up to length l.
func RightPadBytes(slice []byte, l int) []byte { func RightPadBytes(slice []byte, l int) []byte {
if l <= len(slice) { if l <= len(slice) {
return slice return slice
@ -107,6 +116,7 @@ func RightPadBytes(slice []byte, l int) []byte {
return padded return padded
} }
// LeftPadBytes zero-pads slice to the left up to length l.
func LeftPadBytes(slice []byte, l int) []byte { func LeftPadBytes(slice []byte, l int) []byte {
if l <= len(slice) { if l <= len(slice) {
return slice return slice

View file

@ -78,7 +78,7 @@ func ParseBig256(s string) (*big.Int, bool) {
return bigint, ok return bigint, ok
} }
// MustParseBig parses s as a 256 bit big integer and panics if the string is invalid. // MustParseBig256 parses s as a 256 bit big integer and panics if the string is invalid.
func MustParseBig256(s string) *big.Int { func MustParseBig256(s string) *big.Int {
v, ok := ParseBig256(s) v, ok := ParseBig256(s)
if !ok { if !ok {
@ -186,9 +186,8 @@ func U256(x *big.Int) *big.Int {
func S256(x *big.Int) *big.Int { func S256(x *big.Int) *big.Int {
if x.Cmp(tt255) < 0 { if x.Cmp(tt255) < 0 {
return x return x
} else {
return new(big.Int).Sub(x, tt256)
} }
return new(big.Int).Sub(x, tt256)
} }
// Exp implements exponentiation by squaring. // Exp implements exponentiation by squaring.

View file

@ -34,13 +34,12 @@ func limitUnsigned256(x *Number) *Number {
func limitSigned256(x *Number) *Number { func limitSigned256(x *Number) *Number {
if x.num.Cmp(tt255) < 0 { if x.num.Cmp(tt255) < 0 {
return x return x
} else {
x.num.Sub(x.num, tt256)
return x
} }
x.num.Sub(x.num, tt256)
return x
} }
// Number function // Initialiser is a Number function
type Initialiser func(n int64) *Number type Initialiser func(n int64) *Number
// A Number represents a generic integer with a bounding function limiter. Limit is called after each operations // A Number represents a generic integer with a bounding function limiter. Limit is called after each operations
@ -51,65 +50,65 @@ type Number struct {
limit func(n *Number) *Number limit func(n *Number) *Number
} }
// Returns a new initialiser for a new *Number without having to expose certain fields // NewInitialiser returns a new initialiser for a new *Number without having to expose certain fields
func NewInitialiser(limiter func(*Number) *Number) Initialiser { func NewInitialiser(limiter func(*Number) *Number) Initialiser {
return func(n int64) *Number { return func(n int64) *Number {
return &Number{big.NewInt(n), limiter} return &Number{big.NewInt(n), limiter}
} }
} }
// Return a Number with a UNSIGNED limiter up to 256 bits // Uint256 returns a Number with a UNSIGNED limiter up to 256 bits
func Uint256(n int64) *Number { func Uint256(n int64) *Number {
return &Number{big.NewInt(n), limitUnsigned256} return &Number{big.NewInt(n), limitUnsigned256}
} }
// Return a Number with a SIGNED limiter up to 256 bits // Int256 returns Number with a SIGNED limiter up to 256 bits
func Int256(n int64) *Number { func Int256(n int64) *Number {
return &Number{big.NewInt(n), limitSigned256} return &Number{big.NewInt(n), limitSigned256}
} }
// Returns a Number with a SIGNED unlimited size // Big returns a Number with a SIGNED unlimited size
func Big(n int64) *Number { func Big(n int64) *Number {
return &Number{big.NewInt(n), func(x *Number) *Number { return x }} return &Number{big.NewInt(n), func(x *Number) *Number { return x }}
} }
// Sets i to sum of x+y // Add sets i to sum of x+y
func (i *Number) Add(x, y *Number) *Number { func (i *Number) Add(x, y *Number) *Number {
i.num.Add(x.num, y.num) i.num.Add(x.num, y.num)
return i.limit(i) return i.limit(i)
} }
// Sets i to difference of x-y // Sub sets i to difference of x-y
func (i *Number) Sub(x, y *Number) *Number { func (i *Number) Sub(x, y *Number) *Number {
i.num.Sub(x.num, y.num) i.num.Sub(x.num, y.num)
return i.limit(i) return i.limit(i)
} }
// Sets i to product of x*y // Mul sets i to product of x*y
func (i *Number) Mul(x, y *Number) *Number { func (i *Number) Mul(x, y *Number) *Number {
i.num.Mul(x.num, y.num) i.num.Mul(x.num, y.num)
return i.limit(i) return i.limit(i)
} }
// Sets i to the quotient prodject of x/y // Div sets i to the quotient prodject of x/y
func (i *Number) Div(x, y *Number) *Number { func (i *Number) Div(x, y *Number) *Number {
i.num.Div(x.num, y.num) i.num.Div(x.num, y.num)
return i.limit(i) return i.limit(i)
} }
// Sets i to x % y // Mod sets i to x % y
func (i *Number) Mod(x, y *Number) *Number { func (i *Number) Mod(x, y *Number) *Number {
i.num.Mod(x.num, y.num) i.num.Mod(x.num, y.num)
return i.limit(i) return i.limit(i)
} }
// Sets i to x << s // Lsh sets i to x << s
func (i *Number) Lsh(x *Number, s uint) *Number { func (i *Number) Lsh(x *Number, s uint) *Number {
i.num.Lsh(x.num, s) i.num.Lsh(x.num, s)
return i.limit(i) return i.limit(i)
} }
// Sets i to x^y // Pow sets i to x^y
func (i *Number) Pow(x, y *Number) *Number { func (i *Number) Pow(x, y *Number) *Number {
i.num.Exp(x.num, y.num, big.NewInt(0)) i.num.Exp(x.num, y.num, big.NewInt(0))
return i.limit(i) return i.limit(i)
@ -117,13 +116,13 @@ func (i *Number) Pow(x, y *Number) *Number {
// Setters // Setters
// Set x to i // Set sets x to i
func (i *Number) Set(x *Number) *Number { func (i *Number) Set(x *Number) *Number {
i.num.Set(x.num) i.num.Set(x.num)
return i.limit(i) return i.limit(i)
} }
// Set x bytes to i // SetBytes sets x bytes to i
func (i *Number) SetBytes(x []byte) *Number { func (i *Number) SetBytes(x []byte) *Number {
i.num.SetBytes(x) i.num.SetBytes(x)
return i.limit(i) return i.limit(i)
@ -140,12 +139,12 @@ func (i *Number) Cmp(x *Number) int {
// Getters // Getters
// Returns the string representation of i // String returns the string representation of i
func (i *Number) String() string { func (i *Number) String() string {
return i.num.String() return i.num.String()
} }
// Returns the byte representation of i // Bytes returns the byte representation of i
func (i *Number) Bytes() []byte { func (i *Number) Bytes() []byte {
return i.num.Bytes() return i.num.Bytes()
} }
@ -160,17 +159,17 @@ func (i *Number) Int64() int64 {
return i.num.Int64() return i.num.Int64()
} }
// Returns the signed version of i // Int256 returns the signed version of i
func (i *Number) Int256() *Number { func (i *Number) Int256() *Number {
return Int(0).Set(i) return Int(0).Set(i)
} }
// Returns the unsigned version of i // Uint256 returns the unsigned version of i
func (i *Number) Uint256() *Number { func (i *Number) Uint256() *Number {
return Uint(0).Set(i) return Uint(0).Set(i)
} }
// Returns the index of the first bit that's set to 1 // FirstBitSet returns the index of the first bit that's set to 1
func (i *Number) FirstBitSet() int { func (i *Number) FirstBitSet() int {
for j := 0; j < i.num.BitLen(); j++ { for j := 0; j < i.num.BitLen(); j++ {
if i.num.Bit(j) > 0 { if i.num.Bit(j) > 0 {

View file

@ -30,6 +30,7 @@ func MakeName(name, version string) string {
return fmt.Sprintf("%s/v%s/%s/%s", name, version, runtime.GOOS, runtime.Version()) return fmt.Sprintf("%s/v%s/%s/%s", name, version, runtime.GOOS, runtime.Version())
} }
// FileExist checks if a file exists at filePath.
func FileExist(filePath string) bool { func FileExist(filePath string) bool {
_, err := os.Stat(filePath) _, err := os.Stat(filePath)
if err != nil && os.IsNotExist(err) { if err != nil && os.IsNotExist(err) {
@ -39,9 +40,10 @@ func FileExist(filePath string) bool {
return true return true
} }
func AbsolutePath(Datadir string, filename string) string { // AbsolutePath returns datadir + filename, or filename if it is absolute.
func AbsolutePath(datadir string, filename string) string {
if filepath.IsAbs(filename) { if filepath.IsAbs(filename) {
return filename return filename
} }
return filepath.Join(Datadir, filename) return filepath.Join(datadir, filename)
} }

View file

@ -42,19 +42,30 @@ var (
// Hash represents the 32 byte Keccak256 hash of arbitrary data. // Hash represents the 32 byte Keccak256 hash of arbitrary data.
type Hash [HashLength]byte type Hash [HashLength]byte
// BytesToHash sets b to hash.
// If b is larger than len(h), b will be cropped from the left.
func BytesToHash(b []byte) Hash { func BytesToHash(b []byte) Hash {
var h Hash var h Hash
h.SetBytes(b) h.SetBytes(b)
return h return h
} }
func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) }
func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) }
// Get the string representation of the underlying hash // BigToHash sets byte representation of b to hash.
func (h Hash) Str() string { return string(h[:]) } // If b is larger than len(h), b will be cropped from the left.
func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) }
// HexToHash sets byte representation of s to hash.
// If b is larger than len(h), b will be cropped from the left.
func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) }
// Bytes gets the byte representation of the underlying hash.
func (h Hash) Bytes() []byte { return h[:] } func (h Hash) Bytes() []byte { return h[:] }
// Big converts a hash to a big integer.
func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) } func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) }
func (h Hash) Hex() string { return hexutil.Encode(h[:]) }
// Hex converts a hash to a hex string.
func (h Hash) Hex() string { return hexutil.Encode(h[:]) }
// TerminalString implements log.TerminalStringer, formatting a string for console // TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging. // output during logging.
@ -89,7 +100,8 @@ func (h Hash) MarshalText() ([]byte, error) {
return hexutil.Bytes(h[:]).MarshalText() return hexutil.Bytes(h[:]).MarshalText()
} }
// Sets the hash to the value of b. If b is larger than len(h), 'b' will be cropped (from the left). // SetBytes sets the hash to the value of b.
// If b is larger than len(h), b will be cropped from the left.
func (h *Hash) SetBytes(b []byte) { func (h *Hash) SetBytes(b []byte) {
if len(b) > len(h) { if len(b) > len(h) {
b = b[len(b)-HashLength:] b = b[len(b)-HashLength:]
@ -98,16 +110,6 @@ func (h *Hash) SetBytes(b []byte) {
copy(h[HashLength-len(b):], b) copy(h[HashLength-len(b):], b)
} }
// Set string `s` to h. If s is larger than len(h) s will be cropped (from left) to fit.
func (h *Hash) SetString(s string) { h.SetBytes([]byte(s)) }
// Sets h to other
func (h *Hash) Set(other Hash) {
for i, v := range other {
h[i] = v
}
}
// Generate implements testing/quick.Generator. // Generate implements testing/quick.Generator.
func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value {
m := rand.Intn(len(h)) m := rand.Intn(len(h))
@ -117,10 +119,6 @@ func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value {
return reflect.ValueOf(h) return reflect.ValueOf(h)
} }
func EmptyHash(h Hash) bool {
return h == Hash{}
}
// UnprefixedHash allows marshaling a Hash without 0x prefix. // UnprefixedHash allows marshaling a Hash without 0x prefix.
type UnprefixedHash Hash type UnprefixedHash Hash
@ -139,13 +137,21 @@ func (h UnprefixedHash) MarshalText() ([]byte, error) {
// Address represents the 20 byte address of an Ethereum account. // Address represents the 20 byte address of an Ethereum account.
type Address [AddressLength]byte type Address [AddressLength]byte
// BytesToAddress returns Address with value b.
// If b is larger than len(h), b will be cropped from the left.
func BytesToAddress(b []byte) Address { func BytesToAddress(b []byte) Address {
var a Address var a Address
a.SetBytes(b) a.SetBytes(b)
return a return a
} }
// BigToAddress returns Address with byte values of b.
// If b is larger than len(h), b will be cropped from the left.
func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) }
func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) }
// HexToAddress returns Address with byte values of s.
// If s is larger than len(h), s will be cropped from the left.
func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) }
// IsHexAddress verifies whether a string can represent a valid hex-encoded // IsHexAddress verifies whether a string can represent a valid hex-encoded
// Ethereum address or not. // Ethereum address or not.
@ -156,11 +162,14 @@ func IsHexAddress(s string) bool {
return len(s) == 2*AddressLength && isHex(s) return len(s) == 2*AddressLength && isHex(s)
} }
// Get the string representation of the underlying address // Bytes gets the string representation of the underlying address.
func (a Address) Str() string { return string(a[:]) }
func (a Address) Bytes() []byte { return a[:] } func (a Address) Bytes() []byte { return a[:] }
// Big converts an address to a big integer.
func (a Address) Big() *big.Int { return new(big.Int).SetBytes(a[:]) } func (a Address) Big() *big.Int { return new(big.Int).SetBytes(a[:]) }
func (a Address) Hash() Hash { return BytesToHash(a[:]) }
// Hash converts an address to a hash by left-padding it with zeros.
func (a Address) Hash() Hash { return BytesToHash(a[:]) }
// Hex returns an EIP55-compliant hex string representation of the address. // Hex returns an EIP55-compliant hex string representation of the address.
func (a Address) Hex() string { func (a Address) Hex() string {
@ -184,7 +193,7 @@ func (a Address) Hex() string {
return "0x" + string(result) return "0x" + string(result)
} }
// String implements the stringer interface and is used also by the logger. // String implements fmt.Stringer.
func (a Address) String() string { func (a Address) String() string {
return a.Hex() return a.Hex()
} }
@ -195,7 +204,8 @@ func (a Address) Format(s fmt.State, c rune) {
fmt.Fprintf(s, "%"+string(c), a[:]) fmt.Fprintf(s, "%"+string(c), a[:])
} }
// Sets the address to the value of b. If b is larger than len(a) it will panic // SetBytes sets the address to the value of b.
// If b is larger than len(a) it will panic.
func (a *Address) SetBytes(b []byte) { func (a *Address) SetBytes(b []byte) {
if len(b) > len(a) { if len(b) > len(a) {
b = b[len(b)-AddressLength:] b = b[len(b)-AddressLength:]
@ -203,16 +213,6 @@ func (a *Address) SetBytes(b []byte) {
copy(a[AddressLength-len(b):], b) copy(a[AddressLength-len(b):], b)
} }
// Set string `s` to a. If s is larger than len(a) it will panic
func (a *Address) SetString(s string) { a.SetBytes([]byte(s)) }
// Sets a to other
func (a *Address) Set(other Address) {
for i, v := range other {
a[i] = v
}
}
// MarshalText returns the hex representation of a. // MarshalText returns the hex representation of a.
func (a Address) MarshalText() ([]byte, error) { func (a Address) MarshalText() ([]byte, error) {
return hexutil.Bytes(a[:]).MarshalText() return hexutil.Bytes(a[:]).MarshalText()
@ -228,7 +228,7 @@ func (a *Address) UnmarshalJSON(input []byte) error {
return hexutil.UnmarshalFixedJSON(addressT, input, a[:]) return hexutil.UnmarshalFixedJSON(addressT, input, a[:])
} }
// UnprefixedHash allows marshaling an Address without 0x prefix. // UnprefixedAddress allows marshaling an Address without 0x prefix.
type UnprefixedAddress Address type UnprefixedAddress Address
// UnmarshalText decodes the address from hex. The 0x prefix is optional. // UnmarshalText decodes the address from hex. The 0x prefix is optional.

View file

@ -1,64 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// +build none
//sed -e 's/_N_/Hash/g' -e 's/_S_/32/g' -e '1d' types_template.go | gofmt -w hash.go
package common
import "math/big"
type _N_ [_S_]byte
func BytesTo_N_(b []byte) _N_ {
var h _N_
h.SetBytes(b)
return h
}
func StringTo_N_(s string) _N_ { return BytesTo_N_([]byte(s)) }
func BigTo_N_(b *big.Int) _N_ { return BytesTo_N_(b.Bytes()) }
func HexTo_N_(s string) _N_ { return BytesTo_N_(FromHex(s)) }
// Don't use the default 'String' method in case we want to overwrite
// Get the string representation of the underlying hash
func (h _N_) Str() string { return string(h[:]) }
func (h _N_) Bytes() []byte { return h[:] }
func (h _N_) Big() *big.Int { return new(big.Int).SetBytes(h[:]) }
func (h _N_) Hex() string { return "0x" + Bytes2Hex(h[:]) }
// Sets the hash to the value of b. If b is larger than len(h) it will panic
func (h *_N_) SetBytes(b []byte) {
// Use the right most bytes
if len(b) > len(h) {
b = b[len(b)-_S_:]
}
// Reverse the loop
for i := len(b) - 1; i >= 0; i-- {
h[_S_-len(b)+i] = b[i]
}
}
// Set string `s` to h. If s is larger than len(h) it will panic
func (h *_N_) SetString(s string) { h.SetBytes([]byte(s)) }
// Sets h to other
func (h *_N_) Set(other _N_) {
for i, v := range other {
h[i] = v
}
}

View file

@ -383,7 +383,7 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo
// If an on-disk checkpoint snapshot can be found, use that // If an on-disk checkpoint snapshot can be found, use that
if number%checkpointInterval == 0 { if number%checkpointInterval == 0 {
if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil { if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash) log.Trace("Loaded voting snapshot from disk", "number", number, "hash", hash)
snap = s snap = s
break break
} }

View file

@ -21,8 +21,8 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
// TxPreEvent is posted when a transaction enters the transaction pool. // NewTxsEvent is posted when a batch of transactions enter the transaction pool.
type TxPreEvent struct{ Tx *types.Transaction } type NewTxsEvent struct{ Txs []*types.Transaction }
// PendingLogsEvent is posted pre mining and notifies of pending logs. // PendingLogsEvent is posted pre mining and notifies of pending logs.
type PendingLogsEvent struct { type PendingLogsEvent struct {
@ -35,9 +35,6 @@ type PendingStateEvent struct{}
// NewMinedBlockEvent is posted when a block has been imported. // NewMinedBlockEvent is posted when a block has been imported.
type NewMinedBlockEvent struct{ Block *types.Block } type NewMinedBlockEvent struct{ Block *types.Block }
// RemovedTransactionEvent is posted when a reorg happens
type RemovedTransactionEvent struct{ Txs types.Transactions }
// RemovedLogsEvent is posted when a reorg happens // RemovedLogsEvent is posted when a reorg happens
type RemovedLogsEvent struct{ Logs []*types.Log } type RemovedLogsEvent struct{ Logs []*types.Log }

View file

@ -99,7 +99,7 @@ func (s *StateSuite) TestNull(c *checker.C) {
s.state.SetState(address, common.Hash{}, value) s.state.SetState(address, common.Hash{}, value)
s.state.Commit(false) s.state.Commit(false)
value = s.state.GetState(address, common.Hash{}) value = s.state.GetState(address, common.Hash{})
if !common.EmptyHash(value) { if value != (common.Hash{}) {
c.Errorf("expected empty hash. got %x", value) c.Errorf("expected empty hash. got %x", value)
} }
} }

View file

@ -358,7 +358,7 @@ func (self *StateDB) deleteStateObject(stateObject *stateObject) {
self.setError(self.trie.TryDelete(addr[:])) self.setError(self.trie.TryDelete(addr[:]))
} }
// Retrieve a state object given my the address. Returns nil if not found. // Retrieve a state object given by the address. Returns nil if not found.
func (self *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) { func (self *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) {
// Prefer 'live' objects. // Prefer 'live' objects.
if obj := self.stateObjects[addr]; obj != nil { if obj := self.stateObjects[addr]; obj != nil {

View file

@ -25,8 +25,8 @@ import (
) )
// NewStateSync create a new state trie download scheduler. // NewStateSync create a new state trie download scheduler.
func NewStateSync(root common.Hash, database trie.DatabaseReader) *trie.TrieSync { func NewStateSync(root common.Hash, database trie.DatabaseReader) *trie.Sync {
var syncer *trie.TrieSync var syncer *trie.Sync
callback := func(leaf []byte, parent common.Hash) error { callback := func(leaf []byte, parent common.Hash) error {
var obj Account var obj Account
if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil { if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil {
@ -36,6 +36,6 @@ func NewStateSync(root common.Hash, database trie.DatabaseReader) *trie.TrieSync
syncer.AddRawEntry(common.BytesToHash(obj.CodeHash), 64, parent) syncer.AddRawEntry(common.BytesToHash(obj.CodeHash), 64, parent)
return nil return nil
} }
syncer = trie.NewTrieSync(root, database, callback) syncer = trie.NewSync(root, database, callback)
return syncer return syncer
} }

View file

@ -56,7 +56,7 @@ func newTxJournal(path string) *txJournal {
// load parses a transaction journal dump from disk, loading its contents into // load parses a transaction journal dump from disk, loading its contents into
// the specified pool. // the specified pool.
func (journal *txJournal) load(add func(*types.Transaction) error) error { func (journal *txJournal) load(add func([]*types.Transaction) []error) error {
// Skip the parsing if the journal file doens't exist at all // Skip the parsing if the journal file doens't exist at all
if _, err := os.Stat(journal.path); os.IsNotExist(err) { if _, err := os.Stat(journal.path); os.IsNotExist(err) {
return nil return nil
@ -76,7 +76,21 @@ func (journal *txJournal) load(add func(*types.Transaction) error) error {
stream := rlp.NewStream(input, 0) stream := rlp.NewStream(input, 0)
total, dropped := 0, 0 total, dropped := 0, 0
var failure error // Create a method to load a limited batch of transactions and bump the
// appropriate progress counters. Then use this method to load all the
// journalled transactions in small-ish batches.
loadBatch := func(txs types.Transactions) {
for _, err := range add(txs) {
if err != nil {
log.Debug("Failed to add journaled transaction", "err", err)
dropped++
}
}
}
var (
failure error
batch types.Transactions
)
for { for {
// Parse the next transaction and terminate on error // Parse the next transaction and terminate on error
tx := new(types.Transaction) tx := new(types.Transaction)
@ -84,14 +98,17 @@ func (journal *txJournal) load(add func(*types.Transaction) error) error {
if err != io.EOF { if err != io.EOF {
failure = err failure = err
} }
if batch.Len() > 0 {
loadBatch(batch)
}
break break
} }
// Import the transaction and bump the appropriate progress counters // New transaction parsed, queue up for later, import if threnshold is reached
total++ total++
if err = add(tx); err != nil {
log.Debug("Failed to add journaled transaction", "err", err) if batch = append(batch, tx); batch.Len() > 1024 {
dropped++ loadBatch(batch)
continue batch = batch[:0]
} }
} }
log.Info("Loaded local transaction journal", "transactions", total, "dropped", dropped) log.Info("Loaded local transaction journal", "transactions", total, "dropped", dropped)

View file

@ -397,13 +397,13 @@ func (h *priceHeap) Pop() interface{} {
// txPricedList is a price-sorted heap to allow operating on transactions pool // txPricedList is a price-sorted heap to allow operating on transactions pool
// contents in a price-incrementing way. // contents in a price-incrementing way.
type txPricedList struct { type txPricedList struct {
all *map[common.Hash]*types.Transaction // Pointer to the map of all transactions all *txLookup // Pointer to the map of all transactions
items *priceHeap // Heap of prices of all the stored transactions items *priceHeap // Heap of prices of all the stored transactions
stales int // Number of stale price points to (re-heap trigger) stales int // Number of stale price points to (re-heap trigger)
} }
// newTxPricedList creates a new price-sorted transaction heap. // newTxPricedList creates a new price-sorted transaction heap.
func newTxPricedList(all *map[common.Hash]*types.Transaction) *txPricedList { func newTxPricedList(all *txLookup) *txPricedList {
return &txPricedList{ return &txPricedList{
all: all, all: all,
items: new(priceHeap), items: new(priceHeap),
@ -425,12 +425,13 @@ func (l *txPricedList) Removed() {
return return
} }
// Seems we've reached a critical number of stale transactions, reheap // Seems we've reached a critical number of stale transactions, reheap
reheap := make(priceHeap, 0, len(*l.all)) reheap := make(priceHeap, 0, l.all.Count())
l.stales, l.items = 0, &reheap l.stales, l.items = 0, &reheap
for _, tx := range *l.all { l.all.Range(func(hash common.Hash, tx *types.Transaction) bool {
*l.items = append(*l.items, tx) *l.items = append(*l.items, tx)
} return true
})
heap.Init(l.items) heap.Init(l.items)
} }
@ -443,7 +444,7 @@ func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transact
for len(*l.items) > 0 { for len(*l.items) > 0 {
// Discard stale transactions if found during cleanup // Discard stale transactions if found during cleanup
tx := heap.Pop(l.items).(*types.Transaction) tx := heap.Pop(l.items).(*types.Transaction)
if _, ok := (*l.all)[tx.Hash()]; !ok { if l.all.Get(tx.Hash()) == nil {
l.stales-- l.stales--
continue continue
} }
@ -475,7 +476,7 @@ func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) boo
// Discard stale price points if found at the heap start // Discard stale price points if found at the heap start
for len(*l.items) > 0 { for len(*l.items) > 0 {
head := []*types.Transaction(*l.items)[0] head := []*types.Transaction(*l.items)[0]
if _, ok := (*l.all)[head.Hash()]; !ok { if l.all.Get(head.Hash()) == nil {
l.stales-- l.stales--
heap.Pop(l.items) heap.Pop(l.items)
continue continue
@ -500,7 +501,7 @@ func (l *txPricedList) Discard(count int, local *accountSet) types.Transactions
for len(*l.items) > 0 && count > 0 { for len(*l.items) > 0 && count > 0 {
// Discard stale transactions if found during cleanup // Discard stale transactions if found during cleanup
tx := heap.Pop(l.items).(*types.Transaction) tx := heap.Pop(l.items).(*types.Transaction)
if _, ok := (*l.all)[tx.Hash()]; !ok { if l.all.Get(tx.Hash()) == nil {
l.stales-- l.stales--
continue continue
} }

View file

@ -200,11 +200,11 @@ type TxPool struct {
locals *accountSet // Set of local transaction to exempt from eviction rules locals *accountSet // Set of local transaction to exempt from eviction rules
journal *txJournal // Journal of local transaction to back up to disk journal *txJournal // Journal of local transaction to back up to disk
pending map[common.Address]*txList // All currently processable transactions pending map[common.Address]*txList // All currently processable transactions
queue map[common.Address]*txList // Queued but non-processable transactions queue map[common.Address]*txList // Queued but non-processable transactions
beats map[common.Address]time.Time // Last heartbeat from each known account beats map[common.Address]time.Time // Last heartbeat from each known account
all map[common.Hash]*types.Transaction // All transactions to allow lookups all *txLookup // All transactions to allow lookups
priced *txPricedList // All transactions sorted by price priced *txPricedList // All transactions sorted by price
wg sync.WaitGroup // for shutdown sync wg sync.WaitGroup // for shutdown sync
@ -226,19 +226,19 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
pending: make(map[common.Address]*txList), pending: make(map[common.Address]*txList),
queue: make(map[common.Address]*txList), queue: make(map[common.Address]*txList),
beats: make(map[common.Address]time.Time), beats: make(map[common.Address]time.Time),
all: make(map[common.Hash]*types.Transaction), all: newTxLookup(),
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize), chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
gasPrice: new(big.Int).SetUint64(config.PriceLimit), gasPrice: new(big.Int).SetUint64(config.PriceLimit),
} }
pool.locals = newAccountSet(pool.signer) pool.locals = newAccountSet(pool.signer)
pool.priced = newTxPricedList(&pool.all) pool.priced = newTxPricedList(pool.all)
pool.reset(nil, chain.CurrentBlock().Header()) pool.reset(nil, chain.CurrentBlock().Header())
// If local transactions and journaling is enabled, load from disk // If local transactions and journaling is enabled, load from disk
if !config.NoLocals && config.Journal != "" { if !config.NoLocals && config.Journal != "" {
pool.journal = newTxJournal(config.Journal) pool.journal = newTxJournal(config.Journal)
if err := pool.journal.load(pool.AddLocal); err != nil { if err := pool.journal.load(pool.AddLocals); err != nil {
log.Warn("Failed to load transaction journal", "err", err) log.Warn("Failed to load transaction journal", "err", err)
} }
if err := pool.journal.rotate(pool.local()); err != nil { if err := pool.journal.rotate(pool.local()); err != nil {
@ -444,9 +444,9 @@ func (pool *TxPool) Stop() {
log.Info("Transaction pool stopped") log.Info("Transaction pool stopped")
} }
// SubscribeTxPreEvent registers a subscription of TxPreEvent and // SubscribeNewTxsEvent registers a subscription of NewTxsEvent and
// starts sending event to the given channel. // starts sending event to the given channel.
func (pool *TxPool) SubscribeTxPreEvent(ch chan<- TxPreEvent) event.Subscription { func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- NewTxsEvent) event.Subscription {
return pool.scope.Track(pool.txFeed.Subscribe(ch)) return pool.scope.Track(pool.txFeed.Subscribe(ch))
} }
@ -605,7 +605,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) { func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
// If the transaction is already known, discard it // If the transaction is already known, discard it
hash := tx.Hash() hash := tx.Hash()
if pool.all[hash] != nil { if pool.all.Get(hash) != nil {
log.Trace("Discarding already known transaction", "hash", hash) log.Trace("Discarding already known transaction", "hash", hash)
return false, fmt.Errorf("known transaction: %x", hash) return false, fmt.Errorf("known transaction: %x", hash)
} }
@ -616,7 +616,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
return false, err return false, err
} }
// If the transaction pool is full, discard underpriced transactions // If the transaction pool is full, discard underpriced transactions
if uint64(len(pool.all)) >= pool.config.GlobalSlots+pool.config.GlobalQueue { if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
// If the new transaction is underpriced, don't accept it // If the new transaction is underpriced, don't accept it
if !local && pool.priced.Underpriced(tx, pool.locals) { if !local && pool.priced.Underpriced(tx, pool.locals) {
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
@ -624,7 +624,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
return false, ErrUnderpriced return false, ErrUnderpriced
} }
// New transaction is better than our worse ones, make room for it // New transaction is better than our worse ones, make room for it
drop := pool.priced.Discard(len(pool.all)-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals) drop := pool.priced.Discard(pool.all.Count()-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals)
for _, tx := range drop { for _, tx := range drop {
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice()) log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice())
underpricedTxCounter.Inc(1) underpricedTxCounter.Inc(1)
@ -642,18 +642,18 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
} }
// New transaction is better, replace old one // New transaction is better, replace old one
if old != nil { if old != nil {
delete(pool.all, old.Hash()) pool.all.Remove(old.Hash())
pool.priced.Removed() pool.priced.Removed()
pendingReplaceCounter.Inc(1) pendingReplaceCounter.Inc(1)
} }
pool.all[tx.Hash()] = tx pool.all.Add(tx)
pool.priced.Put(tx) pool.priced.Put(tx)
pool.journalTx(from, tx) pool.journalTx(from, tx)
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
// We've directly injected a replacement transaction, notify subsystems // We've directly injected a replacement transaction, notify subsystems
go pool.txFeed.Send(TxPreEvent{tx}) go pool.txFeed.Send(NewTxsEvent{types.Transactions{tx}})
return old != nil, nil return old != nil, nil
} }
@ -689,12 +689,12 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er
} }
// Discard any previous transaction and mark this // Discard any previous transaction and mark this
if old != nil { if old != nil {
delete(pool.all, old.Hash()) pool.all.Remove(old.Hash())
pool.priced.Removed() pool.priced.Removed()
queuedReplaceCounter.Inc(1) queuedReplaceCounter.Inc(1)
} }
if pool.all[hash] == nil { if pool.all.Get(hash) == nil {
pool.all[hash] = tx pool.all.Add(tx)
pool.priced.Put(tx) pool.priced.Put(tx)
} }
return old != nil, nil return old != nil, nil
@ -712,10 +712,11 @@ func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) {
} }
} }
// promoteTx adds a transaction to the pending (processable) list of transactions. // promoteTx adds a transaction to the pending (processable) list of transactions
// and returns whether it was inserted or an older was better.
// //
// Note, this method assumes the pool lock is held! // Note, this method assumes the pool lock is held!
func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) { func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool {
// Try to insert the transaction into the pending queue // Try to insert the transaction into the pending queue
if pool.pending[addr] == nil { if pool.pending[addr] == nil {
pool.pending[addr] = newTxList(true) pool.pending[addr] = newTxList(true)
@ -725,29 +726,29 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
inserted, old := list.Add(tx, pool.config.PriceBump) inserted, old := list.Add(tx, pool.config.PriceBump)
if !inserted { if !inserted {
// An older transaction was better, discard this // An older transaction was better, discard this
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
pendingDiscardCounter.Inc(1) pendingDiscardCounter.Inc(1)
return return false
} }
// Otherwise discard any previous transaction and mark this // Otherwise discard any previous transaction and mark this
if old != nil { if old != nil {
delete(pool.all, old.Hash()) pool.all.Remove(old.Hash())
pool.priced.Removed() pool.priced.Removed()
pendingReplaceCounter.Inc(1) pendingReplaceCounter.Inc(1)
} }
// Failsafe to work around direct pending inserts (tests) // Failsafe to work around direct pending inserts (tests)
if pool.all[hash] == nil { if pool.all.Get(hash) == nil {
pool.all[hash] = tx pool.all.Add(tx)
pool.priced.Put(tx) pool.priced.Put(tx)
} }
// Set the potentially new pending nonce and notify any subsystems of the new tx // Set the potentially new pending nonce and notify any subsystems of the new tx
pool.beats[addr] = time.Now() pool.beats[addr] = time.Now()
pool.pendingState.SetNonce(addr, tx.Nonce()+1) pool.pendingState.SetNonce(addr, tx.Nonce()+1)
go pool.txFeed.Send(TxPreEvent{tx}) return true
} }
// AddLocal enqueues a single transaction into the pool if it is valid, marking // AddLocal enqueues a single transaction into the pool if it is valid, marking
@ -839,7 +840,7 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
status := make([]TxStatus, len(hashes)) status := make([]TxStatus, len(hashes))
for i, hash := range hashes { for i, hash := range hashes {
if tx := pool.all[hash]; tx != nil { if tx := pool.all.Get(hash); tx != nil {
from, _ := types.Sender(pool.signer, tx) // already validated from, _ := types.Sender(pool.signer, tx) // already validated
if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil { if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil {
status[i] = TxStatusPending status[i] = TxStatusPending
@ -854,24 +855,21 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
// Get returns a transaction if it is contained in the pool // Get returns a transaction if it is contained in the pool
// and nil otherwise. // and nil otherwise.
func (pool *TxPool) Get(hash common.Hash) *types.Transaction { func (pool *TxPool) Get(hash common.Hash) *types.Transaction {
pool.mu.RLock() return pool.all.Get(hash)
defer pool.mu.RUnlock()
return pool.all[hash]
} }
// removeTx removes a single transaction from the queue, moving all subsequent // removeTx removes a single transaction from the queue, moving all subsequent
// transactions back to the future queue. // transactions back to the future queue.
func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
// Fetch the transaction we wish to delete // Fetch the transaction we wish to delete
tx, ok := pool.all[hash] tx := pool.all.Get(hash)
if !ok { if tx == nil {
return return
} }
addr, _ := types.Sender(pool.signer, tx) // already validated during insertion addr, _ := types.Sender(pool.signer, tx) // already validated during insertion
// Remove it from the list of known transactions // Remove it from the list of known transactions
delete(pool.all, hash) pool.all.Remove(hash)
if outofbound { if outofbound {
pool.priced.Removed() pool.priced.Removed()
} }
@ -907,6 +905,9 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
// future queue to the set of pending transactions. During this process, all // future queue to the set of pending transactions. During this process, all
// invalidated transactions (low nonce, low balance) are deleted. // invalidated transactions (low nonce, low balance) are deleted.
func (pool *TxPool) promoteExecutables(accounts []common.Address) { func (pool *TxPool) promoteExecutables(accounts []common.Address) {
// Track the promoted transactions to broadcast them at once
var promoted []*types.Transaction
// Gather all the accounts potentially needing updates // Gather all the accounts potentially needing updates
if accounts == nil { if accounts == nil {
accounts = make([]common.Address, 0, len(pool.queue)) accounts = make([]common.Address, 0, len(pool.queue))
@ -924,7 +925,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
for _, tx := range list.Forward(pool.currentState.GetNonce(addr)) { for _, tx := range list.Forward(pool.currentState.GetNonce(addr)) {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed old queued transaction", "hash", hash) log.Trace("Removed old queued transaction", "hash", hash)
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
} }
// Drop all transactions that are too costly (low balance or out of gas) // Drop all transactions that are too costly (low balance or out of gas)
@ -932,21 +933,23 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
for _, tx := range drops { for _, tx := range drops {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed unpayable queued transaction", "hash", hash) log.Trace("Removed unpayable queued transaction", "hash", hash)
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
queuedNofundsCounter.Inc(1) queuedNofundsCounter.Inc(1)
} }
// Gather all executable transactions and promote them // Gather all executable transactions and promote them
for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) { for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Promoting queued transaction", "hash", hash) if pool.promoteTx(addr, hash, tx) {
pool.promoteTx(addr, hash, tx) log.Trace("Promoting queued transaction", "hash", hash)
promoted = append(promoted, tx)
}
} }
// Drop all transactions over the allowed limit // Drop all transactions over the allowed limit
if !pool.locals.contains(addr) { if !pool.locals.contains(addr) {
for _, tx := range list.Cap(int(pool.config.AccountQueue)) { for _, tx := range list.Cap(int(pool.config.AccountQueue)) {
hash := tx.Hash() hash := tx.Hash()
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
queuedRateLimitCounter.Inc(1) queuedRateLimitCounter.Inc(1)
log.Trace("Removed cap-exceeding queued transaction", "hash", hash) log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
@ -957,6 +960,10 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
delete(pool.queue, addr) delete(pool.queue, addr)
} }
} }
// Notify subsystem for new promoted transactions.
if len(promoted) > 0 {
go pool.txFeed.Send(NewTxsEvent{promoted})
}
// If the pending limit is overflown, start equalizing allowances // If the pending limit is overflown, start equalizing allowances
pending := uint64(0) pending := uint64(0)
for _, list := range pool.pending { for _, list := range pool.pending {
@ -991,7 +998,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
for _, tx := range list.Cap(list.Len() - 1) { for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too // Drop the transaction from the global pools too
hash := tx.Hash() hash := tx.Hash()
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
// Update the account nonce to the dropped transaction // Update the account nonce to the dropped transaction
@ -1013,7 +1020,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
for _, tx := range list.Cap(list.Len() - 1) { for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too // Drop the transaction from the global pools too
hash := tx.Hash() hash := tx.Hash()
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
// Update the account nonce to the dropped transaction // Update the account nonce to the dropped transaction
@ -1082,7 +1089,7 @@ func (pool *TxPool) demoteUnexecutables() {
for _, tx := range list.Forward(nonce) { for _, tx := range list.Forward(nonce) {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed old pending transaction", "hash", hash) log.Trace("Removed old pending transaction", "hash", hash)
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
} }
// Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
@ -1090,7 +1097,7 @@ func (pool *TxPool) demoteUnexecutables() {
for _, tx := range drops { for _, tx := range drops {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed unpayable pending transaction", "hash", hash) log.Trace("Removed unpayable pending transaction", "hash", hash)
delete(pool.all, hash) pool.all.Remove(hash)
pool.priced.Removed() pool.priced.Removed()
pendingNofundsCounter.Inc(1) pendingNofundsCounter.Inc(1)
} }
@ -1162,3 +1169,68 @@ func (as *accountSet) containsTx(tx *types.Transaction) bool {
func (as *accountSet) add(addr common.Address) { func (as *accountSet) add(addr common.Address) {
as.accounts[addr] = struct{}{} as.accounts[addr] = struct{}{}
} }
// txLookup is used internally by TxPool to track transactions while allowing lookup without
// mutex contention.
//
// Note, although this type is properly protected against concurrent access, it
// is **not** a type that should ever be mutated or even exposed outside of the
// transaction pool, since its internal state is tightly coupled with the pools
// internal mechanisms. The sole purpose of the type is to permit out-of-bound
// peeking into the pool in TxPool.Get without having to acquire the widely scoped
// TxPool.mu mutex.
type txLookup struct {
all map[common.Hash]*types.Transaction
lock sync.RWMutex
}
// newTxLookup returns a new txLookup structure.
func newTxLookup() *txLookup {
return &txLookup{
all: make(map[common.Hash]*types.Transaction),
}
}
// Range calls f on each key and value present in the map.
func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction) bool) {
t.lock.RLock()
defer t.lock.RUnlock()
for key, value := range t.all {
if !f(key, value) {
break
}
}
}
// Get returns a transaction if it exists in the lookup, or nil if not found.
func (t *txLookup) Get(hash common.Hash) *types.Transaction {
t.lock.RLock()
defer t.lock.RUnlock()
return t.all[hash]
}
// Count returns the current number of items in the lookup.
func (t *txLookup) Count() int {
t.lock.RLock()
defer t.lock.RUnlock()
return len(t.all)
}
// Add adds a transaction to the lookup.
func (t *txLookup) Add(tx *types.Transaction) {
t.lock.Lock()
defer t.lock.Unlock()
t.all[tx.Hash()] = tx
}
// Remove removes a transaction from the lookup.
func (t *txLookup) Remove(hash common.Hash) {
t.lock.Lock()
defer t.lock.Unlock()
delete(t.all, hash)
}

View file

@ -94,7 +94,7 @@ func validateTxPoolInternals(pool *TxPool) error {
// Ensure the total transaction set is consistent with pending + queued // Ensure the total transaction set is consistent with pending + queued
pending, queued := pool.stats() pending, queued := pool.stats()
if total := len(pool.all); total != pending+queued { if total := pool.all.Count(); total != pending+queued {
return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued) return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued)
} }
if priced := pool.priced.items.Len() - pool.priced.stales; priced != pending+queued { if priced := pool.priced.items.Len() - pool.priced.stales; priced != pending+queued {
@ -118,21 +118,27 @@ func validateTxPoolInternals(pool *TxPool) error {
// validateEvents checks that the correct number of transaction addition events // validateEvents checks that the correct number of transaction addition events
// were fired on the pool's event feed. // were fired on the pool's event feed.
func validateEvents(events chan TxPreEvent, count int) error { func validateEvents(events chan NewTxsEvent, count int) error {
for i := 0; i < count; i++ { var received []*types.Transaction
for len(received) < count {
select { select {
case <-events: case ev := <-events:
received = append(received, ev.Txs...)
case <-time.After(time.Second): case <-time.After(time.Second):
return fmt.Errorf("event #%d not fired", i) return fmt.Errorf("event #%d not fired", received)
} }
} }
if len(received) > count {
return fmt.Errorf("more than %d events fired: %v", count, received[count:])
}
select { select {
case tx := <-events: case ev := <-events:
return fmt.Errorf("more than %d events fired: %v", count, tx.Tx) return fmt.Errorf("more than %d events fired: %v", count, ev.Txs)
case <-time.After(50 * time.Millisecond): case <-time.After(50 * time.Millisecond):
// This branch should be "default", but it's a data race between goroutines, // This branch should be "default", but it's a data race between goroutines,
// reading the event channel and pushng into it, so better wait a bit ensuring // reading the event channel and pushing into it, so better wait a bit ensuring
// really nothing gets injected. // really nothing gets injected.
} }
return nil return nil
@ -395,8 +401,8 @@ func TestTransactionDoubleNonce(t *testing.T) {
t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash())
} }
// Ensure the total transaction count is correct // Ensure the total transaction count is correct
if len(pool.all) != 1 { if pool.all.Count() != 1 {
t.Error("expected 1 total transactions, got", len(pool.all)) t.Error("expected 1 total transactions, got", pool.all.Count())
} }
} }
@ -418,8 +424,8 @@ func TestTransactionMissingNonce(t *testing.T) {
if pool.queue[addr].Len() != 1 { if pool.queue[addr].Len() != 1 {
t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) t.Error("expected 1 queued transaction, got", pool.queue[addr].Len())
} }
if len(pool.all) != 1 { if pool.all.Count() != 1 {
t.Error("expected 1 total transactions, got", len(pool.all)) t.Error("expected 1 total transactions, got", pool.all.Count())
} }
} }
@ -482,8 +488,8 @@ func TestTransactionDropping(t *testing.T) {
if pool.queue[account].Len() != 3 { if pool.queue[account].Len() != 3 {
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
} }
if len(pool.all) != 6 { if pool.all.Count() != 6 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 6) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
} }
pool.lockedReset(nil, nil) pool.lockedReset(nil, nil)
if pool.pending[account].Len() != 3 { if pool.pending[account].Len() != 3 {
@ -492,8 +498,8 @@ func TestTransactionDropping(t *testing.T) {
if pool.queue[account].Len() != 3 { if pool.queue[account].Len() != 3 {
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
} }
if len(pool.all) != 6 { if pool.all.Count() != 6 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 6) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
} }
// Reduce the balance of the account, and check that invalidated transactions are dropped // Reduce the balance of the account, and check that invalidated transactions are dropped
pool.currentState.AddBalance(account, big.NewInt(-650)) pool.currentState.AddBalance(account, big.NewInt(-650))
@ -517,8 +523,8 @@ func TestTransactionDropping(t *testing.T) {
if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok {
t.Errorf("out-of-fund queued transaction present: %v", tx11) t.Errorf("out-of-fund queued transaction present: %v", tx11)
} }
if len(pool.all) != 4 { if pool.all.Count() != 4 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 4) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 4)
} }
// Reduce the block gas limit, check that invalidated transactions are dropped // Reduce the block gas limit, check that invalidated transactions are dropped
pool.chain.(*testBlockChain).gasLimit = 100 pool.chain.(*testBlockChain).gasLimit = 100
@ -536,8 +542,8 @@ func TestTransactionDropping(t *testing.T) {
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok {
t.Errorf("over-gased queued transaction present: %v", tx11) t.Errorf("over-gased queued transaction present: %v", tx11)
} }
if len(pool.all) != 2 { if pool.all.Count() != 2 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 2) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 2)
} }
} }
@ -590,8 +596,8 @@ func TestTransactionPostponing(t *testing.T) {
if len(pool.queue) != 0 { if len(pool.queue) != 0 {
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
} }
if len(pool.all) != len(txs) { if pool.all.Count() != len(txs) {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), len(txs)) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
} }
pool.lockedReset(nil, nil) pool.lockedReset(nil, nil)
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
@ -600,8 +606,8 @@ func TestTransactionPostponing(t *testing.T) {
if len(pool.queue) != 0 { if len(pool.queue) != 0 {
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
} }
if len(pool.all) != len(txs) { if pool.all.Count() != len(txs) {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), len(txs)) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
} }
// Reduce the balance of the account, and check that transactions are reorganised // Reduce the balance of the account, and check that transactions are reorganised
for _, addr := range accs { for _, addr := range accs {
@ -650,8 +656,8 @@ func TestTransactionPostponing(t *testing.T) {
} }
} }
} }
if len(pool.all) != len(txs)/2 { if pool.all.Count() != len(txs)/2 {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), len(txs)/2) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)/2)
} }
} }
@ -669,7 +675,7 @@ func TestTransactionGapFilling(t *testing.T) {
pool.currentState.AddBalance(account, big.NewInt(1000000)) pool.currentState.AddBalance(account, big.NewInt(1000000))
// Keep track of transaction events to ensure all executables get announced // Keep track of transaction events to ensure all executables get announced
events := make(chan TxPreEvent, testTxPoolConfig.AccountQueue+5) events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5)
sub := pool.txFeed.Subscribe(events) sub := pool.txFeed.Subscribe(events)
defer sub.Unsubscribe() defer sub.Unsubscribe()
@ -742,8 +748,8 @@ func TestTransactionQueueAccountLimiting(t *testing.T) {
} }
} }
} }
if len(pool.all) != int(testTxPoolConfig.AccountQueue) { if pool.all.Count() != int(testTxPoolConfig.AccountQueue) {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), testTxPoolConfig.AccountQueue) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue)
} }
} }
@ -920,7 +926,7 @@ func TestTransactionPendingLimiting(t *testing.T) {
pool.currentState.AddBalance(account, big.NewInt(1000000)) pool.currentState.AddBalance(account, big.NewInt(1000000))
// Keep track of transaction events to ensure all executables get announced // Keep track of transaction events to ensure all executables get announced
events := make(chan TxPreEvent, testTxPoolConfig.AccountQueue+5) events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5)
sub := pool.txFeed.Subscribe(events) sub := pool.txFeed.Subscribe(events)
defer sub.Unsubscribe() defer sub.Unsubscribe()
@ -936,8 +942,8 @@ func TestTransactionPendingLimiting(t *testing.T) {
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0) t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0)
} }
} }
if len(pool.all) != int(testTxPoolConfig.AccountQueue+5) { if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) {
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), testTxPoolConfig.AccountQueue+5) t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue+5)
} }
if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil { if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil {
t.Fatalf("event firing failed: %v", err) t.Fatalf("event firing failed: %v", err)
@ -987,8 +993,8 @@ func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
if len(pool1.queue) != len(pool2.queue) { if len(pool1.queue) != len(pool2.queue) {
t.Errorf("queued transaction count mismatch: one-by-one algo: %d, batch algo: %d", len(pool1.queue), len(pool2.queue)) t.Errorf("queued transaction count mismatch: one-by-one algo: %d, batch algo: %d", len(pool1.queue), len(pool2.queue))
} }
if len(pool1.all) != len(pool2.all) { if pool1.all.Count() != pool2.all.Count() {
t.Errorf("total transaction count mismatch: one-by-one algo %d, batch algo %d", len(pool1.all), len(pool2.all)) t.Errorf("total transaction count mismatch: one-by-one algo %d, batch algo %d", pool1.all.Count(), pool2.all.Count())
} }
if err := validateTxPoolInternals(pool1); err != nil { if err := validateTxPoolInternals(pool1); err != nil {
t.Errorf("pool 1 internal state corrupted: %v", err) t.Errorf("pool 1 internal state corrupted: %v", err)
@ -1140,7 +1146,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
defer pool.Stop() defer pool.Stop()
// Keep track of transaction events to ensure all executables get announced // Keep track of transaction events to ensure all executables get announced
events := make(chan TxPreEvent, 32) events := make(chan NewTxsEvent, 32)
sub := pool.txFeed.Subscribe(events) sub := pool.txFeed.Subscribe(events)
defer sub.Unsubscribe() defer sub.Unsubscribe()
@ -1327,7 +1333,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
defer pool.Stop() defer pool.Stop()
// Keep track of transaction events to ensure all executables get announced // Keep track of transaction events to ensure all executables get announced
events := make(chan TxPreEvent, 32) events := make(chan NewTxsEvent, 32)
sub := pool.txFeed.Subscribe(events) sub := pool.txFeed.Subscribe(events)
defer sub.Unsubscribe() defer sub.Unsubscribe()
@ -1433,7 +1439,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
defer pool.Stop() defer pool.Stop()
// Keep track of transaction events to ensure all executables get announced // Keep track of transaction events to ensure all executables get announced
events := make(chan TxPreEvent, 32) events := make(chan NewTxsEvent, 32)
sub := pool.txFeed.Subscribe(events) sub := pool.txFeed.Subscribe(events)
defer sub.Unsubscribe() defer sub.Unsubscribe()
@ -1495,7 +1501,7 @@ func TestTransactionReplacement(t *testing.T) {
defer pool.Stop() defer pool.Stop()
// Keep track of transaction events to ensure all executables get announced // Keep track of transaction events to ensure all executables get announced
events := make(chan TxPreEvent, 32) events := make(chan NewTxsEvent, 32)
sub := pool.txFeed.Subscribe(events) sub := pool.txFeed.Subscribe(events)
defer sub.Unsubscribe() defer sub.Unsubscribe()

View file

@ -12,10 +12,11 @@ import (
var _ = (*receiptMarshaling)(nil) var _ = (*receiptMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (r Receipt) MarshalJSON() ([]byte, error) { func (r Receipt) MarshalJSON() ([]byte, error) {
type Receipt struct { type Receipt struct {
PostState hexutil.Bytes `json:"root"` PostState hexutil.Bytes `json:"root"`
Status hexutil.Uint `json:"status"` Status hexutil.Uint64 `json:"status"`
CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"`
Logs []*Log `json:"logs" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"`
@ -25,7 +26,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
} }
var enc Receipt var enc Receipt
enc.PostState = r.PostState enc.PostState = r.PostState
enc.Status = hexutil.Uint(r.Status) enc.Status = hexutil.Uint64(r.Status)
enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed) enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed)
enc.Bloom = r.Bloom enc.Bloom = r.Bloom
enc.Logs = r.Logs enc.Logs = r.Logs
@ -35,10 +36,11 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON.
func (r *Receipt) UnmarshalJSON(input []byte) error { func (r *Receipt) UnmarshalJSON(input []byte) error {
type Receipt struct { type Receipt struct {
PostState *hexutil.Bytes `json:"root"` PostState *hexutil.Bytes `json:"root"`
Status *hexutil.Uint `json:"status"` Status *hexutil.Uint64 `json:"status"`
CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
Bloom *Bloom `json:"logsBloom" gencodec:"required"` Bloom *Bloom `json:"logsBloom" gencodec:"required"`
Logs []*Log `json:"logs" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"`
@ -54,7 +56,7 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
r.PostState = *dec.PostState r.PostState = *dec.PostState
} }
if dec.Status != nil { if dec.Status != nil {
r.Status = uint(*dec.Status) r.Status = uint64(*dec.Status)
} }
if dec.CumulativeGasUsed == nil { if dec.CumulativeGasUsed == nil {
return errors.New("missing required field 'cumulativeGasUsed' for Receipt") return errors.New("missing required field 'cumulativeGasUsed' for Receipt")

View file

@ -36,17 +36,17 @@ var (
const ( const (
// ReceiptStatusFailed is the status code of a transaction if execution failed. // ReceiptStatusFailed is the status code of a transaction if execution failed.
ReceiptStatusFailed = uint(0) ReceiptStatusFailed = uint64(0)
// ReceiptStatusSuccessful is the status code of a transaction if execution succeeded. // ReceiptStatusSuccessful is the status code of a transaction if execution succeeded.
ReceiptStatusSuccessful = uint(1) ReceiptStatusSuccessful = uint64(1)
) )
// Receipt represents the results of a transaction. // Receipt represents the results of a transaction.
type Receipt struct { type Receipt struct {
// Consensus fields // Consensus fields
PostState []byte `json:"root"` PostState []byte `json:"root"`
Status uint `json:"status"` Status uint64 `json:"status"`
CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"` CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"`
Logs []*Log `json:"logs" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"`
@ -59,7 +59,7 @@ type Receipt struct {
type receiptMarshaling struct { type receiptMarshaling struct {
PostState hexutil.Bytes PostState hexutil.Bytes
Status hexutil.Uint Status hexutil.Uint64
CumulativeGasUsed hexutil.Uint64 CumulativeGasUsed hexutil.Uint64
GasUsed hexutil.Uint64 GasUsed hexutil.Uint64
} }

View file

@ -165,28 +165,13 @@ func TestTransactionPriceNonceSort(t *testing.T) {
t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce()) t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce())
} }
} }
// Find the previous and next nonce of this account
prev, next := i-1, i+1 // If the next tx has different from account, the price must be lower than the current one
for j := i - 1; j >= 0; j-- { if i+1 < len(txs) {
if fromj, _ := Sender(signer, txs[j]); fromi == fromj { next := txs[i+1]
prev = j fromNext, _ := Sender(signer, next)
break if fromi != fromNext && txi.GasPrice().Cmp(next.GasPrice()) < 0 {
} t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
}
for j := i + 1; j < len(txs); j++ {
if fromj, _ := Sender(signer, txs[j]); fromi == fromj {
next = j
break
}
}
// Make sure that in between the neighbor nonces, the transaction is correctly positioned price wise
for j := prev + 1; j < next; j++ {
fromj, _ := Sender(signer, txs[j])
if j < i && txs[j].GasPrice().Cmp(txi.GasPrice()) < 0 {
t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", j, fromj[:4], txs[j].GasPrice(), i, fromi[:4], txi.GasPrice())
}
if j > i && txs[j].GasPrice().Cmp(txi.GasPrice()) > 0 {
t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) > tx #%d (A=%x P=%v)", j, fromj[:4], txs[j].GasPrice(), i, fromi[:4], txi.GasPrice())
} }
} }
} }

View file

@ -124,12 +124,12 @@ func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, m
// 1. From a zero-value address to a non-zero value (NEW VALUE) // 1. From a zero-value address to a non-zero value (NEW VALUE)
// 2. From a non-zero value address to a zero-value address (DELETE) // 2. From a non-zero value address to a zero-value address (DELETE)
// 3. From a non-zero to a non-zero (CHANGE) // 3. From a non-zero to a non-zero (CHANGE)
if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) { if val == (common.Hash{}) && y.Sign() != 0 {
// 0 => non 0 // 0 => non 0
return params.SstoreSetGas, nil return params.SstoreSetGas, nil
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) { } else if val != (common.Hash{}) && y.Sign() == 0 {
// non 0 => 0
evm.StateDB.AddRefund(params.SstoreRefundGas) evm.StateDB.AddRefund(params.SstoreRefundGas)
return params.SstoreClearGas, nil return params.SstoreClearGas, nil
} else { } else {
// non 0 => non 0 (or 0 => 0) // non 0 => non 0 (or 0 => 0)

View file

@ -850,7 +850,7 @@ func makePush(size uint64, pushByteSize int) executionFunc {
} }
} }
// make push instruction function // make dup instruction function
func makeDup(size int64) executionFunc { func makeDup(size int64) executionFunc {
return func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { return func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.dup(evm.interpreter.intPool, int(size)) stack.dup(evm.interpreter.intPool, int(size))

View file

@ -33,7 +33,7 @@ type (
var errGasUintOverflow = errors.New("gas uint64 overflow") var errGasUintOverflow = errors.New("gas uint64 overflow")
type operation struct { type operation struct {
// op is the operation function // execute is the operation function
execute executionFunc execute executionFunc
// gasCost is the gas function and returns the gas required for execution // gasCost is the gas function and returns the gas required for execution
gasCost gasFunc gasCost gasFunc

View file

@ -188,8 +188,8 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions,
return b.eth.TxPool().Content() return b.eth.TxPool().Content()
} }
func (b *EthAPIBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return b.eth.TxPool().SubscribeTxPreEvent(ch) return b.eth.TxPool().SubscribeNewTxsEvent(ch)
} }
func (b *EthAPIBackend) Downloader() *downloader.Downloader { func (b *EthAPIBackend) Downloader() *downloader.Downloader {

View file

@ -215,14 +215,14 @@ func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chai
return clique.New(chainConfig.Clique, db) return clique.New(chainConfig.Clique, db)
} }
// Otherwise assume proof-of-work // Otherwise assume proof-of-work
switch { switch config.PowMode {
case config.PowMode == ethash.ModeFake: case ethash.ModeFake:
log.Warn("Ethash used in fake mode") log.Warn("Ethash used in fake mode")
return ethash.NewFaker() return ethash.NewFaker()
case config.PowMode == ethash.ModeTest: case ethash.ModeTest:
log.Warn("Ethash used in test mode") log.Warn("Ethash used in test mode")
return ethash.NewTester() return ethash.NewTester()
case config.PowMode == ethash.ModeShared: case ethash.ModeShared:
log.Warn("Ethash used in shared mode") log.Warn("Ethash used in shared mode")
return ethash.NewShared() return ethash.NewShared()
default: default:
@ -239,7 +239,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chai
} }
} }
// APIs returns the collection of RPC services the ethereum package offers. // APIs return the collection of RPC services the ethereum package offers.
// NOTE, some of these services probably need to be moved to somewhere else. // NOTE, some of these services probably need to be moved to somewhere else.
func (s *Ethereum) APIs() []rpc.API { func (s *Ethereum) APIs() []rpc.API {
apis := ethapi.GetAPIs(s.APIBackend) apis := ethapi.GetAPIs(s.APIBackend)

View file

@ -680,7 +680,7 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
} }
} }
// If the head fetch already found an ancestor, return // If the head fetch already found an ancestor, return
if !common.EmptyHash(hash) { if hash != (common.Hash{}) {
if int64(number) <= floor { if int64(number) <= floor {
p.log.Warn("Ancestor below allowance", "number", number, "hash", hash, "allowance", floor) p.log.Warn("Ancestor below allowance", "number", number, "hash", hash, "allowance", floor)
return 0, errInvalidAncestor return 0, errInvalidAncestor

View file

@ -214,7 +214,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
type stateSync struct { type stateSync struct {
d *Downloader // Downloader instance to access and manage current peerset d *Downloader // Downloader instance to access and manage current peerset
sched *trie.TrieSync // State trie sync scheduler defining the tasks sched *trie.Sync // State trie sync scheduler defining the tasks
keccak hash.Hash // Keccak256 hasher to verify deliveries with keccak hash.Hash // Keccak256 hasher to verify deliveries with
tasks map[common.Hash]*stateTask // Set of tasks currently queued for retrieval tasks map[common.Hash]*stateTask // Set of tasks currently queued for retrieval

View file

@ -292,20 +292,20 @@ func (f *Fetcher) loop() {
height := f.chainHeight() height := f.chainHeight()
for !f.queue.Empty() { for !f.queue.Empty() {
op := f.queue.PopItem().(*inject) op := f.queue.PopItem().(*inject)
hash := op.block.Hash()
if f.queueChangeHook != nil { if f.queueChangeHook != nil {
f.queueChangeHook(op.block.Hash(), false) f.queueChangeHook(hash, false)
} }
// If too high up the chain or phase, continue later // If too high up the chain or phase, continue later
number := op.block.NumberU64() number := op.block.NumberU64()
if number > height+1 { if number > height+1 {
f.queue.Push(op, -float32(op.block.NumberU64())) f.queue.Push(op, -float32(number))
if f.queueChangeHook != nil { if f.queueChangeHook != nil {
f.queueChangeHook(op.block.Hash(), true) f.queueChangeHook(hash, true)
} }
break break
} }
// Otherwise if fresh and still unknown, try and import // Otherwise if fresh and still unknown, try and import
hash := op.block.Hash()
if number+maxUncleDist < height || f.getBlock(hash) != nil { if number+maxUncleDist < height || f.getBlock(hash) != nil {
f.forgetBlock(hash) f.forgetBlock(hash)
continue continue

View file

@ -104,8 +104,8 @@ func (api *PublicFilterAPI) timeoutLoop() {
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter
func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
var ( var (
pendingTxs = make(chan common.Hash) pendingTxs = make(chan []common.Hash)
pendingTxSub = api.events.SubscribePendingTxEvents(pendingTxs) pendingTxSub = api.events.SubscribePendingTxs(pendingTxs)
) )
api.filtersMu.Lock() api.filtersMu.Lock()
@ -118,7 +118,7 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
case ph := <-pendingTxs: case ph := <-pendingTxs:
api.filtersMu.Lock() api.filtersMu.Lock()
if f, found := api.filters[pendingTxSub.ID]; found { if f, found := api.filters[pendingTxSub.ID]; found {
f.hashes = append(f.hashes, ph) f.hashes = append(f.hashes, ph...)
} }
api.filtersMu.Unlock() api.filtersMu.Unlock()
case <-pendingTxSub.Err(): case <-pendingTxSub.Err():
@ -144,13 +144,17 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su
rpcSub := notifier.CreateSubscription() rpcSub := notifier.CreateSubscription()
go func() { go func() {
txHashes := make(chan common.Hash) txHashes := make(chan []common.Hash, 128)
pendingTxSub := api.events.SubscribePendingTxEvents(txHashes) pendingTxSub := api.events.SubscribePendingTxs(txHashes)
for { for {
select { select {
case h := <-txHashes: case hashes := <-txHashes:
notifier.Notify(rpcSub.ID, h) // To keep the original behaviour, send a single tx hash in one notification.
// TODO(rjl493456442) Send a batch of tx hashes in one notification
for _, h := range hashes {
notifier.Notify(rpcSub.ID, h)
}
case <-rpcSub.Err(): case <-rpcSub.Err():
pendingTxSub.Unsubscribe() pendingTxSub.Unsubscribe()
return return

View file

@ -36,7 +36,7 @@ type Backend interface {
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error)
SubscribeTxPreEvent(chan<- core.TxPreEvent) event.Subscription SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription

View file

@ -59,7 +59,7 @@ const (
const ( const (
// txChanSize is the size of channel listening to TxPreEvent. // txChanSize is the size of channel listening to NewTxsEvent.
// The number is referenced from the size of tx pool. // The number is referenced from the size of tx pool.
txChanSize = 4096 txChanSize = 4096
// rmLogsChanSize is the size of channel listening to RemovedLogsEvent. // rmLogsChanSize is the size of channel listening to RemovedLogsEvent.
@ -80,7 +80,7 @@ type subscription struct {
created time.Time created time.Time
logsCrit ethereum.FilterQuery logsCrit ethereum.FilterQuery
logs chan []*types.Log logs chan []*types.Log
hashes chan common.Hash hashes chan []common.Hash
headers chan *types.Header headers chan *types.Header
installed chan struct{} // closed when the filter is installed installed chan struct{} // closed when the filter is installed
err chan error // closed when the filter is uninstalled err chan error // closed when the filter is uninstalled
@ -95,7 +95,7 @@ type EventSystem struct {
lastHead *types.Header lastHead *types.Header
// Subscriptions // Subscriptions
txSub event.Subscription // Subscription for new transaction event txsSub event.Subscription // Subscription for new transaction event
logsSub event.Subscription // Subscription for new log event logsSub event.Subscription // Subscription for new log event
rmLogsSub event.Subscription // Subscription for removed log event rmLogsSub event.Subscription // Subscription for removed log event
chainSub event.Subscription // Subscription for new chain event chainSub event.Subscription // Subscription for new chain event
@ -104,7 +104,7 @@ type EventSystem struct {
// Channels // Channels
install chan *subscription // install filter for event notification install chan *subscription // install filter for event notification
uninstall chan *subscription // remove filter for event notification uninstall chan *subscription // remove filter for event notification
txCh chan core.TxPreEvent // Channel to receive new transaction event txsCh chan core.NewTxsEvent // Channel to receive new transactions event
logsCh chan []*types.Log // Channel to receive new log event logsCh chan []*types.Log // Channel to receive new log event
rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event
chainCh chan core.ChainEvent // Channel to receive new chain event chainCh chan core.ChainEvent // Channel to receive new chain event
@ -123,14 +123,14 @@ func NewEventSystem(mux *event.TypeMux, backend Backend, lightMode bool) *EventS
lightMode: lightMode, lightMode: lightMode,
install: make(chan *subscription), install: make(chan *subscription),
uninstall: make(chan *subscription), uninstall: make(chan *subscription),
txCh: make(chan core.TxPreEvent, txChanSize), txsCh: make(chan core.NewTxsEvent, txChanSize),
logsCh: make(chan []*types.Log, logsChanSize), logsCh: make(chan []*types.Log, logsChanSize),
rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize), rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize),
chainCh: make(chan core.ChainEvent, chainEvChanSize), chainCh: make(chan core.ChainEvent, chainEvChanSize),
} }
// Subscribe events // Subscribe events
m.txSub = m.backend.SubscribeTxPreEvent(m.txCh) m.txsSub = m.backend.SubscribeNewTxsEvent(m.txsCh)
m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh) m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh)
m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh) m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh)
m.chainSub = m.backend.SubscribeChainEvent(m.chainCh) m.chainSub = m.backend.SubscribeChainEvent(m.chainCh)
@ -138,7 +138,7 @@ func NewEventSystem(mux *event.TypeMux, backend Backend, lightMode bool) *EventS
m.pendingLogSub = m.mux.Subscribe(core.PendingLogsEvent{}) m.pendingLogSub = m.mux.Subscribe(core.PendingLogsEvent{})
// Make sure none of the subscriptions are empty // Make sure none of the subscriptions are empty
if m.txSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil || if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil ||
m.pendingLogSub.Closed() { m.pendingLogSub.Closed() {
log.Crit("Subscribe for event system failed") log.Crit("Subscribe for event system failed")
} }
@ -240,7 +240,7 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit ethereum.FilterQuery, logs
logsCrit: crit, logsCrit: crit,
created: time.Now(), created: time.Now(),
logs: logs, logs: logs,
hashes: make(chan common.Hash), hashes: make(chan []common.Hash),
headers: make(chan *types.Header), headers: make(chan *types.Header),
installed: make(chan struct{}), installed: make(chan struct{}),
err: make(chan error), err: make(chan error),
@ -257,7 +257,7 @@ func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*typ
logsCrit: crit, logsCrit: crit,
created: time.Now(), created: time.Now(),
logs: logs, logs: logs,
hashes: make(chan common.Hash), hashes: make(chan []common.Hash),
headers: make(chan *types.Header), headers: make(chan *types.Header),
installed: make(chan struct{}), installed: make(chan struct{}),
err: make(chan error), err: make(chan error),
@ -274,7 +274,7 @@ func (es *EventSystem) subscribePendingLogs(crit ethereum.FilterQuery, logs chan
logsCrit: crit, logsCrit: crit,
created: time.Now(), created: time.Now(),
logs: logs, logs: logs,
hashes: make(chan common.Hash), hashes: make(chan []common.Hash),
headers: make(chan *types.Header), headers: make(chan *types.Header),
installed: make(chan struct{}), installed: make(chan struct{}),
err: make(chan error), err: make(chan error),
@ -290,7 +290,7 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti
typ: BlocksSubscription, typ: BlocksSubscription,
created: time.Now(), created: time.Now(),
logs: make(chan []*types.Log), logs: make(chan []*types.Log),
hashes: make(chan common.Hash), hashes: make(chan []common.Hash),
headers: headers, headers: headers,
installed: make(chan struct{}), installed: make(chan struct{}),
err: make(chan error), err: make(chan error),
@ -298,9 +298,9 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti
return es.subscribe(sub) return es.subscribe(sub)
} }
// SubscribePendingTxEvents creates a subscription that writes transaction hashes for // SubscribePendingTxs creates a subscription that writes transaction hashes for
// transactions that enter the transaction pool. // transactions that enter the transaction pool.
func (es *EventSystem) SubscribePendingTxEvents(hashes chan common.Hash) *Subscription { func (es *EventSystem) SubscribePendingTxs(hashes chan []common.Hash) *Subscription {
sub := &subscription{ sub := &subscription{
id: rpc.NewID(), id: rpc.NewID(),
typ: PendingTransactionsSubscription, typ: PendingTransactionsSubscription,
@ -348,9 +348,13 @@ func (es *EventSystem) broadcast(filters filterIndex, ev interface{}) {
} }
} }
} }
case core.TxPreEvent: case core.NewTxsEvent:
hashes := make([]common.Hash, 0, len(e.Txs))
for _, tx := range e.Txs {
hashes = append(hashes, tx.Hash())
}
for _, f := range filters[PendingTransactionsSubscription] { for _, f := range filters[PendingTransactionsSubscription] {
f.hashes <- e.Tx.Hash() f.hashes <- hashes
} }
case core.ChainEvent: case core.ChainEvent:
for _, f := range filters[BlocksSubscription] { for _, f := range filters[BlocksSubscription] {
@ -446,7 +450,7 @@ func (es *EventSystem) eventLoop() {
// Ensure all subscriptions get cleaned up // Ensure all subscriptions get cleaned up
defer func() { defer func() {
es.pendingLogSub.Unsubscribe() es.pendingLogSub.Unsubscribe()
es.txSub.Unsubscribe() es.txsSub.Unsubscribe()
es.logsSub.Unsubscribe() es.logsSub.Unsubscribe()
es.rmLogsSub.Unsubscribe() es.rmLogsSub.Unsubscribe()
es.chainSub.Unsubscribe() es.chainSub.Unsubscribe()
@ -460,7 +464,7 @@ func (es *EventSystem) eventLoop() {
for { for {
select { select {
// Handle subscribed events // Handle subscribed events
case ev := <-es.txCh: case ev := <-es.txsCh:
es.broadcast(index, ev) es.broadcast(index, ev)
case ev := <-es.logsCh: case ev := <-es.logsCh:
es.broadcast(index, ev) es.broadcast(index, ev)
@ -495,7 +499,7 @@ func (es *EventSystem) eventLoop() {
close(f.err) close(f.err)
// System stopped // System stopped
case <-es.txSub.Err(): case <-es.txsSub.Err():
return return
case <-es.logsSub.Err(): case <-es.logsSub.Err():
return return

View file

@ -96,7 +96,7 @@ func (b *testBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types
return logs, nil return logs, nil
} }
func (b *testBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { func (b *testBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return b.txFeed.Subscribe(ch) return b.txFeed.Subscribe(ch)
} }
@ -232,10 +232,7 @@ func TestPendingTxFilter(t *testing.T) {
fid0 := api.NewPendingTransactionFilter() fid0 := api.NewPendingTransactionFilter()
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
for _, tx := range transactions { txFeed.Send(core.NewTxsEvent{Txs: transactions})
ev := core.TxPreEvent{Tx: tx}
txFeed.Send(ev)
}
timeout := time.Now().Add(1 * time.Second) timeout := time.Now().Add(1 * time.Second)
for { for {

View file

@ -46,7 +46,7 @@ const (
softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data. softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data.
estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header
// txChanSize is the size of channel listening to TxPreEvent. // txChanSize is the size of channel listening to NewTxsEvent.
// The number is referenced from the size of tx pool. // The number is referenced from the size of tx pool.
txChanSize = 4096 txChanSize = 4096
) )
@ -81,8 +81,8 @@ type ProtocolManager struct {
SubProtocols []p2p.Protocol SubProtocols []p2p.Protocol
eventMux *event.TypeMux eventMux *event.TypeMux
txCh chan core.TxPreEvent txsCh chan core.NewTxsEvent
txSub event.Subscription txsSub event.Subscription
minedBlockSub *event.TypeMuxSubscription minedBlockSub *event.TypeMuxSubscription
// channels for fetcher, syncer, txsyncLoop // channels for fetcher, syncer, txsyncLoop
@ -204,8 +204,8 @@ func (pm *ProtocolManager) Start(maxPeers int) {
pm.maxPeers = maxPeers pm.maxPeers = maxPeers
// broadcast transactions // broadcast transactions
pm.txCh = make(chan core.TxPreEvent, txChanSize) pm.txsCh = make(chan core.NewTxsEvent, txChanSize)
pm.txSub = pm.txpool.SubscribeTxPreEvent(pm.txCh) pm.txsSub = pm.txpool.SubscribeNewTxsEvent(pm.txsCh)
go pm.txBroadcastLoop() go pm.txBroadcastLoop()
// broadcast mined blocks // broadcast mined blocks
@ -220,7 +220,7 @@ func (pm *ProtocolManager) Start(maxPeers int) {
func (pm *ProtocolManager) Stop() { func (pm *ProtocolManager) Stop() {
log.Info("Stopping Ethereum protocol") log.Info("Stopping Ethereum protocol")
pm.txSub.Unsubscribe() // quits txBroadcastLoop pm.txsSub.Unsubscribe() // quits txBroadcastLoop
pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop
// Quit the sync loop. // Quit the sync loop.
@ -698,7 +698,7 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) {
// Send the block to a subset of our peers // Send the block to a subset of our peers
transfer := peers[:int(math.Sqrt(float64(len(peers))))] transfer := peers[:int(math.Sqrt(float64(len(peers))))]
for _, peer := range transfer { for _, peer := range transfer {
peer.SendNewBlock(block, td) peer.AsyncSendNewBlock(block, td)
} }
log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
return return
@ -706,22 +706,29 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) {
// Otherwise if the block is indeed in out own chain, announce it // Otherwise if the block is indeed in out own chain, announce it
if pm.blockchain.HasBlock(hash, block.NumberU64()) { if pm.blockchain.HasBlock(hash, block.NumberU64()) {
for _, peer := range peers { for _, peer := range peers {
peer.SendNewBlockHashes([]common.Hash{hash}, []uint64{block.NumberU64()}) peer.AsyncSendNewBlockHash(block)
} }
log.Trace("Announced block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) log.Trace("Announced block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
} }
} }
// BroadcastTx will propagate a transaction to all peers which are not known to // BroadcastTxs will propagate a batch of transactions to all peers which are not known to
// already have the given transaction. // already have the given transaction.
func (pm *ProtocolManager) BroadcastTx(hash common.Hash, tx *types.Transaction) { func (pm *ProtocolManager) BroadcastTxs(txs types.Transactions) {
// Broadcast transaction to a batch of peers not knowing about it var txset = make(map[*peer]types.Transactions)
peers := pm.peers.PeersWithoutTx(hash)
//FIXME include this again: peers = peers[:int(math.Sqrt(float64(len(peers))))] // Broadcast transactions to a batch of peers not knowing about it
for _, peer := range peers { for _, tx := range txs {
peer.SendTransactions(types.Transactions{tx}) peers := pm.peers.PeersWithoutTx(tx.Hash())
for _, peer := range peers {
txset[peer] = append(txset[peer], tx)
}
log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(peers))
}
// FIXME include this again: peers = peers[:int(math.Sqrt(float64(len(peers))))]
for peer, txs := range txset {
peer.AsyncSendTransactions(txs)
} }
log.Trace("Broadcast transaction", "hash", hash, "recipients", len(peers))
} }
// Mined broadcast loop // Mined broadcast loop
@ -739,11 +746,11 @@ func (pm *ProtocolManager) minedBroadcastLoop() {
func (pm *ProtocolManager) txBroadcastLoop() { func (pm *ProtocolManager) txBroadcastLoop() {
for { for {
select { select {
case event := <-pm.txCh: case event := <-pm.txsCh:
pm.BroadcastTx(event.Tx.Hash(), event.Tx) pm.BroadcastTxs(event.Txs)
// Err() channel will be closed when unsubscribing. // Err() channel will be closed when unsubscribing.
case <-pm.txSub.Err(): case <-pm.txsSub.Err():
return return
} }
} }

View file

@ -124,7 +124,7 @@ func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) {
return batches, nil return batches, nil
} }
func (p *testTxPool) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return p.txFeed.Subscribe(ch) return p.txFeed.Subscribe(ch)
} }

View file

@ -37,8 +37,24 @@ var (
) )
const ( const (
maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS) maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS) maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS)
// maxQueuedTxs is the maximum number of transaction lists to queue up before
// dropping broadcasts. This is a sensitive number as a transaction list might
// contain a single transaction, or thousands.
maxQueuedTxs = 128
// maxQueuedProps is the maximum number of block propagations to queue up before
// dropping broadcasts. There's not much point in queueing stale blocks, so a few
// that might cover uncles should be enough.
maxQueuedProps = 4
// maxQueuedAnns is the maximum number of block announcements to queue up before
// dropping broadcasts. Similarly to block propagations, there's no point to queue
// above some healthy uncle limit, so use that.
maxQueuedAnns = 4
handshakeTimeout = 5 * time.Second handshakeTimeout = 5 * time.Second
) )
@ -50,6 +66,12 @@ type PeerInfo struct {
Head string `json:"head"` // SHA3 hash of the peer's best owned block Head string `json:"head"` // SHA3 hash of the peer's best owned block
} }
// propEvent is a block propagation, waiting for its turn in the broadcast queue.
type propEvent struct {
block *types.Block
td *big.Int
}
type peer struct { type peer struct {
id string id string
@ -63,23 +85,64 @@ type peer struct {
td *big.Int td *big.Int
lock sync.RWMutex lock sync.RWMutex
knownTxs *set.Set // Set of transaction hashes known to be known by this peer knownTxs *set.Set // Set of transaction hashes known to be known by this peer
knownBlocks *set.Set // Set of block hashes known to be known by this peer knownBlocks *set.Set // Set of block hashes known to be known by this peer
queuedTxs chan []*types.Transaction // Queue of transactions to broadcast to the peer
queuedProps chan *propEvent // Queue of blocks to broadcast to the peer
queuedAnns chan *types.Block // Queue of blocks to announce to the peer
term chan struct{} // Termination channel to stop the broadcaster
} }
func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
id := p.ID()
return &peer{ return &peer{
Peer: p, Peer: p,
rw: rw, rw: rw,
version: version, version: version,
id: fmt.Sprintf("%x", id[:8]), id: fmt.Sprintf("%x", p.ID().Bytes()[:8]),
knownTxs: set.New(), knownTxs: set.New(),
knownBlocks: set.New(), knownBlocks: set.New(),
queuedTxs: make(chan []*types.Transaction, maxQueuedTxs),
queuedProps: make(chan *propEvent, maxQueuedProps),
queuedAnns: make(chan *types.Block, maxQueuedAnns),
term: make(chan struct{}),
} }
} }
// broadcast is a write loop that multiplexes block propagations, announcements
// and transaction broadcasts into the remote peer. The goal is to have an async
// writer that does not lock up node internals.
func (p *peer) broadcast() {
for {
select {
case txs := <-p.queuedTxs:
if err := p.SendTransactions(txs); err != nil {
return
}
p.Log().Trace("Broadcast transactions", "count", len(txs))
case prop := <-p.queuedProps:
if err := p.SendNewBlock(prop.block, prop.td); err != nil {
return
}
p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td)
case block := <-p.queuedAnns:
if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil {
return
}
p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash())
case <-p.term:
return
}
}
}
// close signals the broadcast goroutine to terminate.
func (p *peer) close() {
close(p.term)
}
// Info gathers and returns a collection of metadata known about a peer. // Info gathers and returns a collection of metadata known about a peer.
func (p *peer) Info() *PeerInfo { func (p *peer) Info() *PeerInfo {
hash, td := p.Head() hash, td := p.Head()
@ -139,6 +202,19 @@ func (p *peer) SendTransactions(txs types.Transactions) error {
return p2p.Send(p.rw, TxMsg, txs) return p2p.Send(p.rw, TxMsg, txs)
} }
// AsyncSendTransactions queues list of transactions propagation to a remote
// peer. If the peer's broadcast queue is full, the event is silently dropped.
func (p *peer) AsyncSendTransactions(txs []*types.Transaction) {
select {
case p.queuedTxs <- txs:
for _, tx := range txs {
p.knownTxs.Add(tx.Hash())
}
default:
p.Log().Debug("Dropping transaction propagation", "count", len(txs))
}
}
// SendNewBlockHashes announces the availability of a number of blocks through // SendNewBlockHashes announces the availability of a number of blocks through
// a hash notification. // a hash notification.
func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error { func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
@ -153,12 +229,35 @@ func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error
return p2p.Send(p.rw, NewBlockHashesMsg, request) return p2p.Send(p.rw, NewBlockHashesMsg, request)
} }
// AsyncSendNewBlockHash queues the availability of a block for propagation to a
// remote peer. If the peer's broadcast queue is full, the event is silently
// dropped.
func (p *peer) AsyncSendNewBlockHash(block *types.Block) {
select {
case p.queuedAnns <- block:
p.knownBlocks.Add(block.Hash())
default:
p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash())
}
}
// SendNewBlock propagates an entire block to a remote peer. // SendNewBlock propagates an entire block to a remote peer.
func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error { func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error {
p.knownBlocks.Add(block.Hash()) p.knownBlocks.Add(block.Hash())
return p2p.Send(p.rw, NewBlockMsg, []interface{}{block, td}) return p2p.Send(p.rw, NewBlockMsg, []interface{}{block, td})
} }
// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If
// the peer's broadcast queue is full, the event is silently dropped.
func (p *peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
select {
case p.queuedProps <- &propEvent{block: block, td: td}:
p.knownBlocks.Add(block.Hash())
default:
p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash())
}
}
// SendBlockHeaders sends a batch of block headers to the remote peer. // SendBlockHeaders sends a batch of block headers to the remote peer.
func (p *peer) SendBlockHeaders(headers []*types.Header) error { func (p *peer) SendBlockHeaders(headers []*types.Header) error {
return p2p.Send(p.rw, BlockHeadersMsg, headers) return p2p.Send(p.rw, BlockHeadersMsg, headers)
@ -313,7 +412,8 @@ func newPeerSet() *peerSet {
} }
// Register injects a new peer into the working set, or returns an error if the // Register injects a new peer into the working set, or returns an error if the
// peer is already known. // peer is already known. If a new peer it registered, its broadcast loop is also
// started.
func (ps *peerSet) Register(p *peer) error { func (ps *peerSet) Register(p *peer) error {
ps.lock.Lock() ps.lock.Lock()
defer ps.lock.Unlock() defer ps.lock.Unlock()
@ -325,6 +425,8 @@ func (ps *peerSet) Register(p *peer) error {
return errAlreadyRegistered return errAlreadyRegistered
} }
ps.peers[p.id] = p ps.peers[p.id] = p
go p.broadcast()
return nil return nil
} }
@ -334,10 +436,13 @@ func (ps *peerSet) Unregister(id string) error {
ps.lock.Lock() ps.lock.Lock()
defer ps.lock.Unlock() defer ps.lock.Unlock()
if _, ok := ps.peers[id]; !ok { p, ok := ps.peers[id]
if !ok {
return errNotRegistered return errNotRegistered
} }
delete(ps.peers, id) delete(ps.peers, id)
p.close()
return nil return nil
} }

View file

@ -103,9 +103,9 @@ type txPool interface {
// The slice should be modifiable by the caller. // The slice should be modifiable by the caller.
Pending() (map[common.Address]types.Transactions, error) Pending() (map[common.Address]types.Transactions, error)
// SubscribeTxPreEvent should return an event subscription of // SubscribeNewTxsEvent should return an event subscription of
// TxPreEvent and send events to the given channel. // NewTxsEvent and send events to the given channel.
SubscribeTxPreEvent(chan<- core.TxPreEvent) event.Subscription SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
} }
// statusData is the network packet for the status message. // statusData is the network packet for the status message.

View file

@ -116,7 +116,7 @@ func testRecvTransactions(t *testing.T, protocol int) {
t.Errorf("added wrong tx hash: got %v, want %v", added[0].Hash(), tx.Hash()) t.Errorf("added wrong tx hash: got %v, want %v", added[0].Hash(), tx.Hash())
} }
case <-time.After(2 * time.Second): case <-time.After(2 * time.Second):
t.Errorf("no TxPreEvent received within 2 seconds") t.Errorf("no NewTxsEvent received within 2 seconds")
} }
} }

View file

@ -207,6 +207,7 @@ func (db *LDBDatabase) meter(refresh time.Duration) {
delaystats [2]int64 delaystats [2]int64
lastWriteDelay time.Time lastWriteDelay time.Time
lastWriteDelayN time.Time lastWriteDelayN time.Time
lastWritePaused time.Time
) )
// Iterate ad infinitum and collect the stats // Iterate ad infinitum and collect the stats
@ -267,8 +268,9 @@ func (db *LDBDatabase) meter(refresh time.Duration) {
delayN int64 delayN int64
delayDuration string delayDuration string
duration time.Duration duration time.Duration
paused bool
) )
if n, err := fmt.Sscanf(writedelay, "DelayN:%d Delay:%s", &delayN, &delayDuration); n != 2 || err != nil { if n, err := fmt.Sscanf(writedelay, "DelayN:%d Delay:%s Paused:%t", &delayN, &delayDuration, &paused); n != 3 || err != nil {
db.log.Error("Write delay statistic not found") db.log.Error("Write delay statistic not found")
return return
} }
@ -301,6 +303,14 @@ func (db *LDBDatabase) meter(refresh time.Duration) {
lastWriteDelay = time.Now() lastWriteDelay = time.Now()
} }
} }
// If a warning that db is performing compaction has been displayed, any subsequent
// warnings will be withheld for one minute not to overwhelm the user.
if paused && delayN-delaystats[0] == 0 && duration.Nanoseconds()-delaystats[1] == 0 &&
time.Now().After(lastWritePaused.Add(writeDelayWarningThrottler)) {
db.log.Warn("Database compacting, degraded performance")
lastWritePaused = time.Now()
}
delaystats[0], delaystats[1] = delayN, duration.Nanoseconds() delaystats[0], delaystats[1] = delayN, duration.Nanoseconds()
// Retrieve the database iostats. // Retrieve the database iostats.

View file

@ -49,7 +49,7 @@ const (
// history request. // history request.
historyUpdateRange = 50 historyUpdateRange = 50
// txChanSize is the size of channel listening to TxPreEvent. // txChanSize is the size of channel listening to NewTxsEvent.
// The number is referenced from the size of tx pool. // The number is referenced from the size of tx pool.
txChanSize = 4096 txChanSize = 4096
// chainHeadChanSize is the size of channel listening to ChainHeadEvent. // chainHeadChanSize is the size of channel listening to ChainHeadEvent.
@ -57,9 +57,9 @@ const (
) )
type txPool interface { type txPool interface {
// SubscribeTxPreEvent should return an event subscription of // SubscribeNewTxsEvent should return an event subscription of
// TxPreEvent and send events to the given channel. // NewTxsEvent and send events to the given channel.
SubscribeTxPreEvent(chan<- core.TxPreEvent) event.Subscription SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
} }
type blockChain interface { type blockChain interface {
@ -150,8 +150,8 @@ func (s *Service) loop() {
headSub := blockchain.SubscribeChainHeadEvent(chainHeadCh) headSub := blockchain.SubscribeChainHeadEvent(chainHeadCh)
defer headSub.Unsubscribe() defer headSub.Unsubscribe()
txEventCh := make(chan core.TxPreEvent, txChanSize) txEventCh := make(chan core.NewTxsEvent, txChanSize)
txSub := txpool.SubscribeTxPreEvent(txEventCh) txSub := txpool.SubscribeNewTxsEvent(txEventCh)
defer txSub.Unsubscribe() defer txSub.Unsubscribe()
// Start a goroutine that exhausts the subsciptions to avoid events piling up // Start a goroutine that exhausts the subsciptions to avoid events piling up

View file

@ -144,7 +144,7 @@ type FilterQuery struct {
// {} or nil matches any topic list // {} or nil matches any topic list
// {{A}} matches topic A in first position // {{A}} matches topic A in first position
// {{}, {B}} matches any topic in first position, B in second position // {{}, {B}} matches any topic in first position, B in second position
// {{A}}, {B}} matches topic A in first position, B in second position // {{A}, {B}} matches topic A in first position, B in second position
// {{A, B}}, {C, D}} matches topic (A OR B) in first position, (C OR D) in second position // {{A, B}}, {C, D}} matches topic (A OR B) in first position, (C OR D) in second position
Topics [][]common.Hash Topics [][]common.Hash
} }

View file

@ -65,7 +65,7 @@ type Backend interface {
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)
Stats() (pending int, queued int) Stats() (pending int, queued int)
TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions)
SubscribeTxPreEvent(chan<- core.TxPreEvent) event.Subscription SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
ChainConfig() *params.ChainConfig ChainConfig() *params.ChainConfig
CurrentBlock() *types.Block CurrentBlock() *types.Block

View file

@ -136,8 +136,8 @@ func (b *LesApiBackend) TxPoolContent() (map[common.Address]types.Transactions,
return b.eth.txPool.Content() return b.eth.txPool.Content()
} }
func (b *LesApiBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { func (b *LesApiBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return b.eth.txPool.SubscribeTxPreEvent(ch) return b.eth.txPool.SubscribeNewTxsEvent(ch)
} }
func (b *LesApiBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { func (b *LesApiBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {

View file

@ -230,7 +230,7 @@ func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error {
} }
nodeSet := proofs[0].NodeSet() nodeSet := proofs[0].NodeSet()
// Verify the proof and store if checks out // Verify the proof and store if checks out
if _, err, _ := trie.VerifyProof(r.Id.Root, r.Key, nodeSet); err != nil { if _, _, err := trie.VerifyProof(r.Id.Root, r.Key, nodeSet); err != nil {
return fmt.Errorf("merkle proof verification failed: %v", err) return fmt.Errorf("merkle proof verification failed: %v", err)
} }
r.Proof = nodeSet r.Proof = nodeSet
@ -241,7 +241,7 @@ func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error {
// Verify the proof and store if checks out // Verify the proof and store if checks out
nodeSet := proofs.NodeSet() nodeSet := proofs.NodeSet()
reads := &readTraceDB{db: nodeSet} reads := &readTraceDB{db: nodeSet}
if _, err, _ := trie.VerifyProof(r.Id.Root, r.Key, reads); err != nil { if _, _, err := trie.VerifyProof(r.Id.Root, r.Key, reads); err != nil {
return fmt.Errorf("merkle proof verification failed: %v", err) return fmt.Errorf("merkle proof verification failed: %v", err)
} }
// check if all nodes have been read by VerifyProof // check if all nodes have been read by VerifyProof
@ -400,7 +400,7 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
var encNumber [8]byte var encNumber [8]byte
binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
value, err, _ := trie.VerifyProof(r.ChtRoot, encNumber[:], light.NodeList(proof.Proof).NodeSet()) value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], light.NodeList(proof.Proof).NodeSet())
if err != nil { if err != nil {
return err return err
} }
@ -435,7 +435,7 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
reads := &readTraceDB{db: nodeSet} reads := &readTraceDB{db: nodeSet}
value, err, _ := trie.VerifyProof(r.ChtRoot, encNumber[:], reads) value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads)
if err != nil { if err != nil {
return fmt.Errorf("merkle proof verification failed: %v", err) return fmt.Errorf("merkle proof verification failed: %v", err)
} }
@ -529,7 +529,7 @@ func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error {
for i, idx := range r.SectionIdxList { for i, idx := range r.SectionIdxList {
binary.BigEndian.PutUint64(encNumber[2:], idx) binary.BigEndian.PutUint64(encNumber[2:], idx)
value, err, _ := trie.VerifyProof(r.BloomTrieRoot, encNumber[:], reads) value, _, err := trie.VerifyProof(r.BloomTrieRoot, encNumber[:], reads)
if err != nil { if err != nil {
return err return err
} }

View file

@ -321,9 +321,9 @@ func (pool *TxPool) Stop() {
log.Info("Transaction pool stopped") log.Info("Transaction pool stopped")
} }
// SubscribeTxPreEvent registers a subscription of core.TxPreEvent and // SubscribeNewTxsEvent registers a subscription of core.NewTxsEvent and
// starts sending event to the given channel. // starts sending event to the given channel.
func (pool *TxPool) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return pool.scope.Track(pool.txFeed.Subscribe(ch)) return pool.scope.Track(pool.txFeed.Subscribe(ch))
} }
@ -412,7 +412,7 @@ func (self *TxPool) add(ctx context.Context, tx *types.Transaction) error {
// Notify the subscribers. This event is posted in a goroutine // Notify the subscribers. This event is posted in a goroutine
// because it's possible that somewhere during the post "Remove transaction" // because it's possible that somewhere during the post "Remove transaction"
// gets called which will then wait for the global tx pool lock and deadlock. // gets called which will then wait for the global tx pool lock and deadlock.
go self.txFeed.Send(core.TxPreEvent{Tx: tx}) go self.txFeed.Send(core.NewTxsEvent{Txs: types.Transactions{tx}})
} }
// Print a log message if low enough level is set // Print a log message if low enough level is set

View file

@ -45,7 +45,7 @@ srvlog.SetHandler(log.MultiHandler(
log.StreamHandler(os.Stderr, log.LogfmtFormat()), log.StreamHandler(os.Stderr, log.LogfmtFormat()),
log.LvlFilterHandler( log.LvlFilterHandler(
log.LvlError, log.LvlError,
log.Must.FileHandler("errors.json", log.JsonFormat())))) log.Must.FileHandler("errors.json", log.JSONFormat()))))
``` ```
Will result in output that looks like this: Will result in output that looks like this:

View file

@ -86,7 +86,7 @@ from the rpc package in logfmt to standard out. The other prints records at Erro
or above in JSON formatted output to the file /var/log/service.json or above in JSON formatted output to the file /var/log/service.json
handler := log.MultiHandler( handler := log.MultiHandler(
log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JsonFormat())), log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JSONFormat())),
log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler()) log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler())
) )
@ -304,8 +304,8 @@ For all Handler functions which can return an error, there is a version of that
function which will return no error but panics on failure. They are all available function which will return no error but panics on failure. They are all available
on the Must object. For example: on the Must object. For example:
log.Must.FileHandler("/path", log.JsonFormat) log.Must.FileHandler("/path", log.JSONFormat)
log.Must.NetHandler("tcp", ":1234", log.JsonFormat) log.Must.NetHandler("tcp", ":1234", log.JSONFormat)
Inspiration and Credit Inspiration and Credit

View file

@ -196,16 +196,16 @@ func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) {
buf.WriteByte('\n') buf.WriteByte('\n')
} }
// JsonFormat formats log records as JSON objects separated by newlines. // JSONFormat formats log records as JSON objects separated by newlines.
// It is the equivalent of JsonFormatEx(false, true). // It is the equivalent of JSONFormatEx(false, true).
func JsonFormat() Format { func JSONFormat() Format {
return JsonFormatEx(false, true) return JSONFormatEx(false, true)
} }
// JsonFormatEx formats log records as JSON objects. If pretty is true, // JSONFormatEx formats log records as JSON objects. If pretty is true,
// records will be pretty-printed. If lineSeparated is true, records // records will be pretty-printed. If lineSeparated is true, records
// will be logged with a new line between each record. // will be logged with a new line between each record.
func JsonFormatEx(pretty, lineSeparated bool) Format { func JSONFormatEx(pretty, lineSeparated bool) Format {
jsonMarshal := json.Marshal jsonMarshal := json.Marshal
if pretty { if pretty {
jsonMarshal = func(v interface{}) ([]byte, error) { jsonMarshal = func(v interface{}) ([]byte, error) {
@ -225,7 +225,7 @@ func JsonFormatEx(pretty, lineSeparated bool) Format {
if !ok { if !ok {
props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i])
} }
props[k] = formatJsonValue(r.Ctx[i+1]) props[k] = formatJSONValue(r.Ctx[i+1])
} }
b, err := jsonMarshal(props) b, err := jsonMarshal(props)
@ -270,7 +270,7 @@ func formatShared(value interface{}) (result interface{}) {
} }
} }
func formatJsonValue(value interface{}) interface{} { func formatJSONValue(value interface{}) interface{} {
value = formatShared(value) value = formatShared(value)
switch value.(type) { switch value.(type) {
case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:

View file

@ -11,8 +11,8 @@ import (
"github.com/go-stack/stack" "github.com/go-stack/stack"
) )
// Handler defines where and how log records are written.
// A Logger prints its log records by writing to a Handler. // A Logger prints its log records by writing to a Handler.
// The Handler interface defines where and how log records are written.
// Handlers are composable, providing you great flexibility in combining // Handlers are composable, providing you great flexibility in combining
// them to achieve the logging structure that suits your applications. // them to achieve the logging structure that suits your applications.
type Handler interface { type Handler interface {
@ -193,7 +193,7 @@ func LvlFilterHandler(maxLvl Lvl, h Handler) Handler {
}, h) }, h)
} }
// A MultiHandler dispatches any write to each of its handlers. // MultiHandler dispatches any write to each of its handlers.
// This is useful for writing different types of log information // This is useful for writing different types of log information
// to different locations. For example, to log to a file and // to different locations. For example, to log to a file and
// standard error: // standard error:
@ -212,7 +212,7 @@ func MultiHandler(hs ...Handler) Handler {
}) })
} }
// A FailoverHandler writes all log records to the first handler // FailoverHandler writes all log records to the first handler
// specified, but will failover and write to the second handler if // specified, but will failover and write to the second handler if
// the first handler has failed, and so on for all handlers specified. // the first handler has failed, and so on for all handlers specified.
// For example you might want to log to a network socket, but failover // For example you might want to log to a network socket, but failover
@ -220,7 +220,7 @@ func MultiHandler(hs ...Handler) Handler {
// standard out if the file write fails: // standard out if the file write fails:
// //
// log.FailoverHandler( // log.FailoverHandler(
// log.Must.NetHandler("tcp", ":9090", log.JsonFormat()), // log.Must.NetHandler("tcp", ":9090", log.JSONFormat()),
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), // log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
// log.StdoutHandler) // log.StdoutHandler)
// //
@ -336,7 +336,7 @@ func DiscardHandler() Handler {
}) })
} }
// The Must object provides the following Handler creation functions // Must provides the following Handler creation functions
// which instead of returning an error parameter only return a Handler // which instead of returning an error parameter only return a Handler
// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler // and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler
var Must muster var Must muster

View file

@ -24,7 +24,7 @@ const (
LvlTrace LvlTrace
) )
// Aligned returns a 5-character string containing the name of a Lvl. // AlignedString returns a 5-character string containing the name of a Lvl.
func (l Lvl) AlignedString() string { func (l Lvl) AlignedString() string {
switch l { switch l {
case LvlTrace: case LvlTrace:
@ -64,7 +64,7 @@ func (l Lvl) String() string {
} }
} }
// Returns the appropriate Lvl from a string name. // LvlFromString returns the appropriate Lvl from a string name.
// Useful for parsing command line args and configuration files. // Useful for parsing command line args and configuration files.
func LvlFromString(lvlString string) (Lvl, error) { func LvlFromString(lvlString string) (Lvl, error) {
switch lvlString { switch lvlString {
@ -95,6 +95,7 @@ type Record struct {
KeyNames RecordKeyNames KeyNames RecordKeyNames
} }
// RecordKeyNames gets stored in a Record when the write function is executed.
type RecordKeyNames struct { type RecordKeyNames struct {
Time string Time string
Msg string Msg string

View file

@ -42,7 +42,7 @@ const (
resultQueueSize = 10 resultQueueSize = 10
miningLogAtDepth = 5 miningLogAtDepth = 5
// txChanSize is the size of channel listening to TxPreEvent. // txChanSize is the size of channel listening to NewTxsEvent.
// The number is referenced from the size of tx pool. // The number is referenced from the size of tx pool.
txChanSize = 4096 txChanSize = 4096
// chainHeadChanSize is the size of channel listening to ChainHeadEvent. // chainHeadChanSize is the size of channel listening to ChainHeadEvent.
@ -71,6 +71,7 @@ type Work struct {
family *set.Set // family set (used for checking uncle invalidity) family *set.Set // family set (used for checking uncle invalidity)
uncles *set.Set // uncle set uncles *set.Set // uncle set
tcount int // tx count in cycle tcount int // tx count in cycle
gasPool *core.GasPool // available gas used to pack transactions
Block *types.Block // the new block Block *types.Block // the new block
@ -95,8 +96,8 @@ type worker struct {
// update loop // update loop
mux *event.TypeMux mux *event.TypeMux
txCh chan core.TxPreEvent txsCh chan core.NewTxsEvent
txSub event.Subscription txsSub event.Subscription
chainHeadCh chan core.ChainHeadEvent chainHeadCh chan core.ChainHeadEvent
chainHeadSub event.Subscription chainHeadSub event.Subscription
chainSideCh chan core.ChainSideEvent chainSideCh chan core.ChainSideEvent
@ -137,7 +138,7 @@ func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase com
engine: engine, engine: engine,
eth: eth, eth: eth,
mux: mux, mux: mux,
txCh: make(chan core.TxPreEvent, txChanSize), txsCh: make(chan core.NewTxsEvent, txChanSize),
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
chainDb: eth.ChainDb(), chainDb: eth.ChainDb(),
@ -149,8 +150,8 @@ func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase com
agents: make(map[Agent]struct{}), agents: make(map[Agent]struct{}),
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth), unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
} }
// Subscribe TxPreEvent for tx pool // Subscribe NewTxsEvent for tx pool
worker.txSub = eth.TxPool().SubscribeTxPreEvent(worker.txCh) worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
// Subscribe events for blockchain // Subscribe events for blockchain
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh) worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
@ -241,7 +242,7 @@ func (self *worker) unregister(agent Agent) {
} }
func (self *worker) update() { func (self *worker) update() {
defer self.txSub.Unsubscribe() defer self.txsSub.Unsubscribe()
defer self.chainHeadSub.Unsubscribe() defer self.chainHeadSub.Unsubscribe()
defer self.chainSideSub.Unsubscribe() defer self.chainSideSub.Unsubscribe()
@ -258,15 +259,21 @@ func (self *worker) update() {
self.possibleUncles[ev.Block.Hash()] = ev.Block self.possibleUncles[ev.Block.Hash()] = ev.Block
self.uncleMu.Unlock() self.uncleMu.Unlock()
// Handle TxPreEvent // Handle NewTxsEvent
case ev := <-self.txCh: case ev := <-self.txsCh:
// Apply transaction to the pending state if we're not mining // Apply transactions to the pending state if we're not mining.
//
// Note all transactions received may not be continuous with transactions
// already included in the current mining block. These transactions will
// be automatically eliminated.
if atomic.LoadInt32(&self.mining) == 0 { if atomic.LoadInt32(&self.mining) == 0 {
self.currentMu.Lock() self.currentMu.Lock()
acc, _ := types.Sender(self.current.signer, ev.Tx) txs := make(map[common.Address]types.Transactions)
txs := map[common.Address]types.Transactions{acc: {ev.Tx}} for _, tx := range ev.Txs {
acc, _ := types.Sender(self.current.signer, tx)
txs[acc] = append(txs[acc], tx)
}
txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs) txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs)
self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase) self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)
self.updateSnapshot() self.updateSnapshot()
self.currentMu.Unlock() self.currentMu.Unlock()
@ -278,7 +285,7 @@ func (self *worker) update() {
} }
// System stopped // System stopped
case <-self.txSub.Err(): case <-self.txsSub.Err():
return return
case <-self.chainHeadSub.Err(): case <-self.chainHeadSub.Err():
return return
@ -522,14 +529,16 @@ func (self *worker) updateSnapshot() {
} }
func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) { func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) {
gp := new(core.GasPool).AddGas(env.header.GasLimit) if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
}
var coalescedLogs []*types.Log var coalescedLogs []*types.Log
for { for {
// If we don't have enough gas for any further transactions then we're done // If we don't have enough gas for any further transactions then we're done
if gp.Gas() < params.TxGas { if env.gasPool.Gas() < params.TxGas {
log.Trace("Not enough gas for further transactions", "gp", gp) log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
break break
} }
// Retrieve the next transaction and abort if all done // Retrieve the next transaction and abort if all done
@ -553,7 +562,7 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB
// Start executing the transaction // Start executing the transaction
env.state.Prepare(tx.Hash(), common.Hash{}, env.tcount) env.state.Prepare(tx.Hash(), common.Hash{}, env.tcount)
err, logs := env.commitTransaction(tx, bc, coinbase, gp) err, logs := env.commitTransaction(tx, bc, coinbase, env.gasPool)
switch err { switch err {
case core.ErrGasLimitReached: case core.ErrGasLimitReached:
// Pop the current out-of-gas transaction without shifting in the next from the account // Pop the current out-of-gas transaction without shifting in the next from the account

View file

@ -217,7 +217,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str
return true, nil return true, nil
} }
// StopRPC terminates an already running websocket RPC API endpoint. // StopWS terminates an already running websocket RPC API endpoint.
func (api *PrivateAdminAPI) StopWS() (bool, error) { func (api *PrivateAdminAPI) StopWS() (bool, error) {
api.node.lock.Lock() api.node.lock.Lock()
defer api.node.lock.Unlock() defer api.node.lock.Unlock()

View file

@ -59,7 +59,7 @@ using the same data directory will store this information in different subdirect
the data directory. the data directory.
LevelDB databases are also stored within the instance subdirectory. If multiple node LevelDB databases are also stored within the instance subdirectory. If multiple node
instances use the same data directory, openening the databases with identical names will instances use the same data directory, opening the databases with identical names will
create one database for each instance. create one database for each instance.
The account key store is shared among all node instances using the same data directory The account key store is shared among all node instances using the same data directory
@ -84,7 +84,7 @@ directory. Mode instance A opens the database "db", node instance B opens the da
static-nodes.json -- devp2p static node list of instance B static-nodes.json -- devp2p static node list of instance B
db/ -- LevelDB content for "db" db/ -- LevelDB content for "db"
db-2/ -- LevelDB content for "db-2" db-2/ -- LevelDB content for "db-2"
B.ipc -- JSON-RPC UNIX domain socket endpoint of instance A B.ipc -- JSON-RPC UNIX domain socket endpoint of instance B
keystore/ -- account key store, used by both instances keystore/ -- account key store, used by both instances
*/ */
package node package node

View file

@ -507,8 +507,8 @@ func TestAPIGather(t *testing.T) {
} }
// Register a batch of services with some configured APIs // Register a batch of services with some configured APIs
calls := make(chan string, 1) calls := make(chan string, 1)
makeAPI := func(result string) *OneMethodApi { makeAPI := func(result string) *OneMethodAPI {
return &OneMethodApi{fun: func() { calls <- result }} return &OneMethodAPI{fun: func() { calls <- result }}
} }
services := map[string]struct { services := map[string]struct {
APIs []rpc.API APIs []rpc.API

View file

@ -121,12 +121,12 @@ func InstrumentedServiceMakerC(base ServiceConstructor) ServiceConstructor {
return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceC{})) return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceC{}))
} }
// OneMethodApi is a single-method API handler to be returned by test services. // OneMethodAPI is a single-method API handler to be returned by test services.
type OneMethodApi struct { type OneMethodAPI struct {
fun func() fun func()
} }
func (api *OneMethodApi) TheOneMethod() { func (api *OneMethodAPI) TheOneMethod() {
if api.fun != nil { if api.fun != nil {
api.fun() api.fun()
} }

8
p2p/discv5/metrics.go Normal file
View file

@ -0,0 +1,8 @@
package discv5
import "github.com/ethereum/go-ethereum/metrics"
var (
ingressTrafficMeter = metrics.NewRegisteredMeter("discv5/InboundTraffic", nil)
egressTrafficMeter = metrics.NewRegisteredMeter("discv5/OutboundTraffic", nil)
)

View file

@ -334,8 +334,10 @@ func (t *udp) sendPacket(toid NodeID, toaddr *net.UDPAddr, ptype byte, req inter
return hash, err return hash, err
} }
log.Trace(fmt.Sprintf(">>> %v to %x@%v", nodeEvent(ptype), toid[:8], toaddr)) log.Trace(fmt.Sprintf(">>> %v to %x@%v", nodeEvent(ptype), toid[:8], toaddr))
if _, err = t.conn.WriteToUDP(packet, toaddr); err != nil { if nbytes, err := t.conn.WriteToUDP(packet, toaddr); err != nil {
log.Trace(fmt.Sprint("UDP send failed:", err)) log.Trace(fmt.Sprint("UDP send failed:", err))
} else {
egressTrafficMeter.Mark(int64(nbytes))
} }
//fmt.Println(err) //fmt.Println(err)
return hash, err return hash, err
@ -374,6 +376,7 @@ func (t *udp) readLoop() {
buf := make([]byte, 1280) buf := make([]byte, 1280)
for { for {
nbytes, from, err := t.conn.ReadFromUDP(buf) nbytes, from, err := t.conn.ReadFromUDP(buf)
ingressTrafficMeter.Mark(int64(nbytes))
if netutil.IsTemporaryError(err) { if netutil.IsTemporaryError(err) {
// Ignore temporary read errors. // Ignore temporary read errors.
log.Debug(fmt.Sprintf("Temporary read error: %v", err)) log.Debug(fmt.Sprintf("Temporary read error: %v", err))

View file

@ -21,10 +21,10 @@ import (
) )
const ( const (
VersionMajor = 1 // Major version component of the current release VersionMajor = 1 // Major version component of the current release
VersionMinor = 8 // Minor version component of the current release VersionMinor = 8 // Minor version component of the current release
VersionPatch = 9 // Patch version component of the current release VersionPatch = 10 // Patch version component of the current release
VersionMeta = "unstable" // Version metadata to append to the version string VersionMeta = "stable" // Version metadata to append to the version string
) )
// Version holds the textual version string. // Version holds the textual version string.

View file

@ -83,7 +83,7 @@ func hexToKeybytes(hex []byte) []byte {
if len(hex)&1 != 0 { if len(hex)&1 != 0 {
panic("can't convert hex key of odd length") panic("can't convert hex key of odd length")
} }
key := make([]byte, (len(hex)+1)/2) key := make([]byte, len(hex)/2)
decodeNibbles(hex, key) decodeNibbles(hex, key)
return key return key
} }

View file

@ -196,12 +196,12 @@ func (h *hasher) store(n node, db *Database, force bool) (node, error) {
if h.onleaf != nil { if h.onleaf != nil {
switch n := n.(type) { switch n := n.(type) {
case *shortNode: case *shortNode:
if child, ok := n.Val.(valueNode); ok { if child, ok := n.Val.(valueNode); ok && child != nil {
h.onleaf(child, hash) h.onleaf(child, hash)
} }
case *fullNode: case *fullNode:
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
if child, ok := n.Children[i].(valueNode); ok { if child, ok := n.Children[i].(valueNode); ok && child != nil {
h.onleaf(child, hash) h.onleaf(child, hash)
} }
} }

View file

@ -22,6 +22,7 @@ import (
"errors" "errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
) )
// Iterator is a key-value trie iterator that traverses a Trie. // Iterator is a key-value trie iterator that traverses a Trie.
@ -55,31 +56,50 @@ func (it *Iterator) Next() bool {
return false return false
} }
// Prove generates the Merkle proof for the leaf node the iterator is currently
// positioned on.
func (it *Iterator) Prove() [][]byte {
return it.nodeIt.LeafProof()
}
// NodeIterator is an iterator to traverse the trie pre-order. // NodeIterator is an iterator to traverse the trie pre-order.
type NodeIterator interface { type NodeIterator interface {
// Next moves the iterator to the next node. If the parameter is false, any child // Next moves the iterator to the next node. If the parameter is false, any child
// nodes will be skipped. // nodes will be skipped.
Next(bool) bool Next(bool) bool
// Error returns the error status of the iterator. // Error returns the error status of the iterator.
Error() error Error() error
// Hash returns the hash of the current node. // Hash returns the hash of the current node.
Hash() common.Hash Hash() common.Hash
// Parent returns the hash of the parent of the current node. The hash may be the one // Parent returns the hash of the parent of the current node. The hash may be the one
// grandparent if the immediate parent is an internal node with no hash. // grandparent if the immediate parent is an internal node with no hash.
Parent() common.Hash Parent() common.Hash
// Path returns the hex-encoded path to the current node. // Path returns the hex-encoded path to the current node.
// Callers must not retain references to the return value after calling Next. // Callers must not retain references to the return value after calling Next.
// For leaf nodes, the last element of the path is the 'terminator symbol' 0x10. // For leaf nodes, the last element of the path is the 'terminator symbol' 0x10.
Path() []byte Path() []byte
// Leaf returns true iff the current node is a leaf node. // Leaf returns true iff the current node is a leaf node.
// LeafBlob, LeafKey return the contents and key of the leaf node. These
// method panic if the iterator is not positioned at a leaf.
// Callers must not retain references to their return value after calling Next
Leaf() bool Leaf() bool
LeafBlob() []byte
// LeafKey returns the key of the leaf. The method panics if the iterator is not
// positioned at a leaf. Callers must not retain references to the value after
// calling Next.
LeafKey() []byte LeafKey() []byte
// LeafBlob returns the content of the leaf. The method panics if the iterator
// is not positioned at a leaf. Callers must not retain references to the value
// after calling Next.
LeafBlob() []byte
// LeafProof returns the Merkle proof of the leaf. The method panics if the
// iterator is not positioned at a leaf. Callers must not retain references
// to the value after calling Next.
LeafProof() [][]byte
} }
// nodeIteratorState represents the iteration state at one particular node of the // nodeIteratorState represents the iteration state at one particular node of the
@ -99,8 +119,8 @@ type nodeIterator struct {
err error // Failure set in case of an internal error in the iterator err error // Failure set in case of an internal error in the iterator
} }
// iteratorEnd is stored in nodeIterator.err when iteration is done. // errIteratorEnd is stored in nodeIterator.err when iteration is done.
var iteratorEnd = errors.New("end of iteration") var errIteratorEnd = errors.New("end of iteration")
// seekError is stored in nodeIterator.err if the initial seek has failed. // seekError is stored in nodeIterator.err if the initial seek has failed.
type seekError struct { type seekError struct {
@ -139,6 +159,15 @@ func (it *nodeIterator) Leaf() bool {
return hasTerm(it.path) return hasTerm(it.path)
} }
func (it *nodeIterator) LeafKey() []byte {
if len(it.stack) > 0 {
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
return hexToKeybytes(it.path)
}
}
panic("not at leaf")
}
func (it *nodeIterator) LeafBlob() []byte { func (it *nodeIterator) LeafBlob() []byte {
if len(it.stack) > 0 { if len(it.stack) > 0 {
if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
@ -148,10 +177,22 @@ func (it *nodeIterator) LeafBlob() []byte {
panic("not at leaf") panic("not at leaf")
} }
func (it *nodeIterator) LeafKey() []byte { func (it *nodeIterator) LeafProof() [][]byte {
if len(it.stack) > 0 { if len(it.stack) > 0 {
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
return hexToKeybytes(it.path) hasher := newHasher(0, 0, nil)
proofs := make([][]byte, 0, len(it.stack))
for i, item := range it.stack[:len(it.stack)-1] {
// Gather nodes that end up as hash nodes (or the root)
node, _, _ := hasher.hashChildren(item.node, nil)
hashed, _ := hasher.store(node, nil, false)
if _, ok := hashed.(hashNode); ok || i == 0 {
enc, _ := rlp.EncodeToBytes(node)
proofs = append(proofs, enc)
}
}
return proofs
} }
} }
panic("not at leaf") panic("not at leaf")
@ -162,7 +203,7 @@ func (it *nodeIterator) Path() []byte {
} }
func (it *nodeIterator) Error() error { func (it *nodeIterator) Error() error {
if it.err == iteratorEnd { if it.err == errIteratorEnd {
return nil return nil
} }
if seek, ok := it.err.(seekError); ok { if seek, ok := it.err.(seekError); ok {
@ -176,7 +217,7 @@ func (it *nodeIterator) Error() error {
// sets the Error field to the encountered failure. If `descend` is false, // sets the Error field to the encountered failure. If `descend` is false,
// skips iterating over any subnodes of the current node. // skips iterating over any subnodes of the current node.
func (it *nodeIterator) Next(descend bool) bool { func (it *nodeIterator) Next(descend bool) bool {
if it.err == iteratorEnd { if it.err == errIteratorEnd {
return false return false
} }
if seek, ok := it.err.(seekError); ok { if seek, ok := it.err.(seekError); ok {
@ -201,8 +242,8 @@ func (it *nodeIterator) seek(prefix []byte) error {
// Move forward until we're just before the closest match to key. // Move forward until we're just before the closest match to key.
for { for {
state, parentIndex, path, err := it.peek(bytes.HasPrefix(key, it.path)) state, parentIndex, path, err := it.peek(bytes.HasPrefix(key, it.path))
if err == iteratorEnd { if err == errIteratorEnd {
return iteratorEnd return errIteratorEnd
} else if err != nil { } else if err != nil {
return seekError{prefix, err} return seekError{prefix, err}
} else if bytes.Compare(path, key) >= 0 { } else if bytes.Compare(path, key) >= 0 {
@ -246,7 +287,7 @@ func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, er
// No more child nodes, move back up. // No more child nodes, move back up.
it.pop() it.pop()
} }
return nil, nil, nil, iteratorEnd return nil, nil, nil, errIteratorEnd
} }
func (st *nodeIteratorState) resolve(tr *Trie, path []byte) error { func (st *nodeIteratorState) resolve(tr *Trie, path []byte) error {
@ -361,12 +402,16 @@ func (it *differenceIterator) Leaf() bool {
return it.b.Leaf() return it.b.Leaf()
} }
func (it *differenceIterator) LeafKey() []byte {
return it.b.LeafKey()
}
func (it *differenceIterator) LeafBlob() []byte { func (it *differenceIterator) LeafBlob() []byte {
return it.b.LeafBlob() return it.b.LeafBlob()
} }
func (it *differenceIterator) LeafKey() []byte { func (it *differenceIterator) LeafProof() [][]byte {
return it.b.LeafKey() return it.b.LeafProof()
} }
func (it *differenceIterator) Path() []byte { func (it *differenceIterator) Path() []byte {
@ -464,12 +509,16 @@ func (it *unionIterator) Leaf() bool {
return (*it.items)[0].Leaf() return (*it.items)[0].Leaf()
} }
func (it *unionIterator) LeafKey() []byte {
return (*it.items)[0].LeafKey()
}
func (it *unionIterator) LeafBlob() []byte { func (it *unionIterator) LeafBlob() []byte {
return (*it.items)[0].LeafBlob() return (*it.items)[0].LeafBlob()
} }
func (it *unionIterator) LeafKey() []byte { func (it *unionIterator) LeafProof() [][]byte {
return (*it.items)[0].LeafKey() return (*it.items)[0].LeafProof()
} }
func (it *unionIterator) Path() []byte { func (it *unionIterator) Path() []byte {
@ -509,12 +558,10 @@ func (it *unionIterator) Next(descend bool) bool {
heap.Push(it.items, skipped) heap.Push(it.items, skipped)
} }
} }
if least.Next(descend) { if least.Next(descend) {
it.count++ it.count++
heap.Push(it.items, least) heap.Push(it.items, least)
} }
return len(*it.items) > 0 return len(*it.items) > 0
} }

View file

@ -102,28 +102,28 @@ func (t *SecureTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.Putter) err
// VerifyProof checks merkle proofs. The given proof must contain the value for // VerifyProof checks merkle proofs. The given proof must contain the value for
// key in a trie with the given root hash. VerifyProof returns an error if the // key in a trie with the given root hash. VerifyProof returns an error if the
// proof contains invalid trie nodes or the wrong value. // proof contains invalid trie nodes or the wrong value.
func VerifyProof(rootHash common.Hash, key []byte, proofDb DatabaseReader) (value []byte, err error, nodes int) { func VerifyProof(rootHash common.Hash, key []byte, proofDb DatabaseReader) (value []byte, nodes int, err error) {
key = keybytesToHex(key) key = keybytesToHex(key)
wantHash := rootHash wantHash := rootHash
for i := 0; ; i++ { for i := 0; ; i++ {
buf, _ := proofDb.Get(wantHash[:]) buf, _ := proofDb.Get(wantHash[:])
if buf == nil { if buf == nil {
return nil, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash), i return nil, i, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash)
} }
n, err := decodeNode(wantHash[:], buf, 0) n, err := decodeNode(wantHash[:], buf, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("bad proof node %d: %v", i, err), i return nil, i, fmt.Errorf("bad proof node %d: %v", i, err)
} }
keyrest, cld := get(n, key) keyrest, cld := get(n, key)
switch cld := cld.(type) { switch cld := cld.(type) {
case nil: case nil:
// The trie doesn't contain the key. // The trie doesn't contain the key.
return nil, nil, i return nil, i, nil
case hashNode: case hashNode:
key = keyrest key = keyrest
copy(wantHash[:], cld) copy(wantHash[:], cld)
case valueNode: case valueNode:
return cld, nil, i + 1 return cld, i + 1, nil
} }
} }
} }

View file

@ -32,20 +32,46 @@ func init() {
mrand.Seed(time.Now().Unix()) mrand.Seed(time.Now().Unix())
} }
// makeProvers creates Merkle trie provers based on different implementations to
// test all variations.
func makeProvers(trie *Trie) []func(key []byte) *ethdb.MemDatabase {
var provers []func(key []byte) *ethdb.MemDatabase
// Create a direct trie based Merkle prover
provers = append(provers, func(key []byte) *ethdb.MemDatabase {
proof := ethdb.NewMemDatabase()
trie.Prove(key, 0, proof)
return proof
})
// Create a leaf iterator based Merkle prover
provers = append(provers, func(key []byte) *ethdb.MemDatabase {
proof := ethdb.NewMemDatabase()
if it := NewIterator(trie.NodeIterator(key)); it.Next() && bytes.Equal(key, it.Key) {
for _, p := range it.Prove() {
proof.Put(crypto.Keccak256(p), p)
}
}
return proof
})
return provers
}
func TestProof(t *testing.T) { func TestProof(t *testing.T) {
trie, vals := randomTrie(500) trie, vals := randomTrie(500)
root := trie.Hash() root := trie.Hash()
for _, kv := range vals { for i, prover := range makeProvers(trie) {
proofs := ethdb.NewMemDatabase() for _, kv := range vals {
if trie.Prove(kv.k, 0, proofs) != nil { proof := prover(kv.k)
t.Fatalf("missing key %x while constructing proof", kv.k) if proof == nil {
} t.Fatalf("prover %d: missing key %x while constructing proof", i, kv.k)
val, err, _ := VerifyProof(root, kv.k, proofs) }
if err != nil { val, _, err := VerifyProof(root, kv.k, proof)
t.Fatalf("VerifyProof error for key %x: %v\nraw proof: %v", kv.k, err, proofs) if err != nil {
} t.Fatalf("prover %d: failed to verify proof for key %x: %v\nraw proof: %x", i, kv.k, err, proof)
if !bytes.Equal(val, kv.v) { }
t.Fatalf("VerifyProof returned wrong value for key %x: got %x, want %x", kv.k, val, kv.v) if !bytes.Equal(val, kv.v) {
t.Fatalf("prover %d: verified value mismatch for key %x: have %x, want %x", i, kv.k, val, kv.v)
}
} }
} }
} }
@ -53,37 +79,66 @@ func TestProof(t *testing.T) {
func TestOneElementProof(t *testing.T) { func TestOneElementProof(t *testing.T) {
trie := new(Trie) trie := new(Trie)
updateString(trie, "k", "v") updateString(trie, "k", "v")
proofs := ethdb.NewMemDatabase() for i, prover := range makeProvers(trie) {
trie.Prove([]byte("k"), 0, proofs) proof := prover([]byte("k"))
if len(proofs.Keys()) != 1 { if proof == nil {
t.Error("proof should have one element") t.Fatalf("prover %d: nil proof", i)
} }
val, err, _ := VerifyProof(trie.Hash(), []byte("k"), proofs) if proof.Len() != 1 {
if err != nil { t.Errorf("prover %d: proof should have one element", i)
t.Fatalf("VerifyProof error: %v\nproof hashes: %v", err, proofs.Keys()) }
} val, _, err := VerifyProof(trie.Hash(), []byte("k"), proof)
if !bytes.Equal(val, []byte("v")) { if err != nil {
t.Fatalf("VerifyProof returned wrong value: got %x, want 'k'", val) t.Fatalf("prover %d: failed to verify proof: %v\nraw proof: %x", i, err, proof)
}
if !bytes.Equal(val, []byte("v")) {
t.Fatalf("prover %d: verified value mismatch: have %x, want 'k'", i, val)
}
} }
} }
func TestVerifyBadProof(t *testing.T) { func TestBadProof(t *testing.T) {
trie, vals := randomTrie(800) trie, vals := randomTrie(800)
root := trie.Hash() root := trie.Hash()
for _, kv := range vals { for i, prover := range makeProvers(trie) {
proofs := ethdb.NewMemDatabase() for _, kv := range vals {
trie.Prove(kv.k, 0, proofs) proof := prover(kv.k)
if len(proofs.Keys()) == 0 { if proof == nil {
t.Fatal("zero length proof") t.Fatalf("prover %d: nil proof", i)
}
key := proof.Keys()[mrand.Intn(proof.Len())]
val, _ := proof.Get(key)
proof.Delete(key)
mutateByte(val)
proof.Put(crypto.Keccak256(val), val)
if _, _, err := VerifyProof(root, kv.k, proof); err == nil {
t.Fatalf("prover %d: expected proof to fail for key %x", i, kv.k)
}
} }
keys := proofs.Keys() }
key := keys[mrand.Intn(len(keys))] }
node, _ := proofs.Get(key)
proofs.Delete(key) // Tests that missing keys can also be proven. The test explicitly uses a single
mutateByte(node) // entry trie and checks for missing keys both before and after the single entry.
proofs.Put(crypto.Keccak256(node), node) func TestMissingKeyProof(t *testing.T) {
if _, err, _ := VerifyProof(root, kv.k, proofs); err == nil { trie := new(Trie)
t.Fatalf("expected proof to fail for key %x", kv.k) updateString(trie, "k", "v")
for i, key := range []string{"a", "j", "l", "z"} {
proof := ethdb.NewMemDatabase()
trie.Prove([]byte(key), 0, proof)
if proof.Len() != 1 {
t.Errorf("test %d: proof should have one element", i)
}
val, _, err := VerifyProof(trie.Hash(), []byte(key), proof)
if err != nil {
t.Fatalf("test %d: failed to verify proof: %v\nraw proof: %x", i, err, proof)
}
if val != nil {
t.Fatalf("test %d: verified value mismatch: have %x, want nil", i, val)
} }
} }
} }
@ -131,7 +186,7 @@ func BenchmarkVerifyProof(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
im := i % len(keys) im := i % len(keys)
if _, err, _ := VerifyProof(root, []byte(keys[im]), proofs[im]); err != nil { if _, _, err := VerifyProof(root, []byte(keys[im]), proofs[im]); err != nil {
b.Fatalf("key %x: %v", keys[im], err) b.Fatalf("key %x: %v", keys[im], err)
} }
} }

View file

@ -155,14 +155,19 @@ func (t *SecureTrie) Commit(onleaf LeafCallback) (root common.Hash, err error) {
return t.trie.Commit(onleaf) return t.trie.Commit(onleaf)
} }
// Hash returns the root hash of SecureTrie. It does not write to the
// database and can be used even if the trie doesn't have one.
func (t *SecureTrie) Hash() common.Hash { func (t *SecureTrie) Hash() common.Hash {
return t.trie.Hash() return t.trie.Hash()
} }
// Root returns the root hash of SecureTrie.
// Deprecated: use Hash instead.
func (t *SecureTrie) Root() []byte { func (t *SecureTrie) Root() []byte {
return t.trie.Root() return t.trie.Root()
} }
// Copy returns a copy of SecureTrie.
func (t *SecureTrie) Copy() *SecureTrie { func (t *SecureTrie) Copy() *SecureTrie {
cpy := *t cpy := *t
return &cpy return &cpy

View file

@ -68,19 +68,19 @@ func newSyncMemBatch() *syncMemBatch {
} }
} }
// TrieSync is the main state trie synchronisation scheduler, which provides yet // Sync is the main state trie synchronisation scheduler, which provides yet
// unknown trie hashes to retrieve, accepts node data associated with said hashes // unknown trie hashes to retrieve, accepts node data associated with said hashes
// and reconstructs the trie step by step until all is done. // and reconstructs the trie step by step until all is done.
type TrieSync struct { type Sync struct {
database DatabaseReader // Persistent database to check for existing entries database DatabaseReader // Persistent database to check for existing entries
membatch *syncMemBatch // Memory buffer to avoid frequest database writes membatch *syncMemBatch // Memory buffer to avoid frequest database writes
requests map[common.Hash]*request // Pending requests pertaining to a key hash requests map[common.Hash]*request // Pending requests pertaining to a key hash
queue *prque.Prque // Priority queue with the pending requests queue *prque.Prque // Priority queue with the pending requests
} }
// NewTrieSync creates a new trie data download scheduler. // NewSync creates a new trie data download scheduler.
func NewTrieSync(root common.Hash, database DatabaseReader, callback LeafCallback) *TrieSync { func NewSync(root common.Hash, database DatabaseReader, callback LeafCallback) *Sync {
ts := &TrieSync{ ts := &Sync{
database: database, database: database,
membatch: newSyncMemBatch(), membatch: newSyncMemBatch(),
requests: make(map[common.Hash]*request), requests: make(map[common.Hash]*request),
@ -91,7 +91,7 @@ func NewTrieSync(root common.Hash, database DatabaseReader, callback LeafCallbac
} }
// AddSubTrie registers a new trie to the sync code, rooted at the designated parent. // AddSubTrie registers a new trie to the sync code, rooted at the designated parent.
func (s *TrieSync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callback LeafCallback) { func (s *Sync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callback LeafCallback) {
// Short circuit if the trie is empty or already known // Short circuit if the trie is empty or already known
if root == emptyRoot { if root == emptyRoot {
return return
@ -126,7 +126,7 @@ func (s *TrieSync) AddSubTrie(root common.Hash, depth int, parent common.Hash, c
// interpreted as a trie node, but rather accepted and stored into the database // interpreted as a trie node, but rather accepted and stored into the database
// as is. This method's goal is to support misc state metadata retrievals (e.g. // as is. This method's goal is to support misc state metadata retrievals (e.g.
// contract code). // contract code).
func (s *TrieSync) AddRawEntry(hash common.Hash, depth int, parent common.Hash) { func (s *Sync) AddRawEntry(hash common.Hash, depth int, parent common.Hash) {
// Short circuit if the entry is empty or already known // Short circuit if the entry is empty or already known
if hash == emptyState { if hash == emptyState {
return return
@ -156,7 +156,7 @@ func (s *TrieSync) AddRawEntry(hash common.Hash, depth int, parent common.Hash)
} }
// Missing retrieves the known missing nodes from the trie for retrieval. // Missing retrieves the known missing nodes from the trie for retrieval.
func (s *TrieSync) Missing(max int) []common.Hash { func (s *Sync) Missing(max int) []common.Hash {
requests := []common.Hash{} requests := []common.Hash{}
for !s.queue.Empty() && (max == 0 || len(requests) < max) { for !s.queue.Empty() && (max == 0 || len(requests) < max) {
requests = append(requests, s.queue.PopItem().(common.Hash)) requests = append(requests, s.queue.PopItem().(common.Hash))
@ -167,7 +167,7 @@ func (s *TrieSync) Missing(max int) []common.Hash {
// Process injects a batch of retrieved trie nodes data, returning if something // Process injects a batch of retrieved trie nodes data, returning if something
// was committed to the database and also the index of an entry if processing of // was committed to the database and also the index of an entry if processing of
// it failed. // it failed.
func (s *TrieSync) Process(results []SyncResult) (bool, int, error) { func (s *Sync) Process(results []SyncResult) (bool, int, error) {
committed := false committed := false
for i, item := range results { for i, item := range results {
@ -213,7 +213,7 @@ func (s *TrieSync) Process(results []SyncResult) (bool, int, error) {
// Commit flushes the data stored in the internal membatch out to persistent // Commit flushes the data stored in the internal membatch out to persistent
// storage, returning the number of items written and any occurred error. // storage, returning the number of items written and any occurred error.
func (s *TrieSync) Commit(dbw ethdb.Putter) (int, error) { func (s *Sync) Commit(dbw ethdb.Putter) (int, error) {
// Dump the membatch into a database dbw // Dump the membatch into a database dbw
for i, key := range s.membatch.order { for i, key := range s.membatch.order {
if err := dbw.Put(key[:], s.membatch.batch[key]); err != nil { if err := dbw.Put(key[:], s.membatch.batch[key]); err != nil {
@ -228,14 +228,14 @@ func (s *TrieSync) Commit(dbw ethdb.Putter) (int, error) {
} }
// Pending returns the number of state entries currently pending for download. // Pending returns the number of state entries currently pending for download.
func (s *TrieSync) Pending() int { func (s *Sync) Pending() int {
return len(s.requests) return len(s.requests)
} }
// schedule inserts a new state retrieval request into the fetch queue. If there // schedule inserts a new state retrieval request into the fetch queue. If there
// is already a pending request for this node, the new request will be discarded // is already a pending request for this node, the new request will be discarded
// and only a parent reference added to the old one. // and only a parent reference added to the old one.
func (s *TrieSync) schedule(req *request) { func (s *Sync) schedule(req *request) {
// If we're already requesting this node, add a new reference and stop // If we're already requesting this node, add a new reference and stop
if old, ok := s.requests[req.hash]; ok { if old, ok := s.requests[req.hash]; ok {
old.parents = append(old.parents, req.parents...) old.parents = append(old.parents, req.parents...)
@ -248,7 +248,7 @@ func (s *TrieSync) schedule(req *request) {
// children retrieves all the missing children of a state trie entry for future // children retrieves all the missing children of a state trie entry for future
// retrieval scheduling. // retrieval scheduling.
func (s *TrieSync) children(req *request, object node) ([]*request, error) { func (s *Sync) children(req *request, object node) ([]*request, error) {
// Gather all the children of the node, irrelevant whether known or not // Gather all the children of the node, irrelevant whether known or not
type child struct { type child struct {
node node node node
@ -310,7 +310,7 @@ func (s *TrieSync) children(req *request, object node) ([]*request, error) {
// commit finalizes a retrieval request and stores it into the membatch. If any // commit finalizes a retrieval request and stores it into the membatch. If any
// of the referencing parent requests complete due to this commit, they are also // of the referencing parent requests complete due to this commit, they are also
// committed themselves. // committed themselves.
func (s *TrieSync) commit(req *request) (err error) { func (s *Sync) commit(req *request) (err error) {
// Write the node content to the membatch // Write the node content to the membatch
s.membatch.batch[req.hash] = req.data s.membatch.batch[req.hash] = req.data
s.membatch.order = append(s.membatch.order, req.hash) s.membatch.order = append(s.membatch.order, req.hash)

View file

@ -87,14 +87,14 @@ func checkTrieConsistency(db *Database, root common.Hash) error {
} }
// Tests that an empty trie is not scheduled for syncing. // Tests that an empty trie is not scheduled for syncing.
func TestEmptyTrieSync(t *testing.T) { func TestEmptySync(t *testing.T) {
dbA := NewDatabase(ethdb.NewMemDatabase()) dbA := NewDatabase(ethdb.NewMemDatabase())
dbB := NewDatabase(ethdb.NewMemDatabase()) dbB := NewDatabase(ethdb.NewMemDatabase())
emptyA, _ := New(common.Hash{}, dbA) emptyA, _ := New(common.Hash{}, dbA)
emptyB, _ := New(emptyRoot, dbB) emptyB, _ := New(emptyRoot, dbB)
for i, trie := range []*Trie{emptyA, emptyB} { for i, trie := range []*Trie{emptyA, emptyB} {
if req := NewTrieSync(trie.Hash(), ethdb.NewMemDatabase(), nil).Missing(1); len(req) != 0 { if req := NewSync(trie.Hash(), ethdb.NewMemDatabase(), nil).Missing(1); len(req) != 0 {
t.Errorf("test %d: content requested for empty trie: %v", i, req) t.Errorf("test %d: content requested for empty trie: %v", i, req)
} }
} }
@ -102,17 +102,17 @@ func TestEmptyTrieSync(t *testing.T) {
// Tests that given a root hash, a trie can sync iteratively on a single thread, // Tests that given a root hash, a trie can sync iteratively on a single thread,
// requesting retrieval tasks and returning all of them in one go. // requesting retrieval tasks and returning all of them in one go.
func TestIterativeTrieSyncIndividual(t *testing.T) { testIterativeTrieSync(t, 1) } func TestIterativeSyncIndividual(t *testing.T) { testIterativeSync(t, 1) }
func TestIterativeTrieSyncBatched(t *testing.T) { testIterativeTrieSync(t, 100) } func TestIterativeSyncBatched(t *testing.T) { testIterativeSync(t, 100) }
func testIterativeTrieSync(t *testing.T, batch int) { func testIterativeSync(t *testing.T, batch int) {
// Create a random trie to copy // Create a random trie to copy
srcDb, srcTrie, srcData := makeTestTrie() srcDb, srcTrie, srcData := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
diskdb := ethdb.NewMemDatabase() diskdb := ethdb.NewMemDatabase()
triedb := NewDatabase(diskdb) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) sched := NewSync(srcTrie.Hash(), diskdb, nil)
queue := append([]common.Hash{}, sched.Missing(batch)...) queue := append([]common.Hash{}, sched.Missing(batch)...)
for len(queue) > 0 { for len(queue) > 0 {
@ -138,14 +138,14 @@ func testIterativeTrieSync(t *testing.T, batch int) {
// Tests that the trie scheduler can correctly reconstruct the state even if only // Tests that the trie scheduler can correctly reconstruct the state even if only
// partial results are returned, and the others sent only later. // partial results are returned, and the others sent only later.
func TestIterativeDelayedTrieSync(t *testing.T) { func TestIterativeDelayedSync(t *testing.T) {
// Create a random trie to copy // Create a random trie to copy
srcDb, srcTrie, srcData := makeTestTrie() srcDb, srcTrie, srcData := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
diskdb := ethdb.NewMemDatabase() diskdb := ethdb.NewMemDatabase()
triedb := NewDatabase(diskdb) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) sched := NewSync(srcTrie.Hash(), diskdb, nil)
queue := append([]common.Hash{}, sched.Missing(10000)...) queue := append([]common.Hash{}, sched.Missing(10000)...)
for len(queue) > 0 { for len(queue) > 0 {
@ -173,17 +173,17 @@ func TestIterativeDelayedTrieSync(t *testing.T) {
// Tests that given a root hash, a trie can sync iteratively on a single thread, // Tests that given a root hash, a trie can sync iteratively on a single thread,
// requesting retrieval tasks and returning all of them in one go, however in a // requesting retrieval tasks and returning all of them in one go, however in a
// random order. // random order.
func TestIterativeRandomTrieSyncIndividual(t *testing.T) { testIterativeRandomTrieSync(t, 1) } func TestIterativeRandomSyncIndividual(t *testing.T) { testIterativeRandomSync(t, 1) }
func TestIterativeRandomTrieSyncBatched(t *testing.T) { testIterativeRandomTrieSync(t, 100) } func TestIterativeRandomSyncBatched(t *testing.T) { testIterativeRandomSync(t, 100) }
func testIterativeRandomTrieSync(t *testing.T, batch int) { func testIterativeRandomSync(t *testing.T, batch int) {
// Create a random trie to copy // Create a random trie to copy
srcDb, srcTrie, srcData := makeTestTrie() srcDb, srcTrie, srcData := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
diskdb := ethdb.NewMemDatabase() diskdb := ethdb.NewMemDatabase()
triedb := NewDatabase(diskdb) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) sched := NewSync(srcTrie.Hash(), diskdb, nil)
queue := make(map[common.Hash]struct{}) queue := make(map[common.Hash]struct{})
for _, hash := range sched.Missing(batch) { for _, hash := range sched.Missing(batch) {
@ -217,14 +217,14 @@ func testIterativeRandomTrieSync(t *testing.T, batch int) {
// Tests that the trie scheduler can correctly reconstruct the state even if only // Tests that the trie scheduler can correctly reconstruct the state even if only
// partial results are returned (Even those randomly), others sent only later. // partial results are returned (Even those randomly), others sent only later.
func TestIterativeRandomDelayedTrieSync(t *testing.T) { func TestIterativeRandomDelayedSync(t *testing.T) {
// Create a random trie to copy // Create a random trie to copy
srcDb, srcTrie, srcData := makeTestTrie() srcDb, srcTrie, srcData := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
diskdb := ethdb.NewMemDatabase() diskdb := ethdb.NewMemDatabase()
triedb := NewDatabase(diskdb) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) sched := NewSync(srcTrie.Hash(), diskdb, nil)
queue := make(map[common.Hash]struct{}) queue := make(map[common.Hash]struct{})
for _, hash := range sched.Missing(10000) { for _, hash := range sched.Missing(10000) {
@ -264,14 +264,14 @@ func TestIterativeRandomDelayedTrieSync(t *testing.T) {
// Tests that a trie sync will not request nodes multiple times, even if they // Tests that a trie sync will not request nodes multiple times, even if they
// have such references. // have such references.
func TestDuplicateAvoidanceTrieSync(t *testing.T) { func TestDuplicateAvoidanceSync(t *testing.T) {
// Create a random trie to copy // Create a random trie to copy
srcDb, srcTrie, srcData := makeTestTrie() srcDb, srcTrie, srcData := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
diskdb := ethdb.NewMemDatabase() diskdb := ethdb.NewMemDatabase()
triedb := NewDatabase(diskdb) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) sched := NewSync(srcTrie.Hash(), diskdb, nil)
queue := append([]common.Hash{}, sched.Missing(0)...) queue := append([]common.Hash{}, sched.Missing(0)...)
requested := make(map[common.Hash]struct{}) requested := make(map[common.Hash]struct{})
@ -304,14 +304,14 @@ func TestDuplicateAvoidanceTrieSync(t *testing.T) {
// Tests that at any point in time during a sync, only complete sub-tries are in // Tests that at any point in time during a sync, only complete sub-tries are in
// the database. // the database.
func TestIncompleteTrieSync(t *testing.T) { func TestIncompleteSync(t *testing.T) {
// Create a random trie to copy // Create a random trie to copy
srcDb, srcTrie, _ := makeTestTrie() srcDb, srcTrie, _ := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
diskdb := ethdb.NewMemDatabase() diskdb := ethdb.NewMemDatabase()
triedb := NewDatabase(diskdb) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) sched := NewSync(srcTrie.Hash(), diskdb, nil)
added := []common.Hash{} added := []common.Hash{}
queue := append([]common.Hash{}, sched.Missing(1)...) queue := append([]common.Hash{}, sched.Missing(1)...)

View file

@ -101,7 +101,7 @@ func New(root common.Hash, db *Database) (*Trie, error) {
db: db, db: db,
originalRoot: root, originalRoot: root,
} }
if (root != common.Hash{}) && root != emptyRoot { if root != (common.Hash{}) && root != emptyRoot {
rootnode, err := trie.resolveHash(root[:], nil) rootnode, err := trie.resolveHash(root[:], nil)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -35,6 +35,7 @@ type DB struct {
// Stats. Need 64-bit alignment. // Stats. Need 64-bit alignment.
cWriteDelay int64 // The cumulative duration of write delays cWriteDelay int64 // The cumulative duration of write delays
cWriteDelayN int32 // The cumulative number of write delays cWriteDelayN int32 // The cumulative number of write delays
inWritePaused int32 // The indicator whether write operation is paused by compaction
aliveSnaps, aliveIters int32 aliveSnaps, aliveIters int32
// Session. // Session.
@ -967,7 +968,8 @@ func (db *DB) GetProperty(name string) (value string, err error) {
float64(db.s.stor.writes())/1048576.0) float64(db.s.stor.writes())/1048576.0)
case p == "writedelay": case p == "writedelay":
writeDelayN, writeDelay := atomic.LoadInt32(&db.cWriteDelayN), time.Duration(atomic.LoadInt64(&db.cWriteDelay)) writeDelayN, writeDelay := atomic.LoadInt32(&db.cWriteDelayN), time.Duration(atomic.LoadInt64(&db.cWriteDelay))
value = fmt.Sprintf("DelayN:%d Delay:%s", writeDelayN, writeDelay) paused := atomic.LoadInt32(&db.inWritePaused) == 1
value = fmt.Sprintf("DelayN:%d Delay:%s Paused:%t", writeDelayN, writeDelay, paused)
case p == "sstables": case p == "sstables":
for level, tables := range v.levels { for level, tables := range v.levels {
value += fmt.Sprintf("--- level %d ---\n", level) value += fmt.Sprintf("--- level %d ---\n", level)
@ -996,6 +998,75 @@ func (db *DB) GetProperty(name string) (value string, err error) {
return return
} }
// DBStats is database statistics.
type DBStats struct {
WriteDelayCount int32
WriteDelayDuration time.Duration
WritePaused bool
AliveSnapshots int32
AliveIterators int32
IOWrite uint64
IORead uint64
BlockCacheSize int
OpenedTablesCount int
LevelSizes []int64
LevelTablesCounts []int
LevelRead []int64
LevelWrite []int64
LevelDurations []time.Duration
}
// Stats populates s with database statistics.
func (db *DB) Stats(s *DBStats) error {
err := db.ok()
if err != nil {
return err
}
s.IORead = db.s.stor.reads()
s.IOWrite = db.s.stor.writes()
s.WriteDelayCount = atomic.LoadInt32(&db.cWriteDelayN)
s.WriteDelayDuration = time.Duration(atomic.LoadInt64(&db.cWriteDelay))
s.WritePaused = atomic.LoadInt32(&db.inWritePaused) == 1
s.OpenedTablesCount = db.s.tops.cache.Size()
if db.s.tops.bcache != nil {
s.BlockCacheSize = db.s.tops.bcache.Size()
} else {
s.BlockCacheSize = 0
}
s.AliveIterators = atomic.LoadInt32(&db.aliveIters)
s.AliveSnapshots = atomic.LoadInt32(&db.aliveSnaps)
s.LevelDurations = s.LevelDurations[:0]
s.LevelRead = s.LevelRead[:0]
s.LevelWrite = s.LevelWrite[:0]
s.LevelSizes = s.LevelSizes[:0]
s.LevelTablesCounts = s.LevelTablesCounts[:0]
v := db.s.version()
defer v.release()
for level, tables := range v.levels {
duration, read, write := db.compStats.getStat(level)
if len(tables) == 0 && duration == 0 {
continue
}
s.LevelDurations = append(s.LevelDurations, duration)
s.LevelRead = append(s.LevelRead, read)
s.LevelWrite = append(s.LevelWrite, write)
s.LevelSizes = append(s.LevelSizes, tables.size())
s.LevelTablesCounts = append(s.LevelTablesCounts, len(tables))
}
return nil
}
// SizeOf calculates approximate sizes of the given key ranges. // SizeOf calculates approximate sizes of the given key ranges.
// The length of the returned sizes are equal with the length of the given // The length of the returned sizes are equal with the length of the given
// ranges. The returned sizes measure storage space usage, so if the user // ranges. The returned sizes measure storage space usage, so if the user

View file

@ -89,7 +89,11 @@ func (db *DB) flush(n int) (mdb *memDB, mdbFree int, err error) {
return false return false
case tLen >= pauseTrigger: case tLen >= pauseTrigger:
delayed = true delayed = true
// Set the write paused flag explicitly.
atomic.StoreInt32(&db.inWritePaused, 1)
err = db.compTriggerWait(db.tcompCmdC) err = db.compTriggerWait(db.tcompCmdC)
// Unset the write paused flag.
atomic.StoreInt32(&db.inWritePaused, 0)
if err != nil { if err != nil {
return false return false
} }

6
vendor/vendor.json vendored
View file

@ -418,10 +418,10 @@
"revisionTime": "2017-07-05T02:17:15Z" "revisionTime": "2017-07-05T02:17:15Z"
}, },
{ {
"checksumSHA1": "k13cCuMJO7+KhR8ZXx5oUqDKGQA=", "checksumSHA1": "TJV50D0q8E3vtc90ibC+qOYdjrw=",
"path": "github.com/syndtr/goleveldb/leveldb", "path": "github.com/syndtr/goleveldb/leveldb",
"revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", "revision": "59047f74db0d042c8d8dd8e30bb030bc774a7d7a",
"revisionTime": "2018-05-02T07:23:49Z" "revisionTime": "2018-05-21T04:45:49Z"
}, },
{ {
"checksumSHA1": "EKIow7XkgNdWvR/982ffIZxKG8Y=", "checksumSHA1": "EKIow7XkgNdWvR/982ffIZxKG8Y=",

View file

@ -159,9 +159,9 @@ func (sc *Client) DeleteSymmetricKey(ctx context.Context, id string) error {
} }
// Post a message onto the network. // Post a message onto the network.
func (sc *Client) Post(ctx context.Context, message whisper.NewMessage) error { func (sc *Client) Post(ctx context.Context, message whisper.NewMessage) (string, error) {
var ignored bool var hash string
return sc.c.CallContext(ctx, &ignored, "shh_post", message) return hash, sc.c.CallContext(ctx, &hash, "shh_post", message)
} }
// SubscribeMessages subscribes to messages that match the given criteria. This method // SubscribeMessages subscribes to messages that match the given criteria. This method