This commit is contained in:
Zahoor Mohamed 2016-12-21 20:33:51 +05:30
commit 954e67b5b8
77 changed files with 3403 additions and 1141 deletions

View file

@ -13,15 +13,15 @@ matrix:
go: 1.6.2 go: 1.6.2
- os: linux - os: linux
dist: trusty dist: trusty
go: 1.7 go: 1.7.4
- os: osx - os: osx
go: 1.7 go: 1.7.4
# This builder does the Ubuntu PPA and Linux Azure uploads # This builder does the Ubuntu PPA and Linux Azure uploads
- os: linux - os: linux
dist: trusty dist: trusty
sudo: required sudo: required
go: 1.7 go: 1.7.4
env: env:
- ubuntu-ppa - ubuntu-ppa
- azure-linux - azure-linux
@ -55,7 +55,7 @@ matrix:
# This builder does the OSX Azure, Android Maven and Azure and iOS CocoaPods and Azure uploads # This builder does the OSX Azure, Android Maven and Azure and iOS CocoaPods and Azure uploads
- os: osx - os: osx
go: 1.7 go: 1.7.4
env: env:
- azure-osx - azure-osx
- mobile - mobile

View file

@ -39,9 +39,7 @@ The go-ethereum project comes with several wrappers/executables found in the `cm
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow insolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). | | `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow insolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. | | `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | | `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
| `bzzd` | swarm daemon. This is the entrypoint for the swarm network. `bzzd --help` for command line options. See https://swarm-guide.readthedocs.io for swarm documentation. | | `swarm` | swarm daemon and tools. This is the entrypoint for the swarm network. `swarm --help` for command line options and subcommands. See https://swarm-guide.readthedocs.io for swarm documentation. |
| `bzzup` | swarm command line file uploader. `bzzup --help` for command line options |
| `bzzhash` | command to calculate the swarm hash of a file or directory. `bzzhash --help` for command line options |
## Running geth ## Running geth

View file

@ -1 +1 @@
1.5.5 1.5.6

View file

@ -33,7 +33,7 @@ const (
FixedBytesTy FixedBytesTy
BytesTy BytesTy
HashTy HashTy
RealTy FixedpointTy
) )
// Type is the reflection of the supported argument type // Type is the reflection of the supported argument type
@ -57,16 +57,16 @@ var (
// Types can be in the format of: // Types can be in the format of:
// //
// Input = Type [ "[" [ Number ] "]" ] Name . // Input = Type [ "[" [ Number ] "]" ] Name .
// Type = [ "u" ] "int" [ Number ] . // Type = [ "u" ] "int" [ Number ] [ x ] [ Number ].
// //
// Examples: // Examples:
// //
// string int uint real // string int uint fixed
// string32 int8 uint8 uint[] // string32 int8 uint8 uint[]
// address int256 uint256 real[2] // address int256 uint256 fixed128x128[2]
fullTypeRegex = regexp.MustCompile("([a-zA-Z0-9]+)(\\[([0-9]*)?\\])?") fullTypeRegex = regexp.MustCompile("([a-zA-Z0-9]+)(\\[([0-9]*)\\])?")
// typeRegex parses the abi sub types // typeRegex parses the abi sub types
typeRegex = regexp.MustCompile("([a-zA-Z]+)([0-9]*)?") typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
) )
// NewType creates a new reflection type of abi type given in t. // NewType creates a new reflection type of abi type given in t.
@ -97,7 +97,7 @@ func NewType(t string) (typ Type, err error) {
parsedType := typeRegex.FindAllStringSubmatch(res[1], -1)[0] parsedType := typeRegex.FindAllStringSubmatch(res[1], -1)[0]
// varSize is the size of the variable // varSize is the size of the variable
var varSize int var varSize int
if len(parsedType[2]) > 0 { if len(parsedType[3]) > 0 {
var err error var err error
varSize, err = strconv.Atoi(parsedType[2]) varSize, err = strconv.Atoi(parsedType[2])
if err != nil { if err != nil {

78
accounts/abi/type_test.go Normal file
View file

@ -0,0 +1,78 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package abi
import (
"reflect"
"testing"
)
// typeWithoutStringer is a alias for the Type type which simply doesn't implement
// the stringer interface to allow printing type details in the tests below.
type typeWithoutStringer Type
// Tests that all allowed types get recognized by the type parser.
func TestTypeRegexp(t *testing.T) {
tests := []struct {
blob string
kind Type
}{
{"int", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
{"int8", Type{Kind: reflect.Int8, Type: big_t, Size: 8, T: IntTy, stringKind: "int8"}},
{"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
{"int[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
{"int[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
{"int32[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Int32, Type: big_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
{"int32[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{Kind: reflect.Int32, Type: big_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
{"uint", Type{Kind: reflect.Ptr, Type: ubig_t, Size: 256, T: UintTy, stringKind: "uint256"}},
{"uint8", Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}},
{"uint256", Type{Kind: reflect.Ptr, Type: ubig_t, Size: 256, T: UintTy, stringKind: "uint256"}},
{"uint[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Ptr, Type: ubig_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
{"uint[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{Kind: reflect.Ptr, Type: ubig_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
{"uint32[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint32, Type: big_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
{"uint32[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{Kind: reflect.Uint32, Type: big_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
{"bytes", Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}},
{"bytes32", Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}},
{"bytes[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
{"bytes[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[2]"}},
{"bytes32[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
{"bytes32[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
{"string", Type{Kind: reflect.String, Size: -1, T: StringTy, stringKind: "string"}},
{"string[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.String, Size: -1, T: StringTy, stringKind: "string"}, stringKind: "string[]"}},
{"string[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{Kind: reflect.String, Size: -1, T: StringTy, stringKind: "string"}, stringKind: "string[2]"}},
{"address", Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}},
{"address[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
{"address[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
// TODO when fixed types are implemented properly
// {"fixed", Type{}},
// {"fixed128x128", Type{}},
// {"fixed[]", Type{}},
// {"fixed[2]", Type{}},
// {"fixed128x128[]", Type{}},
// {"fixed128x128[2]", Type{}},
}
for i, tt := range tests {
typ, err := NewType(tt.blob)
if err != nil {
t.Errorf("type %d: failed to parse type string: %v", i, err)
}
if !reflect.DeepEqual(typ, tt.kind) {
t.Errorf("type %d: parsed type mismatch:\n have %+v\n want %+v", i, typeWithoutStringer(typ), typeWithoutStringer(tt.kind))
}
}
}

View file

@ -22,8 +22,8 @@ environment:
install: install:
- rmdir C:\go /s /q - rmdir C:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.7.3.windows-%GETH_ARCH%.zip - appveyor DownloadFile https://storage.googleapis.com/golang/go1.7.4.windows-%GETH_ARCH%.zip
- 7z x go1.7.3.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - 7z x go1.7.4.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
- go version - go version
- gcc --version - gcc --version

View file

@ -72,9 +72,7 @@ var (
executablePath("abigen"), executablePath("abigen"),
executablePath("evm"), executablePath("evm"),
executablePath("geth"), executablePath("geth"),
executablePath("bzzd"), executablePath("swarm"),
executablePath("bzzhash"),
executablePath("bzzup"),
executablePath("rlpdump"), executablePath("rlpdump"),
} }
@ -93,16 +91,8 @@ var (
Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.", Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
}, },
{ {
Name: "bzzd", Name: "swarm",
Description: "Ethereum Swarm daemon", Description: "Ethereum Swarm daemon and tools",
},
{
Name: "bzzup",
Description: "Ethereum Swarm command line file/directory uploader",
},
{
Name: "bzzhash",
Description: "Ethereum Swarm file/directory hash calculator",
}, },
{ {
Name: "abigen", Name: "abigen",
@ -112,7 +102,8 @@ var (
// Distros for which packages are created. // Distros for which packages are created.
// Note: vivid is unsupported because there is no golang-1.6 package for it. // Note: vivid is unsupported because there is no golang-1.6 package for it.
debDistros = []string{"trusty", "wily", "xenial", "yakkety"} // Note: wily is unsupported because it was officially deprecated on lanchpad.
debDistros = []string{"trusty", "xenial", "yakkety"}
) )
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
@ -638,6 +629,7 @@ func doWindowsInstaller(cmdline []string) {
build.Render("build/nsis.geth.nsi", filepath.Join(*workdir, "geth.nsi"), 0644, nil) build.Render("build/nsis.geth.nsi", filepath.Join(*workdir, "geth.nsi"), 0644, nil)
build.Render("build/nsis.install.nsh", filepath.Join(*workdir, "install.nsh"), 0644, templateData) build.Render("build/nsis.install.nsh", filepath.Join(*workdir, "install.nsh"), 0644, templateData)
build.Render("build/nsis.uninstall.nsh", filepath.Join(*workdir, "uninstall.nsh"), 0644, allTools) build.Render("build/nsis.uninstall.nsh", filepath.Join(*workdir, "uninstall.nsh"), 0644, allTools)
build.Render("build/nsis.pathupdate.nsh", filepath.Join(*workdir, "PathUpdate.nsh"), 0644, nil)
build.Render("build/nsis.envvarupdate.nsh", filepath.Join(*workdir, "EnvVarUpdate.nsh"), 0644, nil) build.Render("build/nsis.envvarupdate.nsh", filepath.Join(*workdir, "EnvVarUpdate.nsh"), 0644, nil)
build.CopyFile(filepath.Join(*workdir, "SimpleFC.dll"), "build/nsis.simplefc.dll", 0755) build.CopyFile(filepath.Join(*workdir, "SimpleFC.dll"), "build/nsis.simplefc.dll", 0755)
build.CopyFile(filepath.Join(*workdir, "COPYING"), "COPYING", 0755) build.CopyFile(filepath.Join(*workdir, "COPYING"), "COPYING", 0755)

View file

@ -17,8 +17,12 @@
# #
# Requirements: # Requirements:
# - NSIS, http://nsis.sourceforge.net/Main_Page # - NSIS, http://nsis.sourceforge.net/Main_Page
# - NSIS Large Strings build, http://nsis.sourceforge.net/Special_Builds
# - SFP, http://nsis.sourceforge.net/NSIS_Simple_Firewall_Plugin (put dll in NSIS\Plugins\x86-ansi) # - SFP, http://nsis.sourceforge.net/NSIS_Simple_Firewall_Plugin (put dll in NSIS\Plugins\x86-ansi)
# #
# After intalling NSIS extra the NSIS Large Strings build zip and replace the makensis.exe and the
# files found in Stub.
#
# based on: http://nsis.sourceforge.net/A_simple_installer_with_start_menu_shortcut_and_uninstaller # based on: http://nsis.sourceforge.net/A_simple_installer_with_start_menu_shortcut_and_uninstaller
# #
# TODO: # TODO:
@ -37,6 +41,7 @@ RequestExecutionLevel admin
SetCompressor /SOLID lzma SetCompressor /SOLID lzma
!include LogicLib.nsh !include LogicLib.nsh
!include PathUpdate.nsh
!include EnvVarUpdate.nsh !include EnvVarUpdate.nsh
!macro VerifyUserIsAdmin !macro VerifyUserIsAdmin

View file

@ -37,8 +37,9 @@ Section "Geth" GETH_IDX
${EnvVarUpdate} $0 "ETHEREUM_SOCKET" "R" "HKLM" "\\.\pipe\geth.ipc" ${EnvVarUpdate} $0 "ETHEREUM_SOCKET" "R" "HKLM" "\\.\pipe\geth.ipc"
${EnvVarUpdate} $0 "ETHEREUM_SOCKET" "A" "HKLM" "\\.\pipe\geth.ipc" ${EnvVarUpdate} $0 "ETHEREUM_SOCKET" "A" "HKLM" "\\.\pipe\geth.ipc"
# Add geth to PATH # Add instdir to PATH
${EnvVarUpdate} $0 "PATH" "A" "HKLM" $INSTDIR Push "$INSTDIR"
Call AddToPath
SectionEnd SectionEnd
# Install optional develop tools. # Install optional develop tools.

153
build/nsis.pathupdate.nsh Normal file
View file

@ -0,0 +1,153 @@
!include "WinMessages.nsh"
; see https://support.microsoft.com/en-us/kb/104011
!define Environ 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
; HKEY_LOCAL_MACHINE = 0x80000002
; AddToPath - Appends dir to PATH
; (does not work on Win9x/ME)
;
; Usage:
; Push "dir"
; Call AddToPath
Function AddToPath
Exch $0
Push $1
Push $2
Push $3
Push $4
; NSIS ReadRegStr returns empty string on string overflow
; Native calls are used here to check actual length of PATH
; $4 = RegOpenKey(HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Control\Session Manager\Environment", &$3)
System::Call "advapi32::RegOpenKey(i 0x80000002, t'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', *i.r3) i.r4"
IntCmp $4 0 0 done done
; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2))
; RegCloseKey($3)
System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4"
System::Call "advapi32::RegCloseKey(i $3)"
IntCmp $4 234 0 +4 +4 ; $4 == ERROR_MORE_DATA
DetailPrint "AddToPath: original length $2 > ${NSIS_MAX_STRLEN}"
MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}"
Goto done
IntCmp $4 0 +5 ; $4 != NO_ERROR
IntCmp $4 2 +3 ; $4 != ERROR_FILE_NOT_FOUND
DetailPrint "AddToPath: unexpected error code $4"
Goto done
StrCpy $1 ""
; Check if already in PATH
Push "$1;"
Push "$0;"
Call StrStr
Pop $2
StrCmp $2 "" 0 done
Push "$1;"
Push "$0\;"
Call StrStr
Pop $2
StrCmp $2 "" 0 done
; Prevent NSIS string overflow
StrLen $2 $0
StrLen $3 $1
IntOp $2 $2 + $3
IntOp $2 $2 + 2 ; $2 = strlen(dir) + strlen(PATH) + sizeof(";")
IntCmp $2 ${NSIS_MAX_STRLEN} +4 +4 0
DetailPrint "AddToPath: new length $2 > ${NSIS_MAX_STRLEN}"
MessageBox MB_OK "PATH not updated, new length $2 > ${NSIS_MAX_STRLEN}."
Goto done
; Append dir to PATH
DetailPrint "Add to PATH: $0"
StrCpy $2 $1 1 -1
StrCmp $2 ";" 0 +2
StrCpy $1 $1 -1 ; remove trailing ';'
StrCmp $1 "" +2 ; no leading ';'
StrCpy $0 "$1;$0"
WriteRegExpandStr ${Environ} "PATH" $0
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
done:
Pop $4
Pop $3
Pop $2
Pop $1
Pop $0
FunctionEnd
; RemoveFromPath - Removes dir from PATH
;
; Usage:
; Push "dir"
; Call RemoveFromPath
Function un.RemoveFromPath
Exch $0
Push $1
Push $2
Push $3
Push $4
Push $5
Push $6
; NSIS ReadRegStr returns empty string on string overflow
; Native calls are used here to check actual length of PATH
; $4 = RegOpenKey(HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Control\Session Manager\Environment", &$3)
System::Call "advapi32::RegOpenKey(i 0x80000002, t'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', *i.r3) i.r4"
IntCmp $4 0 0 done done
; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2))
; RegCloseKey($3)
System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4"
System::Call "advapi32::RegCloseKey(i $3)"
IntCmp $4 234 0 +4 +4 ; $4 == ERROR_MORE_DATA
DetailPrint "RemoveFromPath: original length $2 > ${NSIS_MAX_STRLEN}"
MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}"
Goto done
IntCmp $4 0 +5 ; $4 != NO_ERROR
IntCmp $4 2 +3 ; $4 != ERROR_FILE_NOT_FOUND
DetailPrint "RemoveFromPath: unexpected error code $4"
Goto done
StrCpy $1 ""
; length < ${NSIS_MAX_STRLEN} -> ReadRegStr can be used
ReadRegStr $1 ${Environ} "PATH"
StrCpy $5 $1 1 -1
StrCmp $5 ";" +2
StrCpy $1 "$1;" ; ensure trailing ';'
Push $1
Push "$0;"
Call un.StrStr
Pop $2 ; pos of our dir
StrCmp $2 "" done
DetailPrint "Remove from PATH: $0"
StrLen $3 "$0;"
StrLen $4 $2
StrCpy $5 $1 -$4 ; $5 is now the part before the path to remove
StrCpy $6 $2 "" $3 ; $6 is now the part after the path to remove
StrCpy $3 "$5$6"
StrCpy $5 $3 1 -1
StrCmp $5 ";" 0 +2
StrCpy $3 $3 -1 ; remove trailing ';'
WriteRegExpandStr ${Environ} "PATH" $3
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
done:
Pop $6
Pop $5
Pop $4
Pop $3
Pop $2
Pop $1
Pop $0
FunctionEnd

View file

@ -25,7 +25,8 @@ Section "Uninstall"
${un.EnvVarUpdate} $0 "ETHEREUM_SOCKET" "R" "HKLM" "\\.\pipe\geth.ipc" ${un.EnvVarUpdate} $0 "ETHEREUM_SOCKET" "R" "HKLM" "\\.\pipe\geth.ipc"
# Remove install directory from PATH # Remove install directory from PATH
${un.EnvVarUpdate} $0 "PATH" "R" "HKLM" $INSTDIR Push "$INSTDIR"
Call un.RemoveFromPath
# Cleanup registry (deletes all sub keys) # Cleanup registry (deletes all sub keys)
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}"

View file

@ -1,24 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// Simple wrapper to translate the API exposed methods and types to inthernal
// Go versions of the same types.
#include "_cgo_export.h"
int run(const char* args) {
return doRun((char*)args);
}

View file

@ -1,46 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// Contains a simple library definition to allow creating a Geth instance from
// straight C code.
package main
// #ifdef __cplusplus
// extern "C" {
// #endif
//
// extern int run(const char*);
//
// #ifdef __cplusplus
// }
// #endif
import "C"
import (
"fmt"
"os"
"strings"
)
//export doRun
func doRun(args *C.char) C.int {
// This is equivalent to geth.main, just modified to handle the function arg passing
if err := app.Run(strings.Split("geth "+C.GoString(args), " ")); err != nil {
fmt.Fprintln(os.Stderr, err)
return -1
}
return 0
}

View file

@ -1,56 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// Contains specialized code for running Geth on Android.
package main
// #include <android/log.h>
// #cgo LDFLAGS: -llog
import "C"
import (
"bufio"
"os"
)
func init() {
// Redirect the standard output and error to logcat
oldStdout, oldStderr := os.Stdout, os.Stderr
outRead, outWrite, _ := os.Pipe()
errRead, errWrite, _ := os.Pipe()
os.Stdout = outWrite
os.Stderr = errWrite
go func() {
scanner := bufio.NewScanner(outRead)
for scanner.Scan() {
line := scanner.Text()
C.__android_log_write(C.ANDROID_LOG_INFO, C.CString("Stdout"), C.CString(line))
oldStdout.WriteString(line + "\n")
}
}()
go func() {
scanner := bufio.NewScanner(errRead)
for scanner.Scan() {
line := scanner.Text()
C.__android_log_write(C.ANDROID_LOG_INFO, C.CString("Stderr"), C.CString(line))
oldStderr.WriteString(line + "\n")
}
}()
}

View file

@ -19,22 +19,21 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"runtime"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"gopkg.in/urfave/cli.v1"
) )
func main() { func hash(ctx *cli.Context) {
runtime.GOMAXPROCS(runtime.NumCPU()) args := ctx.Args()
if len(args) < 1 {
if len(os.Args) < 2 { log.Fatal("Usage: swarm hash <file name>")
fmt.Println("Usage: bzzhash <file name>")
os.Exit(0)
} }
f, err := os.Open(os.Args[1]) f, err := os.Open(args[0])
if err != nil { if err != nil {
fmt.Println("Error opening file " + os.Args[1]) fmt.Println("Error opening file " + args[1])
os.Exit(1) os.Exit(1)
} }
@ -42,7 +41,7 @@ func main() {
chunker := storage.NewTreeChunker(storage.NewChunkerParams()) chunker := storage.NewTreeChunker(storage.NewChunkerParams())
key, err := chunker.Split(f, stat.Size(), nil, nil, nil) key, err := chunker.Split(f, stat.Size(), nil, nil, nil)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err) log.Fatalf("%v\n", err)
} else { } else {
fmt.Printf("%v\n", key) fmt.Printf("%v\n", key)
} }

View file

@ -43,11 +43,21 @@ import (
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
) )
const clientIdentifier = "bzzd" const (
clientIdentifier = "swarm"
versionString = "0.2"
)
var ( var (
gitCommit string // Git SHA1 commit hash of the release (set via linker flags) gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
app = utils.NewApp(gitCommit, "Ethereum Swarm server daemon") app = utils.NewApp(gitCommit, "Ethereum Swarm")
testbetBootNodes = []string{
"enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429",
"enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430",
"enode://fe29b82319b734ce1ec68b84657d57145fee237387e63273989d354486731e59f78858e452ef800a020559da22dcca759536e6aa5517c53930d29ce0b1029286@13.74.157.139:30431",
"enode://1d7187e7bde45cf0bee489ce9852dd6d1a0d9aa67a33a6b8e6db8a4fbc6fcfa6f0f1a5419343671521b863b187d1c73bad3603bae66421d157ffef357669ddb8@13.74.157.139:30432",
"enode://0e4cba800f7b1ee73673afa6a4acead4018f0149d2e3216be3f133318fd165b324cd71b81fbe1e80deac8dbf56e57a49db7be67f8b9bc81bd2b7ee496434fb5d@13.74.157.139:30433",
}
) )
var ( var (
@ -65,7 +75,7 @@ var (
} }
SwarmNetworkIdFlag = cli.IntFlag{ SwarmNetworkIdFlag = cli.IntFlag{
Name: "bzznetworkid", Name: "bzznetworkid",
Usage: "Network identifier (integer, default 322=swarm testnet)", Usage: "Network identifier (integer, default 3=swarm testnet)",
Value: network.NetworkId, Value: network.NetworkId,
} }
SwarmConfigPathFlag = cli.StringFlag{ SwarmConfigPathFlag = cli.StringFlag{
@ -85,10 +95,25 @@ var (
Usage: "URL of the Ethereum API provider", Usage: "URL of the Ethereum API provider",
Value: node.DefaultIPCEndpoint("geth"), Value: node.DefaultIPCEndpoint("geth"),
} }
SwarmApiFlag = cli.StringFlag{
Name: "bzzapi",
Usage: "Swarm HTTP endpoint",
Value: "http://127.0.0.1:8500",
}
SwarmRecursiveUploadFlag = cli.BoolFlag{
Name: "recursive",
Usage: "Upload directories recursively",
}
SwarmWantManifestFlag = cli.BoolTFlag{
Name: "manifest",
Usage: "Automatic manifest upload",
}
SwarmUploadDefaultPath = cli.StringFlag{
Name: "defaultpath",
Usage: "path to file served for empty url path (none)",
}
) )
var defaultBootnodes = []string{}
func init() { func init() {
// Override flag defaults so bzzd can run alongside geth. // Override flag defaults so bzzd can run alongside geth.
utils.ListenPortFlag.Value = 30399 utils.ListenPortFlag.Value = 30399
@ -96,8 +121,39 @@ func init() {
utils.IPCApiFlag.Value = "admin, bzz, chequebook, debug, rpc, web3" utils.IPCApiFlag.Value = "admin, bzz, chequebook, debug, rpc, web3"
// Set up the cli app. // Set up the cli app.
app.Commands = nil
app.Action = bzzd app.Action = bzzd
app.HideVersion = true // we have a command to print the version
app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
app.Commands = []cli.Command{
cli.Command{
Action: version,
Name: "version",
Usage: "Print version numbers",
ArgsUsage: " ",
Description: `
The output of this command is supposed to be machine-readable.
`,
},
cli.Command{
Action: upload,
Name: "up",
Usage: "upload a file or directory to swarm using the HTTP API",
ArgsUsage: " <file>",
Description: `
"upload a file or directory to swarm using the HTTP API and prints the root hash",
`,
},
cli.Command{
Action: hash,
Name: "hash",
Usage: "print the swarm hash of a file or directory",
ArgsUsage: " <file>",
Description: `
Prints the swarm hash of file or directory.
`,
},
}
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
utils.IdentityFlag, utils.IdentityFlag,
utils.DataDirFlag, utils.DataDirFlag,
@ -123,6 +179,11 @@ func init() {
SwarmAccountFlag, SwarmAccountFlag,
SwarmNetworkIdFlag, SwarmNetworkIdFlag,
ChequebookAddrFlag, ChequebookAddrFlag,
// upload flags
SwarmApiFlag,
SwarmRecursiveUploadFlag,
SwarmWantManifestFlag,
SwarmUploadDefaultPath,
} }
app.Flags = append(app.Flags, debug.Flags...) app.Flags = append(app.Flags, debug.Flags...)
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {
@ -142,17 +203,33 @@ func main() {
} }
} }
func version(ctx *cli.Context) error {
fmt.Println(strings.Title(clientIdentifier))
fmt.Println("Version:", versionString)
if gitCommit != "" {
fmt.Println("Git Commit:", gitCommit)
}
fmt.Println("Network Id:", ctx.GlobalInt(utils.NetworkIdFlag.Name))
fmt.Println("Go Version:", runtime.Version())
fmt.Println("OS:", runtime.GOOS)
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
return nil
}
func bzzd(ctx *cli.Context) error { func bzzd(ctx *cli.Context) error {
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit) stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
registerBzzService(ctx, stack) registerBzzService(ctx, stack)
utils.StartNode(stack) utils.StartNode(stack)
networkId := ctx.GlobalUint64(SwarmNetworkIdFlag.Name)
// Add bootnodes as initial peers. // Add bootnodes as initial peers.
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) { if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
bootnodes := strings.Split(ctx.GlobalString(utils.BootnodesFlag.Name), ",") bootnodes := strings.Split(ctx.GlobalString(utils.BootnodesFlag.Name), ",")
injectBootnodes(stack.Server(), bootnodes) injectBootnodes(stack.Server(), bootnodes)
} else { } else {
injectBootnodes(stack.Server(), defaultBootnodes) if networkId == 3 {
injectBootnodes(stack.Server(), testbetBootNodes)
}
} }
stack.Wait() stack.Wait()
@ -182,14 +259,12 @@ func registerBzzService(ctx *cli.Context, stack *node.Node) {
boot := func(ctx *node.ServiceContext) (node.Service, error) { boot := func(ctx *node.ServiceContext) (node.Service, error) {
var client *ethclient.Client var client *ethclient.Client
if ethapi == "" { if len(ethapi) > 0 {
err = fmt.Errorf("use ethapi flag to connect to a an eth client and talk to the blockchain")
} else {
client, err = ethclient.Dial(ethapi) client, err = ethclient.Dial(ethapi)
}
if err != nil { if err != nil {
utils.Fatalf("Can't connect: %v", err) utils.Fatalf("Can't connect: %v", err)
} }
}
return swarm.NewSwarm(ctx, client, bzzconfig, swapEnabled, syncEnabled) return swarm.NewSwarm(ctx, client, bzzconfig, swapEnabled, syncEnabled)
} }
if err := stack.Register(boot); err != nil { if err := stack.Register(boot); err != nil {

View file

@ -20,7 +20,6 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"flag"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -28,58 +27,83 @@ import (
"mime" "mime"
"net/http" "net/http"
"os" "os"
"os/user"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"gopkg.in/urfave/cli.v1"
) )
func main() { func upload(ctx *cli.Context) {
args := ctx.Args()
var ( var (
bzzapiFlag = flag.String("bzzapi", "http://127.0.0.1:8500", "Swarm HTTP endpoint") bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
recursiveFlag = flag.Bool("recursive", false, "Upload directories recursively") recursive = ctx.GlobalBool(SwarmRecursiveUploadFlag.Name)
manifestFlag = flag.Bool("manifest", true, "Skip automatic manifest upload") wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name)
defaultPath = ctx.GlobalString(SwarmUploadDefaultPath.Name)
) )
log.SetOutput(os.Stderr) if len(args) != 1 {
log.SetFlags(0)
flag.Parse()
if flag.NArg() != 1 {
log.Fatal("need filename as the first and only argument") log.Fatal("need filename as the first and only argument")
} }
var ( var (
file = flag.Arg(0) file = args[0]
client = &client{api: strings.TrimSuffix(*bzzapiFlag, "/")} client = &client{api: bzzapi}
mroot manifest mroot manifest
entry manifestEntry
) )
fi, err := os.Stat(file) fi, err := os.Stat(expandPath(file))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if fi.IsDir() { if fi.IsDir() {
if !*recursiveFlag { if !recursive {
log.Fatal("argument is a directory and recursive upload is disabled") log.Fatal("argument is a directory and recursive upload is disabled")
} }
mroot, err = client.uploadDirectory(file) mroot, err = client.uploadDirectory(file, defaultPath)
} else { } else {
mroot, err = client.uploadFile(file, fi) entry, err = client.uploadFile(file, fi)
if *manifestFlag { mroot = manifest{[]manifestEntry{entry}}
// Wrap the raw file entry in a proper manifest so both hashes get printed.
mroot = manifest{Entries: []manifest{mroot}}
}
} }
if err != nil { if err != nil {
log.Fatalln("upload failed:", err) log.Fatalln("upload failed:", err)
} }
if *manifestFlag { if !wantManifest {
// Print the manifest. This is the only output to stdout.
mrootJSON, _ := json.MarshalIndent(mroot, "", " ")
fmt.Println(string(mrootJSON))
return
}
hash, err := client.uploadManifest(mroot) hash, err := client.uploadManifest(mroot)
if err != nil { if err != nil {
log.Fatalln("manifest upload failed:", err) log.Fatalln("manifest upload failed:", err)
} }
mroot.Hash = hash fmt.Println(hash)
} }
// Print the manifest. This is the only output to stdout. // Expands a file path
mrootJSON, _ := json.MarshalIndent(mroot, "", " ") // 1. replace tilde with users home dir
fmt.Println(string(mrootJSON)) // 2. expands embedded environment variables
// 3. cleans the path, e.g. /a/b/../c -> /a/c
// Note, it has limitations, e.g. ~someuser/tmp will not be expanded
func expandPath(p string) string {
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
if home := homeDir(); home != "" {
p = home + p[1:]
}
}
return path.Clean(os.ExpandEnv(p))
}
func homeDir() string {
if home := os.Getenv("HOME"); home != "" {
return home
}
if usr, err := user.Current(); err == nil {
return usr.HomeDir
}
return ""
} }
// client wraps interaction with the swarm HTTP gateway. // client wraps interaction with the swarm HTTP gateway.
@ -88,24 +112,40 @@ type client struct {
} }
// manifest is the JSON representation of a swarm manifest. // manifest is the JSON representation of a swarm manifest.
type manifest struct { type manifestEntry struct {
Hash string `json:"hash,omitempty"` Hash string `json:"hash,omitempty"`
ContentType string `json:"contentType,omitempty"` ContentType string `json:"contentType,omitempty"`
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
Entries []manifest `json:"entries,omitempty"`
} }
func (c *client) uploadFile(file string, fi os.FileInfo) (manifest, error) { // manifest is the JSON representation of a swarm manifest.
type manifest struct {
Entries []manifestEntry `json:"entries,omitempty"`
}
func (c *client) uploadFile(file string, fi os.FileInfo) (manifestEntry, error) {
hash, err := c.uploadFileContent(file, fi) hash, err := c.uploadFileContent(file, fi)
m := manifest{ m := manifestEntry{
Hash: hash, Hash: hash,
ContentType: mime.TypeByExtension(filepath.Ext(fi.Name())), ContentType: mime.TypeByExtension(filepath.Ext(fi.Name())),
} }
return m, err return m, err
} }
func (c *client) uploadDirectory(dir string) (manifest, error) { func (c *client) uploadDirectory(dir string, defaultPath string) (manifest, error) {
dirm := manifest{} dirm := manifest{}
if len(defaultPath) > 0 {
fi, err := os.Stat(defaultPath)
if err != nil {
log.Fatal(err)
}
entry, err := c.uploadFile(defaultPath, fi)
if err != nil {
log.Fatal(err)
}
entry.Path = ""
dirm.Entries = append(dirm.Entries, entry)
}
prefix := filepath.ToSlash(filepath.Clean(dir)) + "/" prefix := filepath.ToSlash(filepath.Clean(dir)) + "/"
err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
if err != nil || fi.IsDir() { if err != nil || fi.IsDir() {

View file

@ -18,12 +18,14 @@
package utils package utils
import ( import (
"compress/gzip"
"fmt" "fmt"
"io" "io"
"os" "os"
"os/signal" "os/signal"
"regexp" "regexp"
"runtime" "runtime"
"strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -133,7 +135,15 @@ func ImportChain(chain *core.BlockChain, fn string) error {
return err return err
} }
defer fh.Close() defer fh.Close()
stream := rlp.NewStream(fh, 0)
var reader io.Reader = fh
if strings.HasSuffix(fn, ".gz") {
if reader, err = gzip.NewReader(reader); err != nil {
return err
}
}
stream := rlp.NewStream(reader, 0)
// Run actual the import. // Run actual the import.
blocks := make(types.Blocks, importBatchSize) blocks := make(types.Blocks, importBatchSize)
@ -195,10 +205,18 @@ func ExportChain(blockchain *core.BlockChain, fn string) error {
return err return err
} }
defer fh.Close() defer fh.Close()
if err := blockchain.Export(fh); err != nil {
var writer io.Writer = fh
if strings.HasSuffix(fn, ".gz") {
writer = gzip.NewWriter(writer)
defer writer.(*gzip.Writer).Close()
}
if err := blockchain.Export(writer); err != nil {
return err return err
} }
glog.Infoln("Exported blockchain to ", fn) glog.Infoln("Exported blockchain to ", fn)
return nil return nil
} }
@ -210,7 +228,14 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
return err return err
} }
defer fh.Close() defer fh.Close()
if err := blockchain.ExportN(fh, first, last); err != nil {
var writer io.Writer = fh
if strings.HasSuffix(fn, ".gz") {
writer = gzip.NewWriter(writer)
defer writer.(*gzip.Writer).Close()
}
if err := blockchain.ExportN(writer, first, last); err != nil {
return err return err
} }
glog.Infoln("Exported blockchain to ", fn) glog.Infoln("Exported blockchain to ", fn)

View file

@ -601,7 +601,11 @@ func (self *BlockChain) procFutureBlocks() {
} }
if len(blocks) > 0 { if len(blocks) > 0 {
types.BlockBy(types.Number).Sort(blocks) types.BlockBy(types.Number).Sort(blocks)
self.InsertChain(blocks)
// Insert one by one as chain insertion needs contiguous ancestry between blocks
for i := range blocks {
self.InsertChain(blocks[i : i+1])
}
} }
} }
@ -675,6 +679,18 @@ func SetReceiptsData(config *params.ChainConfig, block *types.Block, receipts ty
// transaction and receipt data. // transaction and receipt data.
// XXX should this be moved to the test? // XXX should this be moved to the test?
func (self *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { func (self *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {
// Do a sanity check that the provided chain is actually ordered and linked
for i := 1; i < len(blockChain); i++ {
if blockChain[i].NumberU64() != blockChain[i-1].NumberU64()+1 || blockChain[i].ParentHash() != blockChain[i-1].Hash() {
// Chain broke ancestry, log a messge (programming error) and skip insertion
failure := fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, blockChain[i-1].NumberU64(),
blockChain[i-1].Hash().Bytes()[:4], i, blockChain[i].NumberU64(), blockChain[i].Hash().Bytes()[:4], blockChain[i].ParentHash().Bytes()[:4])
glog.V(logger.Error).Info(failure.Error())
return 0, failure
}
}
// Pre-checks passed, start the block body and receipt imports
self.wg.Add(1) self.wg.Add(1)
defer self.wg.Done() defer self.wg.Done()
@ -843,6 +859,18 @@ func (self *BlockChain) WriteBlock(block *types.Block) (status WriteStatus, err
// InsertChain will attempt to insert the given chain in to the canonical chain or, otherwise, create a fork. It an error is returned // InsertChain will attempt to insert the given chain in to the canonical chain or, otherwise, create a fork. It an error is returned
// it will return the index number of the failing block as well an error describing what went wrong (for possible errors see core/errors.go). // it will return the index number of the failing block as well an error describing what went wrong (for possible errors see core/errors.go).
func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
// Do a sanity check that the provided chain is actually ordered and linked
for i := 1; i < len(chain); i++ {
if chain[i].NumberU64() != chain[i-1].NumberU64()+1 || chain[i].ParentHash() != chain[i-1].Hash() {
// Chain broke ancestry, log a messge (programming error) and skip insertion
failure := fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])",
i-1, chain[i-1].NumberU64(), chain[i-1].Hash().Bytes()[:4], i, chain[i].NumberU64(), chain[i].Hash().Bytes()[:4], chain[i].ParentHash().Bytes()[:4])
glog.V(logger.Error).Info(failure.Error())
return 0, failure
}
}
// Pre-checks passed, start the full block imports
self.wg.Add(1) self.wg.Add(1)
defer self.wg.Done() defer self.wg.Done()
@ -916,10 +944,8 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
} }
self.reportBlock(block, nil, err) self.reportBlock(block, nil, err)
return i, err return i, err
} }
// Create a new statedb using the parent block and report an // Create a new statedb using the parent block and report an
// error if it fails. // error if it fails.
switch { switch {

View file

@ -224,6 +224,17 @@ type WhCallback func(*types.Header) error
// of the header retrieval mechanisms already need to verfy nonces, as well as // of the header retrieval mechanisms already need to verfy nonces, as well as
// because nonces can be verified sparsely, not needing to check each. // because nonces can be verified sparsely, not needing to check each.
func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, checkFreq int, writeHeader WhCallback) (int, error) { func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, checkFreq int, writeHeader WhCallback) (int, error) {
// Do a sanity check that the provided chain is actually ordered and linked
for i := 1; i < len(chain); i++ {
if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != chain[i-1].Hash() {
// Chain broke ancestry, log a messge (programming error) and skip insertion
failure := fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])",
i-1, chain[i-1].Number.Uint64(), chain[i-1].Hash().Bytes()[:4], i, chain[i].Number.Uint64(), chain[i].Hash().Bytes()[:4], chain[i].ParentHash.Bytes()[:4])
glog.V(logger.Error).Info(failure.Error())
return 0, failure
}
}
// Collect some import statistics to report on // Collect some import statistics to report on
stats := struct{ processed, ignored int }{} stats := struct{ processed, ignored int }{}
start := time.Now() start := time.Now()

View file

@ -41,7 +41,6 @@ var (
ErrNonce = errors.New("Nonce too low") ErrNonce = errors.New("Nonce too low")
ErrCheap = errors.New("Gas price too low for acceptance") ErrCheap = errors.New("Gas price too low for acceptance")
ErrBalance = errors.New("Insufficient balance") ErrBalance = errors.New("Insufficient balance")
ErrNonExistentAccount = errors.New("Account does not exist or account balance too low")
ErrInsufficientFunds = errors.New("Insufficient funds for gas * price + value") ErrInsufficientFunds = errors.New("Insufficient funds for gas * price + value")
ErrIntrinsicGas = errors.New("Intrinsic gas too low") ErrIntrinsicGas = errors.New("Intrinsic gas too low")
ErrGasLimit = errors.New("Exceeds block gas limit") ErrGasLimit = errors.New("Exceeds block gas limit")
@ -124,6 +123,8 @@ func NewTxPool(config *params.ChainConfig, eventMux *event.TypeMux, currentState
quit: make(chan struct{}), quit: make(chan struct{}),
} }
pool.resetState()
pool.wg.Add(2) pool.wg.Add(2)
go pool.eventLoop() go pool.eventLoop()
go pool.expirationLoop() go pool.expirationLoop()
@ -176,7 +177,7 @@ func (pool *TxPool) resetState() {
// any transactions that have been included in the block or // any transactions that have been included in the block or
// have been invalidated because of another transaction (e.g. // have been invalidated because of another transaction (e.g.
// higher gas price) // higher gas price)
pool.demoteUnexecutables() pool.demoteUnexecutables(currentState)
// Update all accounts to the latest known pending nonce // Update all accounts to the latest known pending nonce
for addr, list := range pool.pending { for addr, list := range pool.pending {
@ -185,7 +186,7 @@ func (pool *TxPool) resetState() {
} }
// Check the queue and move transactions over to the pending if possible // Check the queue and move transactions over to the pending if possible
// or remove those that have become invalid // or remove those that have become invalid
pool.promoteExecutables() pool.promoteExecutables(currentState)
} }
func (pool *TxPool) Stop() { func (pool *TxPool) Stop() {
@ -237,21 +238,26 @@ func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common
// Pending retrieves all currently processable transactions, groupped by origin // Pending retrieves all currently processable transactions, groupped by origin
// account and sorted by nonce. The returned transaction set is a copy and can be // account and sorted by nonce. The returned transaction set is a copy and can be
// freely modified by calling code. // freely modified by calling code.
func (pool *TxPool) Pending() map[common.Address]types.Transactions { func (pool *TxPool) Pending() (map[common.Address]types.Transactions, error) {
pool.mu.Lock() pool.mu.Lock()
defer pool.mu.Unlock() defer pool.mu.Unlock()
state, err := pool.currentState()
if err != nil {
return nil, err
}
// check queue first // check queue first
pool.promoteExecutables() pool.promoteExecutables(state)
// invalidate any txs // invalidate any txs
pool.demoteUnexecutables() pool.demoteUnexecutables(state)
pending := make(map[common.Address]types.Transactions) pending := make(map[common.Address]types.Transactions)
for addr, list := range pool.pending { for addr, list := range pool.pending {
pending[addr] = list.Flatten() pending[addr] = list.Flatten()
} }
return pending return pending, nil
} }
// SetLocal marks a transaction as local, skipping gas price // SetLocal marks a transaction as local, skipping gas price
@ -280,13 +286,6 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
if err != nil { if err != nil {
return ErrInvalidSender return ErrInvalidSender
} }
// Make sure the account exist. Non existent accounts
// haven't got funds and well therefor never pass.
if !currentState.Exist(from) {
return ErrNonExistentAccount
}
// Last but not least check for nonce errors // Last but not least check for nonce errors
if currentState.GetNonce(from) > tx.Nonce() { if currentState.GetNonce(from) > tx.Nonce() {
return ErrNonce return ErrNonce
@ -372,10 +371,6 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) {
// //
// 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) {
// Init delayed since tx pool could have been started before any state sync
if pool.pendingState == nil {
pool.resetState()
}
// 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)
@ -410,13 +405,19 @@ func (pool *TxPool) Add(tx *types.Transaction) error {
if err := pool.add(tx); err != nil { if err := pool.add(tx); err != nil {
return err return err
} }
pool.promoteExecutables()
state, err := pool.currentState()
if err != nil {
return err
}
pool.promoteExecutables(state)
return nil return nil
} }
// AddBatch attempts to queue a batch of transactions. // AddBatch attempts to queue a batch of transactions.
func (pool *TxPool) AddBatch(txs []*types.Transaction) { func (pool *TxPool) AddBatch(txs []*types.Transaction) error {
pool.mu.Lock() pool.mu.Lock()
defer pool.mu.Unlock() defer pool.mu.Unlock()
@ -425,7 +426,15 @@ func (pool *TxPool) AddBatch(txs []*types.Transaction) {
glog.V(logger.Debug).Infoln("tx error:", err) glog.V(logger.Debug).Infoln("tx error:", err)
} }
} }
pool.promoteExecutables()
state, err := pool.currentState()
if err != nil {
return err
}
pool.promoteExecutables(state)
return nil
} }
// Get returns a transaction if it is contained in the pool // Get returns a transaction if it is contained in the pool
@ -499,17 +508,7 @@ func (pool *TxPool) removeTx(hash common.Hash) {
// promoteExecutables moves transactions that have become processable from the // promoteExecutables moves transactions that have become processable from the
// 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() { func (pool *TxPool) promoteExecutables(state *state.StateDB) {
// Init delayed since tx pool could have been started before any state sync
if pool.pendingState == nil {
pool.resetState()
}
// Retrieve the current state to allow nonce and balance checking
state, err := pool.currentState()
if err != nil {
glog.Errorf("Could not get current state: %v", err)
return
}
// Iterate over all accounts and promote any executable transactions // Iterate over all accounts and promote any executable transactions
queued := uint64(0) queued := uint64(0)
for addr, list := range pool.queue { for addr, list := range pool.queue {
@ -645,13 +644,7 @@ func (pool *TxPool) promoteExecutables() {
// demoteUnexecutables removes invalid and processed transactions from the pools // demoteUnexecutables removes invalid and processed transactions from the pools
// executable/pending queue and any subsequent transactions that become unexecutable // executable/pending queue and any subsequent transactions that become unexecutable
// are moved back into the future queue. // are moved back into the future queue.
func (pool *TxPool) demoteUnexecutables() { func (pool *TxPool) demoteUnexecutables(state *state.StateDB) {
// Retrieve the current state to allow nonce and balance checking
state, err := pool.currentState()
if err != nil {
glog.V(logger.Info).Infoln("failed to get current state: %v", err)
return
}
// Iterate over all accounts and demote any non-executable transactions // Iterate over all accounts and demote any non-executable transactions
for addr, list := range pool.pending { for addr, list := range pool.pending {
nonce := state.GetNonce(addr) nonce := state.GetNonce(addr)

View file

@ -51,14 +51,84 @@ func deriveSender(tx *types.Transaction) (common.Address, error) {
return types.Sender(types.HomesteadSigner{}, tx) return types.Sender(types.HomesteadSigner{}, tx)
} }
// This test simulates a scenario where a new block is imported during a
// state reset and tests whether the pending state is in sync with the
// block head event that initiated the resetState().
func TestStateChangeDuringPoolReset(t *testing.T) {
var (
db, _ = ethdb.NewMemDatabase()
key, _ = crypto.GenerateKey()
address = crypto.PubkeyToAddress(key.PublicKey)
mux = new(event.TypeMux)
statedb, _ = state.New(common.Hash{}, db)
trigger = false
)
// setup pool with 2 transaction in it
statedb.SetBalance(address, new(big.Int).Mul(common.Big1, common.Ether))
tx0 := transaction(0, big.NewInt(100000), key)
tx1 := transaction(1, big.NewInt(100000), key)
// stateFunc is used multiple times to reset the pending state.
// when simulate is true it will create a state that indicates
// that tx0 and tx1 are included in the chain.
stateFunc := func() (*state.StateDB, error) {
// delay "state change" by one. The tx pool fetches the
// state multiple times and by delaying it a bit we simulate
// a state change between those fetches.
stdb := statedb
if trigger {
statedb, _ = state.New(common.Hash{}, db)
// simulate that the new head block included tx0 and tx1
statedb.SetNonce(address, 2)
statedb.SetBalance(address, new(big.Int).Mul(common.Big1, common.Ether))
trigger = false
}
return stdb, nil
}
gasLimitFunc := func() *big.Int { return big.NewInt(1000000000) }
txpool := NewTxPool(testChainConfig(), mux, stateFunc, gasLimitFunc)
txpool.resetState()
nonce := txpool.State().GetNonce(address)
if nonce != 0 {
t.Fatalf("Invalid nonce, want 0, got %d", nonce)
}
txpool.AddBatch(types.Transactions{tx0, tx1})
nonce = txpool.State().GetNonce(address)
if nonce != 2 {
t.Fatalf("Invalid nonce, want 2, got %d", nonce)
}
// trigger state change in the background
trigger = true
txpool.resetState()
pendingTx, err := txpool.Pending()
if err != nil {
t.Fatalf("Could not fetch pending transactions: %v", err)
}
for addr, txs := range pendingTx {
t.Logf("%0x: %d\n", addr, len(txs))
}
nonce = txpool.State().GetNonce(address)
if nonce != 2 {
t.Fatalf("Invalid nonce, want 2, got %d", nonce)
}
}
func TestInvalidTransactions(t *testing.T) { func TestInvalidTransactions(t *testing.T) {
pool, key := setupTxPool() pool, key := setupTxPool()
tx := transaction(0, big.NewInt(100), key) tx := transaction(0, big.NewInt(100), key)
if err := pool.Add(tx); err != ErrNonExistentAccount {
t.Error("expected", ErrNonExistentAccount)
}
from, _ := deriveSender(tx) from, _ := deriveSender(tx)
currentState, _ := pool.currentState() currentState, _ := pool.currentState()
currentState.AddBalance(from, big.NewInt(1)) currentState.AddBalance(from, big.NewInt(1))
@ -97,9 +167,10 @@ func TestTransactionQueue(t *testing.T) {
from, _ := deriveSender(tx) from, _ := deriveSender(tx)
currentState, _ := pool.currentState() currentState, _ := pool.currentState()
currentState.AddBalance(from, big.NewInt(1000)) currentState.AddBalance(from, big.NewInt(1000))
pool.resetState()
pool.enqueueTx(tx.Hash(), tx) pool.enqueueTx(tx.Hash(), tx)
pool.promoteExecutables() pool.promoteExecutables(currentState)
if len(pool.pending) != 1 { if len(pool.pending) != 1 {
t.Error("expected valid txs to be 1 is", len(pool.pending)) t.Error("expected valid txs to be 1 is", len(pool.pending))
} }
@ -108,7 +179,7 @@ func TestTransactionQueue(t *testing.T) {
from, _ = deriveSender(tx) from, _ = deriveSender(tx)
currentState.SetNonce(from, 2) currentState.SetNonce(from, 2)
pool.enqueueTx(tx.Hash(), tx) pool.enqueueTx(tx.Hash(), tx)
pool.promoteExecutables() pool.promoteExecutables(currentState)
if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
t.Error("expected transaction to be in tx pool") t.Error("expected transaction to be in tx pool")
} }
@ -124,11 +195,13 @@ func TestTransactionQueue(t *testing.T) {
from, _ = deriveSender(tx1) from, _ = deriveSender(tx1)
currentState, _ = pool.currentState() currentState, _ = pool.currentState()
currentState.AddBalance(from, big.NewInt(1000)) currentState.AddBalance(from, big.NewInt(1000))
pool.resetState()
pool.enqueueTx(tx1.Hash(), tx1) pool.enqueueTx(tx1.Hash(), tx1)
pool.enqueueTx(tx2.Hash(), tx2) pool.enqueueTx(tx2.Hash(), tx2)
pool.enqueueTx(tx3.Hash(), tx3) pool.enqueueTx(tx3.Hash(), tx3)
pool.promoteExecutables() pool.promoteExecutables(currentState)
if len(pool.pending) != 1 { if len(pool.pending) != 1 {
t.Error("expected tx pool to be 1, got", len(pool.pending)) t.Error("expected tx pool to be 1, got", len(pool.pending))
@ -225,7 +298,8 @@ func TestTransactionDoubleNonce(t *testing.T) {
if err := pool.add(tx2); err != nil { if err := pool.add(tx2); err != nil {
t.Error("didn't expect error", err) t.Error("didn't expect error", err)
} }
pool.promoteExecutables() state, _ := pool.currentState()
pool.promoteExecutables(state)
if pool.pending[addr].Len() != 1 { if pool.pending[addr].Len() != 1 {
t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) t.Error("expected 1 pending transactions, got", pool.pending[addr].Len())
} }
@ -236,7 +310,7 @@ func TestTransactionDoubleNonce(t *testing.T) {
if err := pool.add(tx3); err != nil { if err := pool.add(tx3); err != nil {
t.Error("didn't expect error", err) t.Error("didn't expect error", err)
} }
pool.promoteExecutables() pool.promoteExecutables(state)
if pool.pending[addr].Len() != 1 { if pool.pending[addr].Len() != 1 {
t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) t.Error("expected 1 pending transactions, got", pool.pending[addr].Len())
} }
@ -295,6 +369,7 @@ func TestRemovedTxEvent(t *testing.T) {
from, _ := deriveSender(tx) from, _ := deriveSender(tx)
currentState, _ := pool.currentState() currentState, _ := pool.currentState()
currentState.AddBalance(from, big.NewInt(1000000000000)) currentState.AddBalance(from, big.NewInt(1000000000000))
pool.resetState()
pool.eventMux.Post(RemovedTransactionEvent{types.Transactions{tx}}) pool.eventMux.Post(RemovedTransactionEvent{types.Transactions{tx}})
pool.eventMux.Post(ChainHeadEvent{nil}) pool.eventMux.Post(ChainHeadEvent{nil})
if pool.pending[from].Len() != 1 { if pool.pending[from].Len() != 1 {
@ -452,6 +527,7 @@ func TestTransactionQueueAccountLimiting(t *testing.T) {
state, _ := pool.currentState() state, _ := pool.currentState()
state.AddBalance(account, big.NewInt(1000000)) state.AddBalance(account, big.NewInt(1000000))
pool.resetState()
// Keep queuing up transactions and make sure all above a limit are dropped // Keep queuing up transactions and make sure all above a limit are dropped
for i := uint64(1); i <= maxQueuedPerAccount+5; i++ { for i := uint64(1); i <= maxQueuedPerAccount+5; i++ {
@ -564,6 +640,7 @@ func TestTransactionPendingLimiting(t *testing.T) {
state, _ := pool.currentState() state, _ := pool.currentState()
state.AddBalance(account, big.NewInt(1000000)) state.AddBalance(account, big.NewInt(1000000))
pool.resetState()
// Keep queuing up transactions and make sure all above a limit are dropped // Keep queuing up transactions and make sure all above a limit are dropped
for i := uint64(0); i < maxQueuedPerAccount+5; i++ { for i := uint64(0); i < maxQueuedPerAccount+5; i++ {
@ -733,7 +810,7 @@ func benchmarkPendingDemotion(b *testing.B, size int) {
// Benchmark the speed of pool validation // Benchmark the speed of pool validation
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
pool.demoteUnexecutables() pool.demoteUnexecutables(state)
} }
} }
@ -757,7 +834,7 @@ func benchmarkFuturePromotion(b *testing.B, size int) {
// Benchmark the speed of pool validation // Benchmark the speed of pool validation
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
pool.promoteExecutables() pool.promoteExecutables(state)
} }
} }

View file

@ -18,6 +18,7 @@ package eth
import ( import (
"bytes" "bytes"
"compress/gzip"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -25,6 +26,7 @@ import (
"math/big" "math/big"
"os" "os"
"runtime" "runtime"
"strings"
"time" "time"
"github.com/ethereum/ethash" "github.com/ethereum/ethash"
@ -80,7 +82,7 @@ type PublicMinerAPI struct {
// NewPublicMinerAPI create a new PublicMinerAPI instance. // NewPublicMinerAPI create a new PublicMinerAPI instance.
func NewPublicMinerAPI(e *Ethereum) *PublicMinerAPI { func NewPublicMinerAPI(e *Ethereum) *PublicMinerAPI {
agent := miner.NewRemoteAgent() agent := miner.NewRemoteAgent(e.Pow())
e.Miner().Register(agent) e.Miner().Register(agent)
return &PublicMinerAPI{e, agent} return &PublicMinerAPI{e, agent}
@ -217,8 +219,14 @@ func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) {
} }
defer out.Close() defer out.Close()
var writer io.Writer = out
if strings.HasSuffix(file, ".gz") {
writer = gzip.NewWriter(writer)
defer writer.(*gzip.Writer).Close()
}
// Export the blockchain // Export the blockchain
if err := api.eth.BlockChain().Export(out); err != nil { if err := api.eth.BlockChain().Export(writer); err != nil {
return false, err return false, err
} }
return true, nil return true, nil
@ -243,8 +251,15 @@ func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) {
} }
defer in.Close() defer in.Close()
var reader io.Reader = in
if strings.HasSuffix(file, ".gz") {
if reader, err = gzip.NewReader(reader); err != nil {
return false, err
}
}
// Run actual the import in pre-configured batches // Run actual the import in pre-configured batches
stream := rlp.NewStream(in, 0) stream := rlp.NewStream(reader, 0)
blocks, index := make([]*types.Block, 0, 2500), 0 blocks, index := make([]*types.Block, 0, 2500), 0
for batch := 0; ; batch++ { for batch := 0; ; batch++ {

View file

@ -131,15 +131,20 @@ func (b *EthApiBackend) RemoveTx(txHash common.Hash) {
b.eth.txPool.Remove(txHash) b.eth.txPool.Remove(txHash)
} }
func (b *EthApiBackend) GetPoolTransactions() types.Transactions { func (b *EthApiBackend) GetPoolTransactions() (types.Transactions, error) {
b.eth.txMu.Lock() b.eth.txMu.Lock()
defer b.eth.txMu.Unlock() defer b.eth.txMu.Unlock()
pending, err := b.eth.txPool.Pending()
if err != nil {
return nil, err
}
var txs types.Transactions var txs types.Transactions
for _, batch := range b.eth.txPool.Pending() { for _, batch := range pending {
txs = append(txs, batch...) txs = append(txs, batch...)
} }
return txs return txs, nil
} }
func (b *EthApiBackend) GetPoolTransaction(hash common.Hash) *types.Transaction { func (b *EthApiBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {

View file

@ -104,6 +104,7 @@ type Config struct {
type LesServer interface { type LesServer interface {
Start(srvr *p2p.Server) Start(srvr *p2p.Server)
Synced()
Stop() Stop()
Protocols() []p2p.Protocol Protocols() []p2p.Protocol
} }
@ -145,6 +146,7 @@ type Ethereum struct {
func (s *Ethereum) AddLesServer(ls LesServer) { func (s *Ethereum) AddLesServer(ls LesServer) {
s.lesServer = ls s.lesServer = ls
s.protocolManager.lesServer = ls
} }
// New creates a new Ethereum object (including the // New creates a new Ethereum object (including the

View file

@ -54,6 +54,8 @@ type Filter struct {
// New creates a new filter which uses a bloom filter on blocks to figure out whether // New creates a new filter which uses a bloom filter on blocks to figure out whether
// a particular block is interesting or not. // a particular block is interesting or not.
// MipMaps allow past blocks to be searched much more efficiently, but are not available
// to light clients.
func New(backend Backend, useMipMap bool) *Filter { func New(backend Backend, useMipMap bool) *Filter {
return &Filter{ return &Filter{
backend: backend, backend: backend,
@ -85,8 +87,11 @@ func (f *Filter) SetTopics(topics [][]common.Hash) {
f.topics = topics f.topics = topics
} }
// Run filters logs with the current parameters set // FindOnce searches the blockchain for matching log entries, returning
func (f *Filter) Find(ctx context.Context) ([]*vm.Log, error) { // all matching entries from the first block that contains matches,
// updating the start point of the filter accordingly. If no results are
// found, a nil slice is returned.
func (f *Filter) FindOnce(ctx context.Context) ([]*vm.Log, error) {
head, _ := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) head, _ := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
if head == nil { if head == nil {
return nil, nil return nil, nil
@ -106,47 +111,69 @@ func (f *Filter) Find(ctx context.Context) ([]*vm.Log, error) {
// uses the mipmap bloom filters to check for fast inclusion and uses // uses the mipmap bloom filters to check for fast inclusion and uses
// higher range probability in order to ensure at least a false positive // higher range probability in order to ensure at least a false positive
if !f.useMipMap || len(f.addresses) == 0 { if !f.useMipMap || len(f.addresses) == 0 {
return f.getLogs(ctx, beginBlockNo, endBlockNo) logs, blockNumber, err := f.getLogs(ctx, beginBlockNo, endBlockNo)
f.begin = int64(blockNumber + 1)
return logs, err
} }
return f.mipFind(beginBlockNo, endBlockNo, 0), nil
logs, blockNumber := f.mipFind(beginBlockNo, endBlockNo, 0)
f.begin = int64(blockNumber + 1)
return logs, nil
} }
func (f *Filter) mipFind(start, end uint64, depth int) (logs []*vm.Log) { // Run filters logs with the current parameters set
func (f *Filter) Find(ctx context.Context) (logs []*vm.Log, err error) {
for {
newLogs, err := f.FindOnce(ctx)
if len(newLogs) == 0 || err != nil {
return logs, err
}
logs = append(logs, newLogs...)
}
}
func (f *Filter) mipFind(start, end uint64, depth int) (logs []*vm.Log, blockNumber uint64) {
level := core.MIPMapLevels[depth] level := core.MIPMapLevels[depth]
// normalise numerator so we can work in level specific batches and // normalise numerator so we can work in level specific batches and
// work with the proper range checks // work with the proper range checks
for num := start / level * level; num <= end; num += level { for num := start / level * level; num <= end; num += level {
// find addresses in bloom filters // find addresses in bloom filters
bloom := core.GetMipmapBloom(f.db, num, level) bloom := core.GetMipmapBloom(f.db, num, level)
// Don't bother checking the first time through the loop - we're probably picking
// up where a previous run left off.
first := true
for _, addr := range f.addresses { for _, addr := range f.addresses {
if bloom.TestBytes(addr[:]) { if first || bloom.TestBytes(addr[:]) {
first = false
// range check normalised values and make sure that // range check normalised values and make sure that
// we're resolving the correct range instead of the // we're resolving the correct range instead of the
// normalised values. // normalised values.
start := uint64(math.Max(float64(num), float64(start))) start := uint64(math.Max(float64(num), float64(start)))
end := uint64(math.Min(float64(num+level-1), float64(end))) end := uint64(math.Min(float64(num+level-1), float64(end)))
if depth+1 == len(core.MIPMapLevels) { if depth+1 == len(core.MIPMapLevels) {
l, _ := f.getLogs(context.Background(), start, end) l, blockNumber, _ := f.getLogs(context.Background(), start, end)
logs = append(logs, l...) if len(l) > 0 {
} else { return l, blockNumber
logs = append(logs, f.mipFind(start, end, depth+1)...) }
} else {
l, blockNumber := f.mipFind(start, end, depth+1)
if len(l) > 0 {
return l, blockNumber
}
} }
// break so we don't check the same range for each
// possible address. Checks on multiple addresses
// are handled further down the stack.
break
} }
} }
} }
return logs return nil, end
} }
func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []*vm.Log, err error) { func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []*vm.Log, blockNumber uint64, err error) {
for i := start; i <= end; i++ { for i := start; i <= end; i++ {
header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(i)) blockNumber := rpc.BlockNumber(i)
header, err := f.backend.HeaderByNumber(ctx, blockNumber)
if header == nil || err != nil { if header == nil || err != nil {
return logs, err return logs, end, err
} }
// Use bloom filtering to see if this block is interesting given the // Use bloom filtering to see if this block is interesting given the
@ -155,17 +182,20 @@ func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []*vm.Log
// Get the logs of the block // Get the logs of the block
receipts, err := f.backend.GetReceipts(ctx, header.Hash()) receipts, err := f.backend.GetReceipts(ctx, header.Hash())
if err != nil { if err != nil {
return nil, err return nil, end, err
} }
var unfiltered []*vm.Log var unfiltered []*vm.Log
for _, receipt := range receipts { for _, receipt := range receipts {
unfiltered = append(unfiltered, ([]*vm.Log)(receipt.Logs)...) unfiltered = append(unfiltered, ([]*vm.Log)(receipt.Logs)...)
} }
logs = append(logs, filterLogs(unfiltered, nil, nil, f.addresses, f.topics)...) logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
if len(logs) > 0 {
return logs, uint64(blockNumber), nil
}
} }
} }
return logs, nil return logs, end, nil
} }
func includes(addresses []common.Address, a common.Address) bool { func includes(addresses []common.Address, a common.Address) bool {

View file

@ -87,6 +87,8 @@ type ProtocolManager struct {
quitSync chan struct{} quitSync chan struct{}
noMorePeers chan struct{} noMorePeers chan struct{}
lesServer LesServer
// wait group is used for graceful shutdowns during downloading // wait group is used for graceful shutdowns during downloading
// and processing // and processing
wg sync.WaitGroup wg sync.WaitGroup
@ -171,7 +173,7 @@ func NewProtocolManager(config *params.ChainConfig, fastSync bool, networkId int
return blockchain.CurrentBlock().NumberU64() return blockchain.CurrentBlock().NumberU64()
} }
inserter := func(blocks types.Blocks) (int, error) { inserter := func(blocks types.Blocks) (int, error) {
atomic.StoreUint32(&manager.synced, 1) // Mark initial sync done on any fetcher import manager.setSynced() // Mark initial sync done on any fetcher import
return manager.insertChain(blocks) return manager.insertChain(blocks)
} }
manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, manager.removePeer) manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, manager.removePeer)

View file

@ -93,7 +93,7 @@ type testTxPool struct {
// AddBatch appends a batch of transactions to the pool, and notifies any // AddBatch appends a batch of transactions to the pool, and notifies any
// listeners if the addition channel is non nil // listeners if the addition channel is non nil
func (p *testTxPool) AddBatch(txs []*types.Transaction) { func (p *testTxPool) AddBatch(txs []*types.Transaction) error {
p.lock.Lock() p.lock.Lock()
defer p.lock.Unlock() defer p.lock.Unlock()
@ -101,10 +101,12 @@ func (p *testTxPool) AddBatch(txs []*types.Transaction) {
if p.added != nil { if p.added != nil {
p.added <- txs p.added <- txs
} }
return nil
} }
// Pending returns all the transactions known to the pool // Pending returns all the transactions known to the pool
func (p *testTxPool) Pending() map[common.Address]types.Transactions { func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) {
p.lock.RLock() p.lock.RLock()
defer p.lock.RUnlock() defer p.lock.RUnlock()
@ -116,7 +118,7 @@ func (p *testTxPool) Pending() map[common.Address]types.Transactions {
for _, batch := range batches { for _, batch := range batches {
sort.Sort(types.TxByNonce(batch)) sort.Sort(types.TxByNonce(batch))
} }
return batches return batches, nil
} }
// newTestTransaction create a new dummy transaction. // newTestTransaction create a new dummy transaction.

View file

@ -98,11 +98,11 @@ var errorToString = map[int]string{
type txPool interface { type txPool interface {
// AddBatch should add the given transactions to the pool. // AddBatch should add the given transactions to the pool.
AddBatch([]*types.Transaction) AddBatch([]*types.Transaction) error
// Pending should return pending transactions. // Pending should return pending transactions.
// The slice should be modifiable by the caller. // The slice should be modifiable by the caller.
Pending() map[common.Address]types.Transactions Pending() (map[common.Address]types.Transactions, error)
} }
// statusData is the network packet for the status message. // statusData is the network packet for the status message.

View file

@ -46,7 +46,8 @@ type txsync struct {
// syncTransactions starts sending all currently pending transactions to the given peer. // syncTransactions starts sending all currently pending transactions to the given peer.
func (pm *ProtocolManager) syncTransactions(p *peer) { func (pm *ProtocolManager) syncTransactions(p *peer) {
var txs types.Transactions var txs types.Transactions
for _, batch := range pm.txpool.Pending() { pending, _ := pm.txpool.Pending()
for _, batch := range pending {
txs = append(txs, batch...) txs = append(txs, batch...)
} }
if len(txs) == 0 { if len(txs) == 0 {
@ -180,7 +181,7 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
if err := pm.downloader.Synchronise(peer.id, pHead, pTd, mode); err != nil { if err := pm.downloader.Synchronise(peer.id, pHead, pTd, mode); err != nil {
return return
} }
atomic.StoreUint32(&pm.synced, 1) // Mark initial sync done pm.setSynced() // Mark initial sync done
// If fast sync was enabled, and we synced up, disable it // If fast sync was enabled, and we synced up, disable it
if atomic.LoadUint32(&pm.fastSync) == 1 { if atomic.LoadUint32(&pm.fastSync) == 1 {
@ -191,3 +192,10 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
} }
} }
} }
// setSynced sets the synced flag and notifies the light server if present
func (pm *ProtocolManager) setSynced() {
if atomic.SwapUint32(&pm.synced, 1) == 0 && pm.lesServer != nil {
pm.lesServer.Synced()
}
}

View file

@ -25,6 +25,7 @@ import (
"regexp" "regexp"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -41,6 +42,10 @@ import (
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
) )
// historyUpdateRange is the number of blocks a node should report upon login or
// history request.
const historyUpdateRange = 50
// Service implements an Ethereum netstats reporting daemon that pushes local // Service implements an Ethereum netstats reporting daemon that pushes local
// chain statistics up to a monitoring server. // chain statistics up to a monitoring server.
type Service struct { type Service struct {
@ -53,6 +58,9 @@ type Service struct {
node string // Name of the node to display on the monitoring page node string // Name of the node to display on the monitoring page
pass string // Password to authorize access to the monitoring page pass string // Password to authorize access to the monitoring page
host string // Remote address of the monitoring service host string // Remote address of the monitoring service
pongCh chan struct{} // Pong notifications are fed into this channel
histCh chan []uint64 // History request block numbers are fed into this channel
} }
// New returns a monitoring service ready for stats reporting. // New returns a monitoring service ready for stats reporting.
@ -70,6 +78,8 @@ func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Servic
node: parts[1], node: parts[1],
pass: parts[3], pass: parts[3],
host: parts[4], host: parts[4],
pongCh: make(chan struct{}),
histCh: make(chan []uint64, 1),
}, nil }, nil
} }
@ -115,7 +125,11 @@ func (s *Service) loop() {
// Loop reporting until termination // Loop reporting until termination
for { for {
// Establish a websocket connection to the server and authenticate the node // Establish a websocket connection to the server and authenticate the node
conn, err := websocket.Dial(fmt.Sprintf("wss://%s/api", s.host), "", "http://localhost/") url := fmt.Sprintf("%s/api", s.host)
if !strings.Contains(url, "://") {
url = "wss://" + url
}
conn, err := websocket.Dial(url, "", "http://localhost/")
if err != nil { if err != nil {
glog.V(logger.Warn).Infof("Stats server unreachable: %v", err) glog.V(logger.Warn).Infof("Stats server unreachable: %v", err)
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
@ -130,22 +144,34 @@ func (s *Service) loop() {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
continue continue
} }
if err = s.report(in, out); err != nil { go s.readLoop(conn, in)
// Send the initial stats so our node looks decent from the get go
if err = s.report(out); err != nil {
glog.V(logger.Warn).Infof("Initial stats report failed: %v", err) glog.V(logger.Warn).Infof("Initial stats report failed: %v", err)
conn.Close() conn.Close()
continue continue
} }
if err = s.reportHistory(out, nil); err != nil {
glog.V(logger.Warn).Infof("History report failed: %v", err)
conn.Close()
continue
}
// Keep sending status updates until the connection breaks // Keep sending status updates until the connection breaks
fullReport := time.NewTicker(15 * time.Second) fullReport := time.NewTicker(15 * time.Second)
for err == nil { for err == nil {
select { select {
case <-fullReport.C: case <-fullReport.C:
if err = s.report(in, out); err != nil { if err = s.report(out); err != nil {
glog.V(logger.Warn).Infof("Full stats report failed: %v", err) glog.V(logger.Warn).Infof("Full stats report failed: %v", err)
} }
case head := <-headSub.Chan(): case list := <-s.histCh:
if head == nil { // node stopped if err = s.reportHistory(out, list); err != nil {
glog.V(logger.Warn).Infof("Block history report failed: %v", err)
}
case head, ok := <-headSub.Chan():
if !ok { // node stopped
conn.Close() conn.Close()
return return
} }
@ -155,8 +181,8 @@ func (s *Service) loop() {
if err = s.reportPending(out); err != nil { if err = s.reportPending(out); err != nil {
glog.V(logger.Warn).Infof("Post-block transaction stats report failed: %v", err) glog.V(logger.Warn).Infof("Post-block transaction stats report failed: %v", err)
} }
case ev := <-txSub.Chan(): case _, ok := <-txSub.Chan():
if ev == nil { // node stopped if !ok { // node stopped
conn.Close() conn.Close()
return return
} }
@ -178,6 +204,76 @@ func (s *Service) loop() {
} }
} }
// readLoop loops as long as the connection is alive and retrieves data packets
// from the network socket. If any of them match an active request, it forwards
// it, if they themselves are requests it initiates a reply, and lastly it drops
// unknown packets.
func (s *Service) readLoop(conn *websocket.Conn, in *json.Decoder) {
// If the read loop exists, close the connection
defer conn.Close()
for {
// Retrieve the next generic network packet and bail out on error
var msg map[string][]interface{}
if err := in.Decode(&msg); err != nil {
glog.V(logger.Warn).Infof("Failed to decode stats server message: %v", err)
return
}
if len(msg["emit"]) == 0 {
glog.V(logger.Warn).Infof("Stats server sent non-broadcast: %v", msg)
return
}
command, ok := msg["emit"][0].(string)
if !ok {
glog.V(logger.Warn).Infof("Invalid stats server message type: %v", msg["emit"][0])
return
}
// If the message is a ping reply, deliver (someone must be listening!)
if len(msg["emit"]) == 2 && command == "node-pong" {
select {
case s.pongCh <- struct{}{}:
// Pong delivered, continue listening
continue
default:
// Ping routine dead, abort
glog.V(logger.Warn).Infof("Stats server pinger seems to have died")
return
}
}
// If the message is a history request, forward to the event processor
if len(msg["emit"]) == 2 && command == "history" {
// Make sure the request is valid and doesn't crash us
request, ok := msg["emit"][1].(map[string]interface{})
if !ok {
glog.V(logger.Warn).Infof("Invalid history request: %v", msg["emit"][1])
return
}
list, ok := request["list"].([]interface{})
if !ok {
glog.V(logger.Warn).Infof("Invalid history block list: %v", request["list"])
return
}
// Convert the block number list to an integer list
numbers := make([]uint64, len(list))
for i, num := range list {
n, ok := num.(float64)
if !ok {
glog.V(logger.Warn).Infof("Invalid history block number: %v", num)
return
}
numbers[i] = uint64(n)
}
select {
case s.histCh <- numbers:
continue
default:
}
}
// Report anything else and continue
glog.V(logger.Info).Infof("Unknown stats message: %v", msg)
}
}
// nodeInfo is the collection of metainformation about a node that is displayed // nodeInfo is the collection of metainformation about a node that is displayed
// on the monitoring page. // on the monitoring page.
type nodeInfo struct { type nodeInfo struct {
@ -190,6 +286,7 @@ type nodeInfo struct {
Os string `json:"os"` Os string `json:"os"`
OsVer string `json:"os_v"` OsVer string `json:"os_v"`
Client string `json:"client"` Client string `json:"client"`
History bool `json:"canUpdateHistory"`
} }
// authMsg is the authentication infos needed to login to a monitoring server. // authMsg is the authentication infos needed to login to a monitoring server.
@ -224,6 +321,7 @@ func (s *Service) login(in *json.Decoder, out *json.Encoder) error {
Os: runtime.GOOS, Os: runtime.GOOS,
OsVer: runtime.GOARCH, OsVer: runtime.GOARCH,
Client: "0.1.1", Client: "0.1.1",
History: true,
}, },
Secret: s.pass, Secret: s.pass,
} }
@ -244,8 +342,8 @@ func (s *Service) login(in *json.Decoder, out *json.Encoder) error {
// report collects all possible data to report and send it to the stats server. // report collects all possible data to report and send it to the stats server.
// This should only be used on reconnects or rarely to avoid overloading the // This should only be used on reconnects or rarely to avoid overloading the
// server. Use the individual methods for reporting subscribed events. // server. Use the individual methods for reporting subscribed events.
func (s *Service) report(in *json.Decoder, out *json.Encoder) error { func (s *Service) report(out *json.Encoder) error {
if err := s.reportLatency(in, out); err != nil { if err := s.reportLatency(out); err != nil {
return err return err
} }
if err := s.reportBlock(out, nil); err != nil { if err := s.reportBlock(out, nil); err != nil {
@ -262,7 +360,7 @@ func (s *Service) report(in *json.Decoder, out *json.Encoder) error {
// reportLatency sends a ping request to the server, measures the RTT time and // reportLatency sends a ping request to the server, measures the RTT time and
// finally sends a latency update. // finally sends a latency update.
func (s *Service) reportLatency(in *json.Decoder, out *json.Encoder) error { func (s *Service) reportLatency(out *json.Encoder) error {
// Send the current time to the ethstats server // Send the current time to the ethstats server
start := time.Now() start := time.Now()
@ -276,9 +374,12 @@ func (s *Service) reportLatency(in *json.Decoder, out *json.Encoder) error {
return err return err
} }
// Wait for the pong request to arrive back // Wait for the pong request to arrive back
var pong map[string][]interface{} select {
if err := in.Decode(&pong); err != nil || len(pong["emit"]) != 2 || pong["emit"][0].(string) != "node-pong" { case <-s.pongCh:
return errors.New("unexpected ping reply") // Pong delivered, report the latency
case <-time.After(3 * time.Second):
// Ping timeout, abort
return errors.New("ping timed out")
} }
// Send back the measured latency // Send back the measured latency
latency := map[string][]interface{}{ latency := map[string][]interface{}{
@ -297,6 +398,7 @@ func (s *Service) reportLatency(in *json.Decoder, out *json.Encoder) error {
type blockStats struct { type blockStats struct {
Number *big.Int `json:"number"` Number *big.Int `json:"number"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
Timestamp *big.Int `json:"timestamp"`
Miner common.Address `json:"miner"` Miner common.Address `json:"miner"`
GasUsed *big.Int `json:"gasUsed"` GasUsed *big.Int `json:"gasUsed"`
GasLimit *big.Int `json:"gasLimit"` GasLimit *big.Int `json:"gasLimit"`
@ -330,9 +432,26 @@ func (s uncleStats) MarshalJSON() ([]byte, error) {
// reportBlock retrieves the current chain head and repors it to the stats server. // reportBlock retrieves the current chain head and repors it to the stats server.
func (s *Service) reportBlock(out *json.Encoder, block *types.Block) error { func (s *Service) reportBlock(out *json.Encoder, block *types.Block) error {
// Gather the head block infos from the local blockchain // Assemble the block stats report and send it to the server
stats := map[string]interface{}{
"id": s.node,
"block": s.assembleBlockStats(block),
}
report := map[string][]interface{}{
"emit": []interface{}{"block", stats},
}
if err := out.Encode(report); err != nil {
return err
}
return nil
}
// assembleBlockStats retrieves any required metadata to report a single block
// and assembles the block stats. If block is nil, the current head is processed.
func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
// Gather the block infos from the local blockchain
var ( var (
head *types.Header header *types.Header
td *big.Int td *big.Int
txs []*types.Transaction txs []*types.Transaction
uncles []*types.Header uncles []*types.Header
@ -342,37 +461,77 @@ func (s *Service) reportBlock(out *json.Encoder, block *types.Block) error {
if block == nil { if block == nil {
block = s.eth.BlockChain().CurrentBlock() block = s.eth.BlockChain().CurrentBlock()
} }
head = block.Header() header = block.Header()
td = s.eth.BlockChain().GetTd(head.Hash(), head.Number.Uint64()) td = s.eth.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
txs = block.Transactions() txs = block.Transactions()
uncles = block.Uncles() uncles = block.Uncles()
} else { } else {
// Light nodes would need on-demand lookups for transactions/uncles, skip // Light nodes would need on-demand lookups for transactions/uncles, skip
if block != nil { if block != nil {
head = block.Header() header = block.Header()
} else { } else {
head = s.les.BlockChain().CurrentHeader() header = s.les.BlockChain().CurrentHeader()
} }
td = s.les.BlockChain().GetTd(head.Hash(), head.Number.Uint64()) td = s.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
} }
// Assemble the block stats report and send it to the server // Assemble and return the block stats
stats := map[string]interface{}{ return &blockStats{
"id": s.node, Number: header.Number,
"block": &blockStats{ Hash: header.Hash(),
Number: head.Number, Timestamp: header.Time,
Hash: head.Hash(), Miner: header.Coinbase,
Miner: head.Coinbase, GasUsed: new(big.Int).Set(header.GasUsed),
GasUsed: new(big.Int).Set(head.GasUsed), GasLimit: new(big.Int).Set(header.GasLimit),
GasLimit: new(big.Int).Set(head.GasLimit), Diff: header.Difficulty.String(),
Diff: head.Difficulty.String(),
TotalDiff: td.String(), TotalDiff: td.String(),
Txs: txs, Txs: txs,
Uncles: uncles, Uncles: uncles,
}, }
}
// reportHistory retrieves the most recent batch of blocks and reports it to the
// stats server.
func (s *Service) reportHistory(out *json.Encoder, list []uint64) error {
// Figure out the indexes that need reporting
indexes := make([]uint64, 0, historyUpdateRange)
if len(list) > 0 {
// Specific indexes requested, send them back in particular
for _, idx := range list {
indexes = append(indexes, idx)
}
} else {
// No indexes requested, send back the top ones
var head *types.Header
if s.eth != nil {
head = s.eth.BlockChain().CurrentHeader()
} else {
head = s.les.BlockChain().CurrentHeader()
}
start := head.Number.Int64() - historyUpdateRange
if start < 0 {
start = 0
}
for i := uint64(start); i <= head.Number.Uint64(); i++ {
indexes = append(indexes, i)
}
}
// Gather the batch of blocks to report
history := make([]*blockStats, len(indexes))
for i, number := range indexes {
if s.eth != nil {
history[i] = s.assembleBlockStats(s.eth.BlockChain().GetBlockByNumber(number))
} else {
history[i] = s.assembleBlockStats(types.NewBlockWithHeader(s.les.BlockChain().GetHeaderByNumber(number)))
}
}
// Assemble the history report and send it to the server
stats := map[string]interface{}{
"id": s.node,
"history": history,
} }
report := map[string][]interface{}{ report := map[string][]interface{}{
"emit": []interface{}{"block", stats}, "emit": []interface{}{"history", stats},
} }
if err := out.Encode(report); err != nil { if err := out.Encode(report); err != nil {
return err return err

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/ethash" "github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
@ -289,14 +290,14 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs
} }
// signHash is a helper function that calculates a hash for the given message that can be // signHash is a helper function that calculates a hash for the given message that can be
// safely used to calculate a signature from. The hash is calulcated with: // safely used to calculate a signature from.
//
// The hash is calulcated as
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). // keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
func signHash(message string) []byte { //
data := common.FromHex(message) // This gives context to the signed message and prevents signing of transactions.
// Give context to the signed message. This prevents an adversery to sign a tx. func signHash(data []byte) []byte {
// It has no cryptographic purpose.
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
// Always hash, this prevents choosen plaintext attacks that can extract key information
return crypto.Keccak256([]byte(msg)) return crypto.Keccak256([]byte(msg))
} }
@ -306,13 +307,8 @@ func signHash(message string) []byte {
// The key used to calculate the signature is decrypted with the given password. // The key used to calculate the signature is decrypted with the given password.
// //
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (s *PrivateAccountAPI) Sign(ctx context.Context, message string, addr common.Address, passwd string) (string, error) { func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
hash := signHash(message) return s.b.AccountManager().SignWithPassphrase(addr, passwd, signHash(data))
signature, err := s.b.AccountManager().SignWithPassphrase(addr, passwd, hash)
if err != nil {
return "0x", err
}
return common.ToHex(signature), nil
} }
// EcRecover returns the address for the account that was used to create the signature. // EcRecover returns the address for the account that was used to create the signature.
@ -322,29 +318,20 @@ func (s *PrivateAccountAPI) Sign(ctx context.Context, message string, addr commo
// addr = ecrecover(hash, signature) // addr = ecrecover(hash, signature)
// //
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover
func (s *PrivateAccountAPI) EcRecover(ctx context.Context, message string, signature string) (common.Address, error) { func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
var (
hash = signHash(message)
sig = common.FromHex(signature)
)
if len(sig) != 65 { if len(sig) != 65 {
return common.Address{}, fmt.Errorf("signature must be 65 bytes long") return common.Address{}, fmt.Errorf("signature must be 65 bytes long")
} }
// see crypto.Ecrecover description // see crypto.Ecrecover description
if sig[64] == 27 || sig[64] == 28 { if sig[64] == 27 || sig[64] == 28 {
sig[64] -= 27 sig[64] -= 27
} }
rpk, err := crypto.Ecrecover(signHash(data), sig)
rpk, err := crypto.Ecrecover(hash, sig)
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
pubKey := crypto.ToECDSAPub(rpk) pubKey := crypto.ToECDSAPub(rpk)
recoveredAddr := crypto.PubkeyToAddress(*pubKey) recoveredAddr := crypto.PubkeyToAddress(*pubKey)
return recoveredAddr, nil return recoveredAddr, nil
} }
@ -1116,10 +1103,8 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
// The account associated with addr must be unlocked. // The account associated with addr must be unlocked.
// //
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
func (s *PublicTransactionPoolAPI) Sign(addr common.Address, message string) (string, error) { func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
hash := signHash(message) return s.b.AccountManager().SignEthereum(addr, signHash(data))
signature, err := s.b.AccountManager().SignEthereum(addr, hash)
return common.ToHex(signature), err
} }
// SignTransactionArgs represents the arguments to sign a transaction. // SignTransactionArgs represents the arguments to sign a transaction.
@ -1273,8 +1258,12 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sig
// PendingTransactions returns the transactions that are in the transaction pool and have a from address that is one of // PendingTransactions returns the transactions that are in the transaction pool and have a from address that is one of
// the accounts this node manages. // the accounts this node manages.
func (s *PublicTransactionPoolAPI) PendingTransactions() []*RPCTransaction { func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) {
pending := s.b.GetPoolTransactions() pending, err := s.b.GetPoolTransactions()
if err != nil {
return nil, err
}
transactions := make([]*RPCTransaction, 0, len(pending)) transactions := make([]*RPCTransaction, 0, len(pending))
for _, tx := range pending { for _, tx := range pending {
var signer types.Signer = types.HomesteadSigner{} var signer types.Signer = types.HomesteadSigner{}
@ -1286,13 +1275,17 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() []*RPCTransaction {
transactions = append(transactions, newRPCPendingTransaction(tx)) transactions = append(transactions, newRPCPendingTransaction(tx))
} }
} }
return transactions return transactions, nil
} }
// Resend accepts an existing transaction and a new gas price and limit. It will remove the given transaction from the // Resend accepts an existing transaction and a new gas price and limit. It will remove the given transaction from the
// pool and reinsert it with the new gas price and limit. // pool and reinsert it with the new gas price and limit.
func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, tx Tx, gasPrice, gasLimit *rpc.HexNumber) (common.Hash, error) { func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, tx Tx, gasPrice, gasLimit *rpc.HexNumber) (common.Hash, error) {
pending := s.b.GetPoolTransactions() pending, err := s.b.GetPoolTransactions()
if err != nil {
return common.Hash{}, err
}
for _, p := range pending { for _, p := range pending {
var signer types.Signer = types.HomesteadSigner{} var signer types.Signer = types.HomesteadSigner{}
if p.Protected() { if p.Protected() {

View file

@ -55,7 +55,7 @@ type Backend interface {
// TxPool API // TxPool API
SendTx(ctx context.Context, signedTx *types.Transaction) error SendTx(ctx context.Context, signedTx *types.Transaction) error
RemoveTx(txHash common.Hash) RemoveTx(txHash common.Hash)
GetPoolTransactions() types.Transactions GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction GetPoolTransaction(txHash common.Hash) *types.Transaction
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)

View file

@ -110,7 +110,7 @@ func (b *LesApiBackend) RemoveTx(txHash common.Hash) {
b.eth.txPool.RemoveTx(txHash) b.eth.txPool.RemoveTx(txHash)
} }
func (b *LesApiBackend) GetPoolTransactions() types.Transactions { func (b *LesApiBackend) GetPoolTransactions() (types.Transactions, error) {
return b.eth.txPool.GetTransactions() return b.eth.txPool.GetTransactions()
} }

View file

@ -23,135 +23,374 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
) )
const (
blockDelayTimeout = time.Second * 10 // timeout for a peer to announce a head that has already been confirmed by others
maxNodeCount = 20 // maximum number of fetcherTreeNode entries remembered for each peer
)
// lightFetcher
type lightFetcher struct { type lightFetcher struct {
pm *ProtocolManager pm *ProtocolManager
odr *LesOdr odr *LesOdr
chain BlockChain chain *light.LightChain
headAnnouncedMu sync.Mutex maxConfirmedTd *big.Int
headAnnouncedBy map[common.Hash][]*peer peers map[*peer]*fetcherPeerInfo
currentTd *big.Int lastUpdateStats *updateStatsEntry
lock sync.Mutex // qwerqwerqwe
deliverChn chan fetchResponse deliverChn chan fetchResponse
reqMu sync.RWMutex reqMu sync.RWMutex
requested map[uint64]fetchRequest requested map[uint64]fetchRequest
timeoutChn chan uint64 timeoutChn chan uint64
notifyChn chan bool // true if initiated from outside requestChn chan bool // true if initiated from outside
syncing bool syncing bool
syncDone chan struct{} syncDone chan *peer
} }
// fetcherPeerInfo holds fetcher-specific information about each active peer
type fetcherPeerInfo struct {
root, lastAnnounced *fetcherTreeNode
nodeCnt int
confirmedTd *big.Int
bestConfirmed *fetcherTreeNode
nodeByHash map[common.Hash]*fetcherTreeNode
firstUpdateStats *updateStatsEntry
}
// fetcherTreeNode is a node of a tree that holds information about blocks recently
// announced and confirmed by a certain peer. Each new announce message from a peer
// adds nodes to the tree, based on the previous announced head and the reorg depth.
// There are three possible states for a tree node:
// - announced: not downloaded (known) yet, but we know its head, number and td
// - intermediate: not known, hash and td are empty, they are filled out when it becomes known
// - known: both announced by this peer and downloaded (from any peer).
// This structure makes it possible to always know which peer has a certain block,
// which is necessary for selecting a suitable peer for ODR requests and also for
// canonizing new heads. It also helps to always download the minimum necessary
// amount of headers with a single request.
type fetcherTreeNode struct {
hash common.Hash
number uint64
td *big.Int
known, requested bool
parent *fetcherTreeNode
children []*fetcherTreeNode
}
// fetchRequest represents a header download request
type fetchRequest struct { type fetchRequest struct {
hash common.Hash hash common.Hash
amount uint64 amount uint64
peer *peer peer *peer
sent mclock.AbsTime
timeout bool
} }
// fetchResponse represents a header download response
type fetchResponse struct { type fetchResponse struct {
reqID uint64 reqID uint64
headers []*types.Header headers []*types.Header
peer *peer
} }
// newLightFetcher creates a new light fetcher
func newLightFetcher(pm *ProtocolManager) *lightFetcher { func newLightFetcher(pm *ProtocolManager) *lightFetcher {
f := &lightFetcher{ f := &lightFetcher{
pm: pm, pm: pm,
chain: pm.blockchain, chain: pm.blockchain.(*light.LightChain),
odr: pm.odr, odr: pm.odr,
headAnnouncedBy: make(map[common.Hash][]*peer), peers: make(map[*peer]*fetcherPeerInfo),
deliverChn: make(chan fetchResponse, 100), deliverChn: make(chan fetchResponse, 100),
requested: make(map[uint64]fetchRequest), requested: make(map[uint64]fetchRequest),
timeoutChn: make(chan uint64), timeoutChn: make(chan uint64),
notifyChn: make(chan bool, 100), requestChn: make(chan bool, 100),
syncDone: make(chan struct{}), syncDone: make(chan *peer),
currentTd: big.NewInt(0), maxConfirmedTd: big.NewInt(0),
} }
go f.syncLoop() go f.syncLoop()
return f return f
} }
func (f *lightFetcher) notify(p *peer, head *announceData) { // syncLoop is the main event loop of the light fetcher
var headHash common.Hash func (f *lightFetcher) syncLoop() {
if head == nil { f.pm.wg.Add(1)
// initial notify defer f.pm.wg.Done()
headHash = p.Head()
} else { requestStarted := false
if core.GetTd(f.pm.chainDb, head.Hash, head.Number) != nil { for {
head.haveHeaders = head.Number select {
case <-f.pm.quitSync:
return
// when a new announce is received, request loop keeps running until
// no further requests are necessary or possible
case newAnnounce := <-f.requestChn:
f.lock.Lock()
s := requestStarted
requestStarted = false
if !f.syncing && !(newAnnounce && s) {
if peer, node, amount := f.nextRequest(); node != nil {
requestStarted = true
reqID, started := f.request(peer, node, amount)
if started {
go func() {
time.Sleep(softRequestTimeout)
f.reqMu.Lock()
req, ok := f.requested[reqID]
if ok {
req.timeout = true
f.requested[reqID] = req
} }
//fmt.Println("notify", p.id, head.Number, head.ReorgDepth, head.haveHeaders) f.reqMu.Unlock()
if !p.addNotify(head) { // keep starting new requests while possible
//fmt.Println("addNotify fail") f.requestChn <- false
f.pm.removePeer(p.id) }()
}
}
}
f.lock.Unlock()
case reqID := <-f.timeoutChn:
f.reqMu.Lock()
req, ok := f.requested[reqID]
if ok {
delete(f.requested, reqID)
}
f.reqMu.Unlock()
if ok {
f.pm.serverPool.adjustResponseTime(req.peer.poolEntry, time.Duration(mclock.Now()-req.sent), true)
glog.V(logger.Debug).Infof("hard timeout by peer %v", req.peer.id)
go f.pm.removePeer(req.peer.id)
}
case resp := <-f.deliverChn:
f.reqMu.Lock()
req, ok := f.requested[resp.reqID]
if ok && req.peer != resp.peer {
ok = false
}
if ok {
delete(f.requested, resp.reqID)
}
f.reqMu.Unlock()
if ok {
f.pm.serverPool.adjustResponseTime(req.peer.poolEntry, time.Duration(mclock.Now()-req.sent), req.timeout)
}
f.lock.Lock()
if !ok || !(f.syncing || f.processResponse(req, resp)) {
glog.V(logger.Debug).Infof("failed processing response by peer %v", resp.peer.id)
go f.pm.removePeer(resp.peer.id)
}
f.lock.Unlock()
case p := <-f.syncDone:
f.lock.Lock()
glog.V(logger.Debug).Infof("done synchronising with peer %v", p.id)
f.checkSyncedHeaders(p)
f.syncing = false
f.lock.Unlock()
} }
headHash = head.Hash
} }
f.headAnnouncedMu.Lock()
f.headAnnouncedBy[headHash] = append(f.headAnnouncedBy[headHash], p)
f.headAnnouncedMu.Unlock()
f.notifyChn <- true
} }
func (f *lightFetcher) gotHeader(header *types.Header) { // addPeer adds a new peer to the fetcher's peer set
f.headAnnouncedMu.Lock() func (f *lightFetcher) addPeer(p *peer) {
defer f.headAnnouncedMu.Unlock() p.lock.Lock()
p.hasBlock = func(hash common.Hash, number uint64) bool {
return f.peerHasBlock(p, hash, number)
}
p.lock.Unlock()
hash := header.Hash() f.lock.Lock()
peerList := f.headAnnouncedBy[hash] defer f.lock.Unlock()
if peerList == nil {
f.peers[p] = &fetcherPeerInfo{nodeByHash: make(map[common.Hash]*fetcherTreeNode)}
}
// removePeer removes a new peer from the fetcher's peer set
func (f *lightFetcher) removePeer(p *peer) {
p.lock.Lock()
p.hasBlock = nil
p.lock.Unlock()
f.lock.Lock()
defer f.lock.Unlock()
// check for potential timed out block delay statistics
f.checkUpdateStats(p, nil)
delete(f.peers, p)
}
// announce processes a new announcement message received from a peer, adding new
// nodes to the peer's block tree and removing old nodes if necessary
func (f *lightFetcher) announce(p *peer, head *announceData) {
f.lock.Lock()
defer f.lock.Unlock()
glog.V(logger.Debug).Infof("received announce from peer %v #%d %016x reorg: %d", p.id, head.Number, head.Hash[:8], head.ReorgDepth)
fp := f.peers[p]
if fp == nil {
glog.V(logger.Debug).Infof("announce: unknown peer")
return return
} }
number := header.Number.Uint64()
td := core.GetTd(f.pm.chainDb, hash, number) if fp.lastAnnounced != nil && head.Td.Cmp(fp.lastAnnounced.td) <= 0 {
for _, peer := range peerList { // announced tds should be strictly monotonic
peer.lock.Lock() glog.V(logger.Debug).Infof("non-monotonic Td from peer %v", p.id)
ok := peer.gotHeader(hash, number, td) go f.pm.removePeer(p.id)
peer.lock.Unlock() return
if !ok { }
//fmt.Println("gotHeader fail")
f.pm.removePeer(peer.id) n := fp.lastAnnounced
for i := uint64(0); i < head.ReorgDepth; i++ {
if n == nil {
break
}
n = n.parent
}
if n != nil {
// n is now the reorg common ancestor, add a new branch of nodes
// check if the node count is too high to add new nodes
locked := false
for uint64(fp.nodeCnt)+head.Number-n.number > maxNodeCount && fp.root != nil {
if !locked {
f.chain.LockChain()
defer f.chain.UnlockChain()
locked = true
}
// if one of root's children is canonical, keep it, delete other branches and root itself
var newRoot *fetcherTreeNode
for i, nn := range fp.root.children {
if core.GetCanonicalHash(f.pm.chainDb, nn.number) == nn.hash {
fp.root.children = append(fp.root.children[:i], fp.root.children[i+1:]...)
nn.parent = nil
newRoot = nn
break
} }
} }
delete(f.headAnnouncedBy, hash) fp.deleteNode(fp.root)
if n == fp.root {
n = newRoot
}
fp.root = newRoot
if newRoot == nil || !f.checkKnownNode(p, newRoot) {
fp.bestConfirmed = nil
fp.confirmedTd = nil
}
if n == nil {
break
}
}
if n != nil {
for n.number < head.Number {
nn := &fetcherTreeNode{number: n.number + 1, parent: n}
n.children = append(n.children, nn)
n = nn
fp.nodeCnt++
}
n.hash = head.Hash
n.td = head.Td
fp.nodeByHash[n.hash] = n
}
}
if n == nil {
// could not find reorg common ancestor or had to delete entire tree, a new root and a resync is needed
if fp.root != nil {
fp.deleteNode(fp.root)
}
n = &fetcherTreeNode{hash: head.Hash, number: head.Number, td: head.Td}
fp.root = n
fp.nodeCnt++
fp.nodeByHash[n.hash] = n
fp.bestConfirmed = nil
fp.confirmedTd = nil
}
f.checkKnownNode(p, n)
p.lock.Lock()
p.headInfo = head
fp.lastAnnounced = n
p.lock.Unlock()
f.checkUpdateStats(p, nil)
f.requestChn <- true
} }
func (f *lightFetcher) nextRequest() (*peer, *announceData) { // peerHasBlock returns true if we can assume the peer knows the given block
var bestPeer *peer // based on its announcements
bestTd := f.currentTd func (f *lightFetcher) peerHasBlock(p *peer, hash common.Hash, number uint64) bool {
for _, peer := range f.pm.peers.AllPeers() { f.lock.Lock()
peer.lock.RLock() defer f.lock.Unlock()
if !peer.headInfo.requested && (peer.headInfo.Td.Cmp(bestTd) > 0 ||
(bestPeer != nil && peer.headInfo.Td.Cmp(bestTd) == 0 && peer.headInfo.haveHeaders > bestPeer.headInfo.haveHeaders)) { fp := f.peers[p]
bestPeer = peer if fp == nil || fp.root == nil {
bestTd = peer.headInfo.Td return false
} }
peer.lock.RUnlock()
if number >= fp.root.number {
// it is recent enough that if it is known, is should be in the peer's block tree
return fp.nodeByHash[hash] != nil
} }
if bestPeer == nil { f.chain.LockChain()
return nil, nil defer f.chain.UnlockChain()
} // if it's older than the peer's block tree root but it's in the same canonical chain
bestPeer.lock.Lock() // than the root, we can still be sure the peer knows it
res := bestPeer.headInfo return core.GetCanonicalHash(f.pm.chainDb, fp.root.number) == fp.root.hash && core.GetCanonicalHash(f.pm.chainDb, number) == hash
res.requested = true
bestPeer.lock.Unlock()
for _, peer := range f.pm.peers.AllPeers() {
if peer != bestPeer {
peer.lock.Lock()
if peer.headInfo.Hash == bestPeer.headInfo.Hash && peer.headInfo.haveHeaders == bestPeer.headInfo.haveHeaders {
peer.headInfo.requested = true
}
peer.lock.Unlock()
}
}
return bestPeer, res
} }
func (f *lightFetcher) deliverHeaders(reqID uint64, headers []*types.Header) { // request initiates a header download request from a certain peer
f.deliverChn <- fetchResponse{reqID: reqID, headers: headers} func (f *lightFetcher) request(p *peer, n *fetcherTreeNode, amount uint64) (uint64, bool) {
fp := f.peers[p]
if fp == nil {
glog.V(logger.Debug).Infof("request: unknown peer")
return 0, false
}
if fp.bestConfirmed == nil || fp.root == nil || !f.checkKnownNode(p, fp.root) {
f.syncing = true
go func() {
glog.V(logger.Debug).Infof("synchronising with peer %v", p.id)
f.pm.synchronise(p)
f.syncDone <- p
}()
return 0, false
}
reqID := getNextReqID()
n.requested = true
cost := p.GetRequestCost(GetBlockHeadersMsg, int(amount))
p.fcServer.SendRequest(reqID, cost)
f.reqMu.Lock()
f.requested[reqID] = fetchRequest{hash: n.hash, amount: amount, peer: p, sent: mclock.Now()}
f.reqMu.Unlock()
go p.RequestHeadersByHash(reqID, cost, n.hash, int(amount), 0, true)
go func() {
time.Sleep(hardRequestTimeout)
f.timeoutChn <- reqID
}()
return reqID, true
} }
// requestAmount calculates the amount of headers to be downloaded starting
// from a certain head backwards
func (f *lightFetcher) requestAmount(p *peer, n *fetcherTreeNode) uint64 {
amount := uint64(0)
nn := n
for nn != nil && !f.checkKnownNode(p, nn) {
nn = nn.parent
amount++
}
if nn == nil {
amount = n.number
}
return amount
}
// requestedID tells if a certain reqID has been requested by the fetcher
func (f *lightFetcher) requestedID(reqID uint64) bool { func (f *lightFetcher) requestedID(reqID uint64) bool {
f.reqMu.RLock() f.reqMu.RLock()
_, ok := f.requested[reqID] _, ok := f.requested[reqID]
@ -159,36 +398,51 @@ func (f *lightFetcher) requestedID(reqID uint64) bool {
return ok return ok
} }
func (f *lightFetcher) request(p *peer, block *announceData) { // nextRequest selects the peer and announced head to be requested next, amount
//fmt.Println("request", p.id, block.Number, block.haveHeaders) // to be downloaded starting from the head backwards is also returned
amount := block.Number - block.haveHeaders func (f *lightFetcher) nextRequest() (*peer, *fetcherTreeNode, uint64) {
if amount == 0 { var (
return bestHash common.Hash
bestAmount uint64
)
bestTd := f.maxConfirmedTd
for p, fp := range f.peers {
for hash, n := range fp.nodeByHash {
if !f.checkKnownNode(p, n) && !n.requested && (bestTd == nil || n.td.Cmp(bestTd) >= 0) {
amount := f.requestAmount(p, n)
if bestTd == nil || n.td.Cmp(bestTd) > 0 || amount < bestAmount {
bestHash = hash
bestAmount = amount
bestTd = n.td
} }
if amount > 100 { }
f.syncing = true }
go func() { }
//fmt.Println("f.pm.synchronise(p)") if bestTd == f.maxConfirmedTd {
f.pm.synchronise(p) return nil, nil, 0
//fmt.Println("sync done")
f.syncDone <- struct{}{}
}()
return
} }
reqID := f.odr.getNextReqID() peer := f.pm.serverPool.selectPeer(func(p *peer) (bool, uint64) {
f.reqMu.Lock() fp := f.peers[p]
f.requested[reqID] = fetchRequest{hash: block.Hash, amount: amount, peer: p} if fp == nil || fp.nodeByHash[bestHash] == nil {
f.reqMu.Unlock() return false, 0
cost := p.GetRequestCost(GetBlockHeadersMsg, int(amount)) }
p.fcServer.SendRequest(reqID, cost) return true, p.fcServer.CanSend(p.GetRequestCost(GetBlockHeadersMsg, int(bestAmount)))
go p.RequestHeadersByHash(reqID, cost, block.Hash, int(amount), 0, true) })
go func() { var node *fetcherTreeNode
time.Sleep(hardRequestTimeout) if peer != nil {
f.timeoutChn <- reqID node = f.peers[peer].nodeByHash[bestHash]
}() }
return peer, node, bestAmount
} }
// deliverHeaders delivers header download request responses for processing
func (f *lightFetcher) deliverHeaders(peer *peer, reqID uint64, headers []*types.Header) {
f.deliverChn <- fetchResponse{reqID: reqID, headers: headers, peer: peer}
}
// processResponse processes header download request responses
func (f *lightFetcher) processResponse(req fetchRequest, resp fetchResponse) bool { func (f *lightFetcher) processResponse(req fetchRequest, resp fetchResponse) bool {
if uint64(len(resp.headers)) != req.amount || resp.headers[0].Hash() != req.hash { if uint64(len(resp.headers)) != req.amount || resp.headers[0].Hash() != req.hash {
return false return false
@ -200,96 +454,248 @@ func (f *lightFetcher) processResponse(req fetchRequest, resp fetchResponse) boo
if _, err := f.chain.InsertHeaderChain(headers, 1); err != nil { if _, err := f.chain.InsertHeaderChain(headers, 1); err != nil {
return false return false
} }
for _, header := range headers { tds := make([]*big.Int, len(headers))
td := core.GetTd(f.pm.chainDb, header.Hash(), header.Number.Uint64()) for i, header := range headers {
td := f.chain.GetTd(header.Hash(), header.Number.Uint64())
if td == nil { if td == nil {
return false return false
} }
if td.Cmp(f.currentTd) > 0 { tds[i] = td
f.currentTd = td
}
f.gotHeader(header)
} }
f.newHeaders(headers, tds)
return true return true
} }
func (f *lightFetcher) checkSyncedHeaders() { // newHeaders updates the block trees of all active peers according to a newly
//fmt.Println("checkSyncedHeaders()") // downloaded and validated batch or headers
for _, peer := range f.pm.peers.AllPeers() { func (f *lightFetcher) newHeaders(headers []*types.Header, tds []*big.Int) {
peer.lock.Lock() var maxTd *big.Int
h := peer.firstHeadInfo for p, fp := range f.peers {
remove := false if !f.checkAnnouncedHeaders(fp, headers, tds) {
loop: glog.V(logger.Debug).Infof("announce inconsistency by peer %v", p.id)
for h != nil { go f.pm.removePeer(p.id)
if td := core.GetTd(f.pm.chainDb, h.Hash, h.Number); td != nil {
//fmt.Println(" found", h.Number)
ok := peer.gotHeader(h.Hash, h.Number, td)
if !ok {
remove = true
break loop
} }
if td.Cmp(f.currentTd) > 0 { if fp.confirmedTd != nil && (maxTd == nil || maxTd.Cmp(fp.confirmedTd) > 0) {
f.currentTd = td maxTd = fp.confirmedTd
} }
} }
h = h.next if maxTd != nil {
f.updateMaxConfirmedTd(maxTd)
}
}
// checkAnnouncedHeaders updates peer's block tree if necessary after validating
// a batch of headers. It searches for the latest header in the batch that has a
// matching tree node (if any), and if it has not been marked as known already,
// sets it and its parents to known (even those which are older than the currently
// validated ones). Return value shows if all hashes, numbers and Tds matched
// correctly to the announced values (otherwise the peer should be dropped).
func (f *lightFetcher) checkAnnouncedHeaders(fp *fetcherPeerInfo, headers []*types.Header, tds []*big.Int) bool {
var (
n *fetcherTreeNode
header *types.Header
td *big.Int
)
for i := len(headers) - 1; ; i-- {
if i < 0 {
if n == nil {
// no more headers and nothing to match
return true
}
// we ran out of recently delivered headers but have not reached a node known by this peer yet, continue matching
td = f.chain.GetTd(header.ParentHash, header.Number.Uint64()-1)
header = f.chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
} else {
header = headers[i]
td = tds[i]
}
hash := header.Hash()
number := header.Number.Uint64()
if n == nil {
n = fp.nodeByHash[hash]
}
if n != nil {
if n.td == nil {
// node was unannounced
if nn := fp.nodeByHash[hash]; nn != nil {
// if there was already a node with the same hash, continue there and drop this one
nn.children = append(nn.children, n.children...)
n.children = nil
fp.deleteNode(n)
n = nn
} else {
n.hash = hash
n.td = td
fp.nodeByHash[hash] = n
}
}
// check if it matches the header
if n.hash != hash || n.number != number || n.td.Cmp(td) != 0 {
// peer has previously made an invalid announcement
return false
}
if n.known {
// we reached a known node that matched our expectations, return with success
return true
}
n.known = true
if fp.confirmedTd == nil || td.Cmp(fp.confirmedTd) > 0 {
fp.confirmedTd = td
fp.bestConfirmed = n
}
n = n.parent
if n == nil {
return true
} }
peer.lock.Unlock()
if remove {
//fmt.Println("checkSync fail")
f.pm.removePeer(peer.id)
} }
} }
} }
func (f *lightFetcher) syncLoop() { // checkSyncedHeaders updates peer's block tree after synchronisation by marking
f.pm.wg.Add(1) // downloaded headers as known. If none of the announced headers are found after
defer f.pm.wg.Done() // syncing, the peer is dropped.
func (f *lightFetcher) checkSyncedHeaders(p *peer) {
srtoNotify := false fp := f.peers[p]
for { if fp == nil {
select { glog.V(logger.Debug).Infof("checkSyncedHeaders: unknown peer")
case <-f.pm.quitSync:
return return
case ext := <-f.notifyChn: }
//fmt.Println("<-f.notifyChn", f.syncing, ext, srtoNotify) n := fp.lastAnnounced
s := srtoNotify var td *big.Int
srtoNotify = false for n != nil {
if !f.syncing && !(ext && s) { if td = f.chain.GetTd(n.hash, n.number); td != nil {
if p, r := f.nextRequest(); r != nil { break
srtoNotify = true }
go func() { n = n.parent
time.Sleep(softRequestTimeout) }
f.notifyChn <- false // now n is the latest downloaded header after syncing
}() if n == nil {
f.request(p, r) glog.V(logger.Debug).Infof("synchronisation failed with peer %v", p.id)
go f.pm.removePeer(p.id)
} else {
header := f.chain.GetHeader(n.hash, n.number)
f.newHeaders([]*types.Header{header}, []*big.Int{td})
}
}
// checkKnownNode checks if a block tree node is known (downloaded and validated)
// If it was not known previously but found in the database, sets its known flag
func (f *lightFetcher) checkKnownNode(p *peer, n *fetcherTreeNode) bool {
if n.known {
return true
}
td := f.chain.GetTd(n.hash, n.number)
if td == nil {
return false
}
fp := f.peers[p]
if fp == nil {
glog.V(logger.Debug).Infof("checkKnownNode: unknown peer")
return false
}
header := f.chain.GetHeader(n.hash, n.number)
if !f.checkAnnouncedHeaders(fp, []*types.Header{header}, []*big.Int{td}) {
glog.V(logger.Debug).Infof("announce inconsistency by peer %v", p.id)
go f.pm.removePeer(p.id)
}
if fp.confirmedTd != nil {
f.updateMaxConfirmedTd(fp.confirmedTd)
}
return n.known
}
// deleteNode deletes a node and its child subtrees from a peer's block tree
func (fp *fetcherPeerInfo) deleteNode(n *fetcherTreeNode) {
if n.parent != nil {
for i, nn := range n.parent.children {
if nn == n {
n.parent.children = append(n.parent.children[:i], n.parent.children[i+1:]...)
break
} }
} }
case reqID := <-f.timeoutChn:
f.reqMu.Lock()
req, ok := f.requested[reqID]
if ok {
delete(f.requested, reqID)
} }
f.reqMu.Unlock() for {
if ok { if n.td != nil {
//fmt.Println("hard timeout") delete(fp.nodeByHash, n.hash)
f.pm.removePeer(req.peer.id)
} }
case resp := <-f.deliverChn: fp.nodeCnt--
//fmt.Println("<-f.deliverChn", f.syncing) if len(n.children) == 0 {
f.reqMu.Lock() return
req, ok := f.requested[resp.reqID] }
delete(f.requested, resp.reqID) for i, nn := range n.children {
f.reqMu.Unlock() if i == 0 {
if !ok || !(f.syncing || f.processResponse(req, resp)) { n = nn
//fmt.Println("processResponse fail") } else {
f.pm.removePeer(req.peer.id) fp.deleteNode(nn)
} }
case <-f.syncDone: }
//fmt.Println("<-f.syncDone", f.syncing) }
f.checkSyncedHeaders() }
f.syncing = false
// updateStatsEntry items form a linked list that is expanded with a new item every time a new head with a higher Td
// than the previous one has been downloaded and validated. The list contains a series of maximum confirmed Td values
// and the time these values have been confirmed, both increasing monotonically. A maximum confirmed Td is calculated
// both globally for all peers and also for each individual peer (meaning that the given peer has announced the head
// and it has also been downloaded from any peer, either before or after the given announcement).
// The linked list has a global tail where new confirmed Td entries are added and a separate head for each peer,
// pointing to the next Td entry that is higher than the peer's max confirmed Td (nil if it has already confirmed
// the current global head).
type updateStatsEntry struct {
time mclock.AbsTime
td *big.Int
next *updateStatsEntry
}
// updateMaxConfirmedTd updates the block delay statistics of active peers. Whenever a new highest Td is confirmed,
// adds it to the end of a linked list together with the time it has been confirmed. Then checks which peers have
// already confirmed a head with the same or higher Td (which counts as zero block delay) and updates their statistics.
// Those who have not confirmed such a head by now will be updated by a subsequent checkUpdateStats call with a
// positive block delay value.
func (f *lightFetcher) updateMaxConfirmedTd(td *big.Int) {
if f.maxConfirmedTd == nil || td.Cmp(f.maxConfirmedTd) > 0 {
f.maxConfirmedTd = td
newEntry := &updateStatsEntry{
time: mclock.Now(),
td: td,
}
if f.lastUpdateStats != nil {
f.lastUpdateStats.next = newEntry
}
f.lastUpdateStats = newEntry
for p, _ := range f.peers {
f.checkUpdateStats(p, newEntry)
}
}
}
// checkUpdateStats checks those peers who have not confirmed a certain highest Td (or a larger one) by the time it
// has been confirmed by another peer. If they have confirmed such a head by now, their stats are updated with the
// block delay which is (this peer's confirmation time)-(first confirmation time). After blockDelayTimeout has passed,
// the stats are updated with blockDelayTimeout value. In either case, the confirmed or timed out updateStatsEntry
// items are removed from the head of the linked list.
// If a new entry has been added to the global tail, it is passed as a parameter here even though this function
// assumes that it has already been added, so that if the peer's list is empty (all heads confirmed, head is nil),
// it can set the new head to newEntry.
func (f *lightFetcher) checkUpdateStats(p *peer, newEntry *updateStatsEntry) {
now := mclock.Now()
fp := f.peers[p]
if fp == nil {
glog.V(logger.Debug).Infof("checkUpdateStats: unknown peer")
return
}
if newEntry != nil && fp.firstUpdateStats == nil {
fp.firstUpdateStats = newEntry
}
for fp.firstUpdateStats != nil && fp.firstUpdateStats.time <= now-mclock.AbsTime(blockDelayTimeout) {
f.pm.serverPool.adjustBlockDelay(p.poolEntry, blockDelayTimeout)
fp.firstUpdateStats = fp.firstUpdateStats.next
}
if fp.confirmedTd != nil {
for fp.firstUpdateStats != nil && fp.firstUpdateStats.td.Cmp(fp.confirmedTd) <= 0 {
f.pm.serverPool.adjustBlockDelay(p.poolEntry, time.Duration(now-fp.firstUpdateStats.time))
fp.firstUpdateStats = fp.firstUpdateStats.next
} }
} }
} }

View file

@ -22,8 +22,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"net"
"sync" "sync"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -58,7 +58,7 @@ const (
MaxHeaderProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request MaxHeaderProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
MaxTxSend = 64 // Amount of transactions to be send per request MaxTxSend = 64 // Amount of transactions to be send per request
disableClientRemovePeer = true disableClientRemovePeer = false
) )
// errIncompatibleConfig is returned if the requested protocols and configs are // errIncompatibleConfig is returned if the requested protocols and configs are
@ -88,7 +88,7 @@ type BlockChain interface {
type txPool interface { type txPool interface {
// AddTransactions should add the given transactions to the pool. // AddTransactions should add the given transactions to the pool.
AddBatch([]*types.Transaction) AddBatch([]*types.Transaction) error
} }
type ProtocolManager struct { type ProtocolManager struct {
@ -101,10 +101,7 @@ type ProtocolManager struct {
chainDb ethdb.Database chainDb ethdb.Database
odr *LesOdr odr *LesOdr
server *LesServer server *LesServer
serverPool *serverPool
topicDisc *discv5.Network
lesTopic discv5.Topic
p2pServer *p2p.Server
downloader *downloader.Downloader downloader *downloader.Downloader
fetcher *lightFetcher fetcher *lightFetcher
@ -157,13 +154,29 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, network
Version: version, Version: version,
Length: ProtocolLengths[i], Length: ProtocolLengths[i],
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
var entry *poolEntry
peer := manager.newPeer(int(version), networkId, p, rw) peer := manager.newPeer(int(version), networkId, p, rw)
if manager.serverPool != nil {
addr := p.RemoteAddr().(*net.TCPAddr)
entry = manager.serverPool.connect(peer, addr.IP, uint16(addr.Port))
if entry == nil {
return fmt.Errorf("unwanted connection")
}
}
peer.poolEntry = entry
select { select {
case manager.newPeerCh <- peer: case manager.newPeerCh <- peer:
manager.wg.Add(1) manager.wg.Add(1)
defer manager.wg.Done() defer manager.wg.Done()
return manager.handle(peer) err := manager.handle(peer)
if entry != nil {
manager.serverPool.disconnect(entry)
}
return err
case <-manager.quitSync: case <-manager.quitSync:
if entry != nil {
manager.serverPool.disconnect(entry)
}
return p2p.DiscQuitting return p2p.DiscQuitting
} }
}, },
@ -192,7 +205,6 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, network
manager.downloader = downloader.New(downloader.LightSync, chainDb, manager.eventMux, blockchain.HasHeader, nil, blockchain.GetHeaderByHash, manager.downloader = downloader.New(downloader.LightSync, chainDb, manager.eventMux, blockchain.HasHeader, nil, blockchain.GetHeaderByHash,
nil, blockchain.CurrentHeader, nil, nil, nil, blockchain.GetTdByHash, nil, blockchain.CurrentHeader, nil, nil, nil, blockchain.GetTdByHash,
blockchain.InsertHeaderChain, nil, nil, blockchain.Rollback, removePeer) blockchain.InsertHeaderChain, nil, nil, blockchain.Rollback, removePeer)
manager.fetcher = newLightFetcher(manager)
} }
if odr != nil { if odr != nil {
@ -222,10 +234,12 @@ func (pm *ProtocolManager) removePeer(id string) {
glog.V(logger.Debug).Infof("LES: unregister peer %v", id) glog.V(logger.Debug).Infof("LES: unregister peer %v", id)
if pm.lightSync { if pm.lightSync {
pm.downloader.UnregisterPeer(id) pm.downloader.UnregisterPeer(id)
pm.odr.UnregisterPeer(peer)
if pm.txrelay != nil { if pm.txrelay != nil {
pm.txrelay.removePeer(id) pm.txrelay.removePeer(id)
} }
if pm.fetcher != nil {
pm.fetcher.removePeer(peer)
}
} }
if err := pm.peers.Unregister(id); err != nil { if err := pm.peers.Unregister(id); err != nil {
glog.V(logger.Error).Infoln("Removal failed:", err) glog.V(logger.Error).Infoln("Removal failed:", err)
@ -236,54 +250,26 @@ func (pm *ProtocolManager) removePeer(id string) {
} }
} }
func (pm *ProtocolManager) findServers() {
if pm.p2pServer == nil || pm.topicDisc == nil {
return
}
glog.V(logger.Debug).Infoln("Looking for topic", string(pm.lesTopic))
enodes := make(chan string, 100)
stop := make(chan struct{})
go pm.topicDisc.SearchTopic(pm.lesTopic, stop, enodes)
go func() {
added := make(map[string]bool)
for {
select {
case enode := <-enodes:
if !added[enode] {
glog.V(logger.Info).Infoln("Found LES server:", enode)
added[enode] = true
if node, err := discover.ParseNode(enode); err == nil {
pm.p2pServer.AddPeer(node)
}
}
case <-stop:
return
}
}
}()
select {
case <-time.After(time.Second * 20):
case <-pm.quitSync:
}
close(stop)
}
func (pm *ProtocolManager) Start(srvr *p2p.Server) { func (pm *ProtocolManager) Start(srvr *p2p.Server) {
pm.p2pServer = srvr var topicDisc *discv5.Network
if srvr != nil { if srvr != nil {
pm.topicDisc = srvr.DiscV5 topicDisc = srvr.DiscV5
} }
pm.lesTopic = discv5.Topic("LES@" + common.Bytes2Hex(pm.blockchain.Genesis().Hash().Bytes()[0:8])) lesTopic := discv5.Topic("LES@" + common.Bytes2Hex(pm.blockchain.Genesis().Hash().Bytes()[0:8]))
if pm.lightSync { if pm.lightSync {
// start sync handler // start sync handler
go pm.findServers() if srvr != nil { // srvr is nil during testing
pm.serverPool = newServerPool(pm.chainDb, []byte("serverPool/"), srvr, lesTopic, pm.quitSync, &pm.wg)
pm.odr.serverPool = pm.serverPool
pm.fetcher = newLightFetcher(pm)
}
go pm.syncer() go pm.syncer()
} else { } else {
if pm.topicDisc != nil { if topicDisc != nil {
go func() { go func() {
glog.V(logger.Debug).Infoln("Starting registering topic", string(pm.lesTopic)) glog.V(logger.Info).Infoln("Starting registering topic", string(lesTopic))
pm.topicDisc.RegisterTopic(pm.lesTopic, pm.quitSync) topicDisc.RegisterTopic(lesTopic, pm.quitSync)
glog.V(logger.Debug).Infoln("Stopped registering topic", string(pm.lesTopic)) glog.V(logger.Info).Infoln("Stopped registering topic", string(lesTopic))
}() }()
} }
go func() { go func() {
@ -352,13 +338,13 @@ func (pm *ProtocolManager) handle(p *peer) error {
glog.V(logger.Debug).Infof("LES: register peer %v", p.id) glog.V(logger.Debug).Infof("LES: register peer %v", p.id)
if pm.lightSync { if pm.lightSync {
requestHeadersByHash := func(origin common.Hash, amount int, skip int, reverse bool) error { requestHeadersByHash := func(origin common.Hash, amount int, skip int, reverse bool) error {
reqID := pm.odr.getNextReqID() reqID := getNextReqID()
cost := p.GetRequestCost(GetBlockHeadersMsg, amount) cost := p.GetRequestCost(GetBlockHeadersMsg, amount)
p.fcServer.SendRequest(reqID, cost) p.fcServer.SendRequest(reqID, cost)
return p.RequestHeadersByHash(reqID, cost, origin, amount, skip, reverse) return p.RequestHeadersByHash(reqID, cost, origin, amount, skip, reverse)
} }
requestHeadersByNumber := func(origin uint64, amount int, skip int, reverse bool) error { requestHeadersByNumber := func(origin uint64, amount int, skip int, reverse bool) error {
reqID := pm.odr.getNextReqID() reqID := getNextReqID()
cost := p.GetRequestCost(GetBlockHeadersMsg, amount) cost := p.GetRequestCost(GetBlockHeadersMsg, amount)
p.fcServer.SendRequest(reqID, cost) p.fcServer.SendRequest(reqID, cost)
return p.RequestHeadersByNumber(reqID, cost, origin, amount, skip, reverse) return p.RequestHeadersByNumber(reqID, cost, origin, amount, skip, reverse)
@ -367,12 +353,21 @@ func (pm *ProtocolManager) handle(p *peer) error {
requestHeadersByHash, requestHeadersByNumber, nil, nil, nil); err != nil { requestHeadersByHash, requestHeadersByNumber, nil, nil, nil); err != nil {
return err return err
} }
pm.odr.RegisterPeer(p)
if pm.txrelay != nil { if pm.txrelay != nil {
pm.txrelay.addPeer(p) pm.txrelay.addPeer(p)
} }
pm.fetcher.notify(p, nil) p.lock.Lock()
head := p.headInfo
p.lock.Unlock()
if pm.fetcher != nil {
pm.fetcher.addPeer(p)
pm.fetcher.announce(p, head)
}
if p.poolEntry != nil {
pm.serverPool.registered(p.poolEntry)
}
} }
stop := make(chan struct{}) stop := make(chan struct{})
@ -454,7 +449,9 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
return errResp(ErrDecode, "%v: %v", msg, err) return errResp(ErrDecode, "%v: %v", msg, err)
} }
glog.V(logger.Detail).Infoln("AnnounceMsg:", req.Number, req.Hash, req.Td, req.ReorgDepth) glog.V(logger.Detail).Infoln("AnnounceMsg:", req.Number, req.Hash, req.Td, req.ReorgDepth)
pm.fetcher.notify(p, &req) if pm.fetcher != nil {
go pm.fetcher.announce(p, &req)
}
case GetBlockHeadersMsg: case GetBlockHeadersMsg:
glog.V(logger.Debug).Infof("<=== GetBlockHeadersMsg from peer %v", p.id) glog.V(logger.Debug).Infof("<=== GetBlockHeadersMsg from peer %v", p.id)
@ -552,8 +549,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
return errResp(ErrDecode, "msg %v: %v", msg, err) return errResp(ErrDecode, "msg %v: %v", msg, err)
} }
p.fcServer.GotReply(resp.ReqID, resp.BV) p.fcServer.GotReply(resp.ReqID, resp.BV)
if pm.fetcher.requestedID(resp.ReqID) { if pm.fetcher != nil && pm.fetcher.requestedID(resp.ReqID) {
pm.fetcher.deliverHeaders(resp.ReqID, resp.Headers) pm.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers)
} else { } else {
err := pm.downloader.DeliverHeaders(p.id, resp.Headers) err := pm.downloader.DeliverHeaders(p.id, resp.Headers)
if err != nil { if err != nil {
@ -879,7 +876,11 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
if reqCnt > maxReqs || reqCnt > MaxTxSend { if reqCnt > maxReqs || reqCnt > MaxTxSend {
return errResp(ErrRequestRejected, "") return errResp(ErrRequestRejected, "")
} }
pm.txpool.AddBatch(txs)
if err := pm.txpool.AddBatch(txs); err != nil {
return errResp(ErrUnexpectedResponse, "msg: %v", err)
}
_, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) _, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)

View file

@ -25,6 +25,7 @@ import (
"math/big" "math/big"
"sync" "sync"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -334,3 +335,13 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu
func (p *testPeer) close() { func (p *testPeer) close() {
p.app.Close() p.app.Close()
} }
type testServerPool peer
func (p *testServerPool) selectPeer(func(*peer) (bool, uint64)) *peer {
return (*peer)(p)
}
func (p *testServerPool) adjustResponseTime(*poolEntry, time.Duration, bool) {
}

View file

@ -17,6 +17,8 @@
package les package les
import ( import (
"crypto/rand"
"encoding/binary"
"sync" "sync"
"time" "time"
@ -37,6 +39,11 @@ var (
// peerDropFn is a callback type for dropping a peer detected as malicious. // peerDropFn is a callback type for dropping a peer detected as malicious.
type peerDropFn func(id string) type peerDropFn func(id string)
type odrPeerSelector interface {
selectPeer(func(*peer) (bool, uint64)) *peer
adjustResponseTime(*poolEntry, time.Duration, bool)
}
type LesOdr struct { type LesOdr struct {
light.OdrBackend light.OdrBackend
db ethdb.Database db ethdb.Database
@ -44,15 +51,13 @@ type LesOdr struct {
removePeer peerDropFn removePeer peerDropFn
mlock, clock sync.Mutex mlock, clock sync.Mutex
sentReqs map[uint64]*sentReq sentReqs map[uint64]*sentReq
peers *odrPeerSet serverPool odrPeerSelector
lastReqID uint64
} }
func NewLesOdr(db ethdb.Database) *LesOdr { func NewLesOdr(db ethdb.Database) *LesOdr {
return &LesOdr{ return &LesOdr{
db: db, db: db,
stop: make(chan struct{}), stop: make(chan struct{}),
peers: newOdrPeerSet(),
sentReqs: make(map[uint64]*sentReq), sentReqs: make(map[uint64]*sentReq),
} }
} }
@ -77,16 +82,6 @@ type sentReq struct {
answered chan struct{} // closed and set to nil when any peer answers it answered chan struct{} // closed and set to nil when any peer answers it
} }
// RegisterPeer registers a new LES peer to the ODR capable peer set
func (self *LesOdr) RegisterPeer(p *peer) error {
return self.peers.register(p)
}
// UnregisterPeer removes a peer from the ODR capable peer set
func (self *LesOdr) UnregisterPeer(p *peer) {
self.peers.unregister(p)
}
const ( const (
MsgBlockBodies = iota MsgBlockBodies = iota
MsgCode MsgCode
@ -142,29 +137,26 @@ func (self *LesOdr) requestPeer(req *sentReq, peer *peer, delivered, timeout cha
select { select {
case <-delivered: case <-delivered:
servTime := uint64(mclock.Now() - stime) if self.serverPool != nil {
self.peers.updateTimeout(peer, false) self.serverPool.adjustResponseTime(peer.poolEntry, time.Duration(mclock.Now()-stime), false)
self.peers.updateServTime(peer, servTime) }
return return
case <-time.After(softRequestTimeout): case <-time.After(softRequestTimeout):
close(timeout) close(timeout)
if self.peers.updateTimeout(peer, true) {
self.removePeer(peer.id)
}
case <-self.stop: case <-self.stop:
return return
} }
select { select {
case <-delivered: case <-delivered:
servTime := uint64(mclock.Now() - stime)
self.peers.updateServTime(peer, servTime)
return
case <-time.After(hardRequestTimeout): case <-time.After(hardRequestTimeout):
self.removePeer(peer.id) go self.removePeer(peer.id)
case <-self.stop: case <-self.stop:
return return
} }
if self.serverPool != nil {
self.serverPool.adjustResponseTime(peer.poolEntry, time.Duration(mclock.Now()-stime), true)
}
} }
// networkRequest sends a request to known peers until an answer is received // networkRequest sends a request to known peers until an answer is received
@ -176,7 +168,7 @@ func (self *LesOdr) networkRequest(ctx context.Context, lreq LesOdrRequest) erro
sentTo: make(map[*peer]chan struct{}), sentTo: make(map[*peer]chan struct{}),
answered: answered, // reply delivered by any peer answered: answered, // reply delivered by any peer
} }
reqID := self.getNextReqID() reqID := getNextReqID()
self.mlock.Lock() self.mlock.Lock()
self.sentReqs[reqID] = req self.sentReqs[reqID] = req
self.mlock.Unlock() self.mlock.Unlock()
@ -193,7 +185,16 @@ func (self *LesOdr) networkRequest(ctx context.Context, lreq LesOdrRequest) erro
exclude := make(map[*peer]struct{}) exclude := make(map[*peer]struct{})
for { for {
if peer := self.peers.bestPeer(lreq, exclude); peer == nil { var p *peer
if self.serverPool != nil {
p = self.serverPool.selectPeer(func(p *peer) (bool, uint64) {
if !lreq.CanSend(p) {
return false, 0
}
return true, p.fcServer.CanSend(lreq.GetCost(p))
})
}
if p == nil {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
@ -202,17 +203,17 @@ func (self *LesOdr) networkRequest(ctx context.Context, lreq LesOdrRequest) erro
case <-time.After(retryPeers): case <-time.After(retryPeers):
} }
} else { } else {
exclude[peer] = struct{}{} exclude[p] = struct{}{}
delivered := make(chan struct{}) delivered := make(chan struct{})
timeout := make(chan struct{}) timeout := make(chan struct{})
req.lock.Lock() req.lock.Lock()
req.sentTo[peer] = delivered req.sentTo[p] = delivered
req.lock.Unlock() req.lock.Unlock()
reqWg.Add(1) reqWg.Add(1)
cost := lreq.GetCost(peer) cost := lreq.GetCost(p)
peer.fcServer.SendRequest(reqID, cost) p.fcServer.SendRequest(reqID, cost)
go self.requestPeer(req, peer, delivered, timeout, reqWg) go self.requestPeer(req, p, delivered, timeout, reqWg)
lreq.Request(reqID, peer) lreq.Request(reqID, p)
select { select {
case <-ctx.Done(): case <-ctx.Done():
@ -239,10 +240,8 @@ func (self *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err err
return return
} }
func (self *LesOdr) getNextReqID() uint64 { func getNextReqID() uint64 {
self.clock.Lock() var rnd [8]byte
defer self.clock.Unlock() rand.Read(rnd[:])
return binary.BigEndian.Uint64(rnd[:])
self.lastReqID++
return self.lastReqID
} }

View file

@ -1,120 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import (
"sync"
)
const dropTimeoutRatio = 20
type odrPeerInfo struct {
reqTimeSum, reqTimeCnt, reqCnt, timeoutCnt uint64
}
// odrPeerSet represents the collection of active peer participating in the block
// download procedure.
type odrPeerSet struct {
peers map[*peer]*odrPeerInfo
lock sync.RWMutex
}
// newPeerSet creates a new peer set top track the active download sources.
func newOdrPeerSet() *odrPeerSet {
return &odrPeerSet{
peers: make(map[*peer]*odrPeerInfo),
}
}
// Register injects a new peer into the working set, or returns an error if the
// peer is already known.
func (ps *odrPeerSet) register(p *peer) error {
ps.lock.Lock()
defer ps.lock.Unlock()
if _, ok := ps.peers[p]; ok {
return errAlreadyRegistered
}
ps.peers[p] = &odrPeerInfo{}
return nil
}
// Unregister removes a remote peer from the active set, disabling any further
// actions to/from that particular entity.
func (ps *odrPeerSet) unregister(p *peer) error {
ps.lock.Lock()
defer ps.lock.Unlock()
if _, ok := ps.peers[p]; !ok {
return errNotRegistered
}
delete(ps.peers, p)
return nil
}
func (ps *odrPeerSet) peerPriority(p *peer, info *odrPeerInfo, req LesOdrRequest) uint64 {
tm := p.fcServer.CanSend(req.GetCost(p))
if info.reqTimeCnt > 0 {
tm += info.reqTimeSum / info.reqTimeCnt
}
return tm
}
func (ps *odrPeerSet) bestPeer(req LesOdrRequest, exclude map[*peer]struct{}) *peer {
var best *peer
var bpv uint64
ps.lock.Lock()
defer ps.lock.Unlock()
for p, info := range ps.peers {
if _, ok := exclude[p]; !ok {
pv := ps.peerPriority(p, info, req)
if best == nil || pv < bpv {
best = p
bpv = pv
}
}
}
return best
}
func (ps *odrPeerSet) updateTimeout(p *peer, timeout bool) (drop bool) {
ps.lock.Lock()
defer ps.lock.Unlock()
if info, ok := ps.peers[p]; ok {
info.reqCnt++
if timeout {
// check ratio before increase to allow an extra timeout
if info.timeoutCnt*dropTimeoutRatio >= info.reqCnt {
return true
}
info.timeoutCnt++
}
}
return false
}
func (ps *odrPeerSet) updateServTime(p *peer, servTime uint64) {
ps.lock.Lock()
defer ps.lock.Unlock()
if info, ok := ps.peers[p]; ok {
info.reqTimeSum += servTime
info.reqTimeCnt++
}
}

View file

@ -36,6 +36,7 @@ import (
type LesOdrRequest interface { type LesOdrRequest interface {
GetCost(*peer) uint64 GetCost(*peer) uint64
CanSend(*peer) bool
Request(uint64, *peer) error Request(uint64, *peer) error
Valid(ethdb.Database, *Msg) bool // if true, keeps the retrieved object Valid(ethdb.Database, *Msg) bool // if true, keeps the retrieved object
} }
@ -66,6 +67,11 @@ func (self *BlockRequest) GetCost(peer *peer) uint64 {
return peer.GetRequestCost(GetBlockBodiesMsg, 1) return peer.GetRequestCost(GetBlockBodiesMsg, 1)
} }
// CanSend tells if a certain peer is suitable for serving the given request
func (self *BlockRequest) CanSend(peer *peer) bool {
return peer.HasBlock(self.Hash, self.Number)
}
// Request sends an ODR request to the LES network (implementation of LesOdrRequest) // Request sends an ODR request to the LES network (implementation of LesOdrRequest)
func (self *BlockRequest) Request(reqID uint64, peer *peer) error { func (self *BlockRequest) Request(reqID uint64, peer *peer) error {
glog.V(logger.Debug).Infof("ODR: requesting body of block %08x from peer %v", self.Hash[:4], peer.id) glog.V(logger.Debug).Infof("ODR: requesting body of block %08x from peer %v", self.Hash[:4], peer.id)
@ -121,6 +127,11 @@ func (self *ReceiptsRequest) GetCost(peer *peer) uint64 {
return peer.GetRequestCost(GetReceiptsMsg, 1) return peer.GetRequestCost(GetReceiptsMsg, 1)
} }
// CanSend tells if a certain peer is suitable for serving the given request
func (self *ReceiptsRequest) CanSend(peer *peer) bool {
return peer.HasBlock(self.Hash, self.Number)
}
// Request sends an ODR request to the LES network (implementation of LesOdrRequest) // Request sends an ODR request to the LES network (implementation of LesOdrRequest)
func (self *ReceiptsRequest) Request(reqID uint64, peer *peer) error { func (self *ReceiptsRequest) Request(reqID uint64, peer *peer) error {
glog.V(logger.Debug).Infof("ODR: requesting receipts for block %08x from peer %v", self.Hash[:4], peer.id) glog.V(logger.Debug).Infof("ODR: requesting receipts for block %08x from peer %v", self.Hash[:4], peer.id)
@ -171,6 +182,11 @@ func (self *TrieRequest) GetCost(peer *peer) uint64 {
return peer.GetRequestCost(GetProofsMsg, 1) return peer.GetRequestCost(GetProofsMsg, 1)
} }
// CanSend tells if a certain peer is suitable for serving the given request
func (self *TrieRequest) CanSend(peer *peer) bool {
return peer.HasBlock(self.Id.BlockHash, self.Id.BlockNumber)
}
// Request sends an ODR request to the LES network (implementation of LesOdrRequest) // Request sends an ODR request to the LES network (implementation of LesOdrRequest)
func (self *TrieRequest) Request(reqID uint64, peer *peer) error { func (self *TrieRequest) Request(reqID uint64, peer *peer) error {
glog.V(logger.Debug).Infof("ODR: requesting trie root %08x key %08x from peer %v", self.Id.Root[:4], self.Key[:4], peer.id) glog.V(logger.Debug).Infof("ODR: requesting trie root %08x key %08x from peer %v", self.Id.Root[:4], self.Key[:4], peer.id)
@ -221,6 +237,11 @@ func (self *CodeRequest) GetCost(peer *peer) uint64 {
return peer.GetRequestCost(GetCodeMsg, 1) return peer.GetRequestCost(GetCodeMsg, 1)
} }
// CanSend tells if a certain peer is suitable for serving the given request
func (self *CodeRequest) CanSend(peer *peer) bool {
return peer.HasBlock(self.Id.BlockHash, self.Id.BlockNumber)
}
// Request sends an ODR request to the LES network (implementation of LesOdrRequest) // Request sends an ODR request to the LES network (implementation of LesOdrRequest)
func (self *CodeRequest) Request(reqID uint64, peer *peer) error { func (self *CodeRequest) Request(reqID uint64, peer *peer) error {
glog.V(logger.Debug).Infof("ODR: requesting node data for hash %08x from peer %v", self.Hash[:4], peer.id) glog.V(logger.Debug).Infof("ODR: requesting node data for hash %08x from peer %v", self.Hash[:4], peer.id)
@ -274,6 +295,14 @@ func (self *ChtRequest) GetCost(peer *peer) uint64 {
return peer.GetRequestCost(GetHeaderProofsMsg, 1) return peer.GetRequestCost(GetHeaderProofsMsg, 1)
} }
// CanSend tells if a certain peer is suitable for serving the given request
func (self *ChtRequest) CanSend(peer *peer) bool {
peer.lock.RLock()
defer peer.lock.RUnlock()
return self.ChtNum <= (peer.headInfo.Number-light.ChtConfirmations)/light.ChtFrequency
}
// Request sends an ODR request to the LES network (implementation of LesOdrRequest) // Request sends an ODR request to the LES network (implementation of LesOdrRequest)
func (self *ChtRequest) Request(reqID uint64, peer *peer) error { func (self *ChtRequest) Request(reqID uint64, peer *peer) error {
glog.V(logger.Debug).Infof("ODR: requesting CHT #%d block #%d from peer %v", self.ChtNum, self.BlockNum, peer.id) glog.V(logger.Debug).Infof("ODR: requesting CHT #%d block #%d from peer %v", self.ChtNum, self.BlockNum, peer.id)

View file

@ -160,6 +160,8 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
pm, db, odr := newTestProtocolManagerMust(t, false, 4, testChainGen) pm, db, odr := newTestProtocolManagerMust(t, false, 4, testChainGen)
lpm, ldb, odr := newTestProtocolManagerMust(t, true, 0, nil) lpm, ldb, odr := newTestProtocolManagerMust(t, true, 0, nil)
_, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm) _, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm)
pool := (*testServerPool)(lpeer)
odr.serverPool = pool
select { select {
case <-time.After(time.Millisecond * 100): case <-time.After(time.Millisecond * 100):
case err := <-err1: case err := <-err1:
@ -188,13 +190,13 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
} }
// temporarily remove peer to test odr fails // temporarily remove peer to test odr fails
odr.UnregisterPeer(lpeer) odr.serverPool = nil
// expect retrievals to fail (except genesis block) without a les peer // expect retrievals to fail (except genesis block) without a les peer
test(expFail) test(expFail)
odr.RegisterPeer(lpeer) odr.serverPool = pool
// expect all retrievals to pass // expect all retrievals to pass
test(5) test(5)
odr.UnregisterPeer(lpeer) odr.serverPool = nil
// still expect all retrievals to pass, now data should be cached locally // still expect all retrievals to pass, now data should be cached locally
test(5) test(5)
} }

View file

@ -51,12 +51,14 @@ type peer struct {
id string id string
firstHeadInfo, headInfo *announceData headInfo *announceData
headInfoLen int
lock sync.RWMutex lock sync.RWMutex
announceChn chan announceData announceChn chan announceData
poolEntry *poolEntry
hasBlock func(common.Hash, uint64) bool
fcClient *flowcontrol.ClientNode // nil if the peer is server only fcClient *flowcontrol.ClientNode // nil if the peer is server only
fcServer *flowcontrol.ServerNode // nil if the peer is client only fcServer *flowcontrol.ServerNode // nil if the peer is client only
fcServerParams *flowcontrol.ServerParams fcServerParams *flowcontrol.ServerParams
@ -109,67 +111,6 @@ func (p *peer) headBlockInfo() blockInfo {
return blockInfo{Hash: p.headInfo.Hash, Number: p.headInfo.Number, Td: p.headInfo.Td} return blockInfo{Hash: p.headInfo.Hash, Number: p.headInfo.Number, Td: p.headInfo.Td}
} }
func (p *peer) addNotify(announce *announceData) bool {
p.lock.Lock()
defer p.lock.Unlock()
if announce.Td.Cmp(p.headInfo.Td) < 1 {
return false
}
if p.headInfoLen >= maxHeadInfoLen {
//return false
p.firstHeadInfo = p.firstHeadInfo.next
p.headInfoLen--
}
if announce.haveHeaders == 0 {
hh := p.headInfo.Number - announce.ReorgDepth
if p.headInfo.haveHeaders < hh {
hh = p.headInfo.haveHeaders
}
announce.haveHeaders = hh
}
p.headInfo.next = announce
p.headInfo = announce
p.headInfoLen++
return true
}
func (p *peer) gotHeader(hash common.Hash, number uint64, td *big.Int) bool {
h := p.firstHeadInfo
ptr := 0
for h != nil {
if h.Hash == hash {
if h.Number != number || h.Td.Cmp(td) != 0 {
return false
}
h.headKnown = true
h.haveHeaders = h.Number
p.firstHeadInfo = h
p.headInfoLen -= ptr
last := h
h = h.next
// propagate haveHeaders through the chain
for h != nil {
hh := last.Number - h.ReorgDepth
if last.haveHeaders < hh {
hh = last.haveHeaders
}
if hh > h.haveHeaders {
h.haveHeaders = hh
} else {
return true
}
last = h
h = h.next
}
return true
}
h = h.next
ptr++
}
return true
}
// Td retrieves the current total difficulty of a peer. // Td retrieves the current total difficulty of a peer.
func (p *peer) Td() *big.Int { func (p *peer) Td() *big.Int {
p.lock.RLock() p.lock.RLock()
@ -195,6 +136,9 @@ func sendResponse(w p2p.MsgWriter, msgcode, reqID, bv uint64, data interface{})
} }
func (p *peer) GetRequestCost(msgcode uint64, amount int) uint64 { func (p *peer) GetRequestCost(msgcode uint64, amount int) uint64 {
p.lock.RLock()
defer p.lock.RUnlock()
cost := p.fcCosts[msgcode].baseCost + p.fcCosts[msgcode].reqCost*uint64(amount) cost := p.fcCosts[msgcode].baseCost + p.fcCosts[msgcode].reqCost*uint64(amount)
if cost > p.fcServerParams.BufLimit { if cost > p.fcServerParams.BufLimit {
cost = p.fcServerParams.BufLimit cost = p.fcServerParams.BufLimit
@ -202,6 +146,14 @@ func (p *peer) GetRequestCost(msgcode uint64, amount int) uint64 {
return cost return cost
} }
// HasBlock checks if the peer has a given block
func (p *peer) HasBlock(hash common.Hash, number uint64) bool {
p.lock.RLock()
hashBlock := p.hasBlock
p.lock.RUnlock()
return hashBlock != nil && hashBlock(hash, number)
}
// SendAnnounce announces the availability of a number of blocks through // SendAnnounce announces the availability of a number of blocks through
// a hash notification. // a hash notification.
func (p *peer) SendAnnounce(request announceData) error { func (p *peer) SendAnnounce(request announceData) error {
@ -453,9 +405,7 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
p.fcCosts = MRC.decode() p.fcCosts = MRC.decode()
} }
p.firstHeadInfo = &announceData{Td: rTd, Hash: rHash, Number: rNum} p.headInfo = &announceData{Td: rTd, Hash: rHash, Number: rNum}
p.headInfo = p.firstHeadInfo
p.headInfoLen = 1
return nil return nil
} }

173
les/randselect.go Normal file
View file

@ -0,0 +1,173 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package les implements the Light Ethereum Subprotocol.
package les
import (
"math/rand"
)
// wrsItem interface should be implemented by any entries that are to be selected from
// a weightedRandomSelect set. Note that recalculating monotonously decreasing item
// weights on-demand (without constantly calling update) is allowed
type wrsItem interface {
Weight() int64
}
// weightedRandomSelect is capable of weighted random selection from a set of items
type weightedRandomSelect struct {
root *wrsNode
idx map[wrsItem]int
}
// newWeightedRandomSelect returns a new weightedRandomSelect structure
func newWeightedRandomSelect() *weightedRandomSelect {
return &weightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[wrsItem]int)}
}
// update updates an item's weight, adds it if it was non-existent or removes it if
// the new weight is zero. Note that explicitly updating decreasing weights is not necessary.
func (w *weightedRandomSelect) update(item wrsItem) {
w.setWeight(item, item.Weight())
}
// remove removes an item from the set
func (w *weightedRandomSelect) remove(item wrsItem) {
w.setWeight(item, 0)
}
// setWeight sets an item's weight to a specific value (removes it if zero)
func (w *weightedRandomSelect) setWeight(item wrsItem, weight int64) {
idx, ok := w.idx[item]
if ok {
w.root.setWeight(idx, weight)
if weight == 0 {
delete(w.idx, item)
}
} else {
if weight != 0 {
if w.root.itemCnt == w.root.maxItems {
// add a new level
newRoot := &wrsNode{sumWeight: w.root.sumWeight, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches}
newRoot.items[0] = w.root
newRoot.weights[0] = w.root.sumWeight
w.root = newRoot
}
w.idx[item] = w.root.insert(item, weight)
}
}
}
// choose randomly selects an item from the set, with a chance proportional to its
// current weight. If the weight of the chosen element has been decreased since the
// last stored value, returns it with a newWeight/oldWeight chance, otherwise just
// updates its weight and selects another one
func (w *weightedRandomSelect) choose() wrsItem {
for {
if w.root.sumWeight == 0 {
return nil
}
val := rand.Int63n(w.root.sumWeight)
choice, lastWeight := w.root.choose(val)
weight := choice.Weight()
if weight != lastWeight {
w.setWeight(choice, weight)
}
if weight >= lastWeight || rand.Int63n(lastWeight) < weight {
return choice
}
}
}
const wrsBranches = 8 // max number of branches in the wrsNode tree
// wrsNode is a node of a tree structure that can store wrsItems or further wrsNodes.
type wrsNode struct {
items [wrsBranches]interface{}
weights [wrsBranches]int64
sumWeight int64
level, itemCnt, maxItems int
}
// insert recursively inserts a new item to the tree and returns the item index
func (n *wrsNode) insert(item wrsItem, weight int64) int {
branch := 0
for n.items[branch] != nil && (n.level == 0 || n.items[branch].(*wrsNode).itemCnt == n.items[branch].(*wrsNode).maxItems) {
branch++
if branch == wrsBranches {
panic(nil)
}
}
n.itemCnt++
n.sumWeight += weight
n.weights[branch] += weight
if n.level == 0 {
n.items[branch] = item
return branch
} else {
var subNode *wrsNode
if n.items[branch] == nil {
subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1}
n.items[branch] = subNode
} else {
subNode = n.items[branch].(*wrsNode)
}
subIdx := subNode.insert(item, weight)
return subNode.maxItems*branch + subIdx
}
}
// setWeight updates the weight of a certain item (which should exist) and returns
// the change of the last weight value stored in the tree
func (n *wrsNode) setWeight(idx int, weight int64) int64 {
if n.level == 0 {
oldWeight := n.weights[idx]
n.weights[idx] = weight
diff := weight - oldWeight
n.sumWeight += diff
if weight == 0 {
n.items[idx] = nil
n.itemCnt--
}
return diff
}
branchItems := n.maxItems / wrsBranches
branch := idx / branchItems
diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight)
n.weights[branch] += diff
n.sumWeight += diff
if weight == 0 {
n.itemCnt--
}
return diff
}
// choose recursively selects an item from the tree and returns it along with its weight
func (n *wrsNode) choose(val int64) (wrsItem, int64) {
for i, w := range n.weights {
if val < w {
if n.level == 0 {
return n.items[i].(wrsItem), n.weights[i]
} else {
return n.items[i].(*wrsNode).choose(val)
}
} else {
val -= w
}
}
panic(nil)
}

67
les/randselect_test.go Normal file
View file

@ -0,0 +1,67 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package les
import (
"math/rand"
"testing"
)
type testWrsItem struct {
idx int
widx *int
}
func (t *testWrsItem) Weight() int64 {
w := *t.widx
if w == -1 || w == t.idx {
return int64(t.idx + 1)
}
return 0
}
func TestWeightedRandomSelect(t *testing.T) {
testFn := func(cnt int) {
s := newWeightedRandomSelect()
w := -1
list := make([]testWrsItem, cnt)
for i, _ := range list {
list[i] = testWrsItem{idx: i, widx: &w}
s.update(&list[i])
}
w = rand.Intn(cnt)
c := s.choose()
if c == nil {
t.Errorf("expected item, got nil")
} else {
if c.(*testWrsItem).idx != w {
t.Errorf("expected another item")
}
}
w = -2
if s.choose() != nil {
t.Errorf("expected nil, got item")
}
}
testFn(1)
testFn(10)
testFn(100)
testFn(1000)
testFn(10000)
testFn(100000)
testFn(1000000)
}

View file

@ -71,6 +71,8 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) {
pm, db, _ := newTestProtocolManagerMust(t, false, 4, testChainGen) pm, db, _ := newTestProtocolManagerMust(t, false, 4, testChainGen)
lpm, ldb, odr := newTestProtocolManagerMust(t, true, 0, nil) lpm, ldb, odr := newTestProtocolManagerMust(t, true, 0, nil)
_, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm) _, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm)
pool := (*testServerPool)(lpeer)
odr.serverPool = pool
select { select {
case <-time.After(time.Millisecond * 100): case <-time.After(time.Millisecond * 100):
case err := <-err1: case err := <-err1:
@ -100,11 +102,10 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) {
} }
// temporarily remove peer to test odr fails // temporarily remove peer to test odr fails
odr.UnregisterPeer(lpeer) odr.serverPool = nil
// expect retrievals to fail (except genesis block) without a les peer // expect retrievals to fail (except genesis block) without a les peer
test(0) test(0)
odr.RegisterPeer(lpeer) odr.serverPool = pool
// expect all retrievals to pass // expect all retrievals to pass
test(5) test(5)
odr.UnregisterPeer(lpeer)
} }

View file

@ -42,6 +42,9 @@ type LesServer struct {
fcManager *flowcontrol.ClientManager // nil if our node is client only fcManager *flowcontrol.ClientManager // nil if our node is client only
fcCostStats *requestCostStats fcCostStats *requestCostStats
defParams *flowcontrol.ServerParams defParams *flowcontrol.ServerParams
srvr *p2p.Server
synced, stopped bool
lock sync.Mutex
} }
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) { func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
@ -67,12 +70,35 @@ func (s *LesServer) Protocols() []p2p.Protocol {
return s.protocolManager.SubProtocols return s.protocolManager.SubProtocols
} }
// Start only starts the actual service if the ETH protocol has already been synced,
// otherwise it will be started by Synced()
func (s *LesServer) Start(srvr *p2p.Server) { func (s *LesServer) Start(srvr *p2p.Server) {
s.protocolManager.Start(srvr) s.lock.Lock()
defer s.lock.Unlock()
s.srvr = srvr
if s.synced {
s.protocolManager.Start(s.srvr)
}
} }
// Synced notifies the server that the ETH protocol has been synced and LES service can be started
func (s *LesServer) Synced() {
s.lock.Lock()
defer s.lock.Unlock()
s.synced = true
if s.srvr != nil && !s.stopped {
s.protocolManager.Start(s.srvr)
}
}
// Stop stops the LES service
func (s *LesServer) Stop() { func (s *LesServer) Stop() {
s.lock.Lock()
defer s.lock.Unlock()
s.stopped = true
s.fcCostStats.store() s.fcCostStats.store()
s.fcManager.Stop() s.fcManager.Stop()
go func() { go func() {
@ -325,7 +351,6 @@ func (pm *ProtocolManager) blockLoop() {
var ( var (
lastChtKey = []byte("LastChtNumber") // chtNum (uint64 big endian) lastChtKey = []byte("LastChtNumber") // chtNum (uint64 big endian)
chtPrefix = []byte("cht") // chtPrefix + chtNum (uint64 big endian) -> trie root hash chtPrefix = []byte("cht") // chtPrefix + chtNum (uint64 big endian) -> trie root hash
chtConfirmations = light.ChtFrequency / 2
) )
func getChtRoot(db ethdb.Database, num uint64) common.Hash { func getChtRoot(db ethdb.Database, num uint64) common.Hash {
@ -346,8 +371,8 @@ func makeCht(db ethdb.Database) bool {
headNum := core.GetBlockNumber(db, headHash) headNum := core.GetBlockNumber(db, headHash)
var newChtNum uint64 var newChtNum uint64
if headNum > chtConfirmations { if headNum > light.ChtConfirmations {
newChtNum = (headNum - chtConfirmations) / light.ChtFrequency newChtNum = (headNum - light.ChtConfirmations) / light.ChtFrequency
} }
var lastChtNum uint64 var lastChtNum uint64

766
les/serverpool.go Normal file
View file

@ -0,0 +1,766 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package les implements the Light Ethereum Subprotocol.
package les
import (
"io"
"math"
"math/rand"
"net"
"strconv"
"sync"
"time"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/rlp"
)
const (
// After a connection has been ended or timed out, there is a waiting period
// before it can be selected for connection again.
// waiting period = base delay * (1 + random(1))
// base delay = shortRetryDelay for the first shortRetryCnt times after a
// successful connection, after that longRetryDelay is applied
shortRetryCnt = 5
shortRetryDelay = time.Second * 5
longRetryDelay = time.Minute * 10
// maxNewEntries is the maximum number of newly discovered (never connected) nodes.
// If the limit is reached, the least recently discovered one is thrown out.
maxNewEntries = 1000
// maxKnownEntries is the maximum number of known (already connected) nodes.
// If the limit is reached, the least recently connected one is thrown out.
// (not that unlike new entries, known entries are persistent)
maxKnownEntries = 1000
// target for simultaneously connected servers
targetServerCount = 5
// target for servers selected from the known table
// (we leave room for trying new ones if there is any)
targetKnownSelect = 3
// after dialTimeout, consider the server unavailable and adjust statistics
dialTimeout = time.Second * 30
// targetConnTime is the minimum expected connection duration before a server
// drops a client without any specific reason
targetConnTime = time.Minute * 10
// new entry selection weight calculation based on most recent discovery time:
// unity until discoverExpireStart, then exponential decay with discoverExpireConst
discoverExpireStart = time.Minute * 20
discoverExpireConst = time.Minute * 20
// known entry selection weight is dropped by a factor of exp(-failDropLn) after
// each unsuccessful connection (restored after a successful one)
failDropLn = 0.1
// known node connection success and quality statistics have a long term average
// and a short term value which is adjusted exponentially with a factor of
// pstatRecentAdjust with each dial/connection and also returned exponentially
// to the average with the time constant pstatReturnToMeanTC
pstatRecentAdjust = 0.1
pstatReturnToMeanTC = time.Hour
// node address selection weight is dropped by a factor of exp(-addrFailDropLn) after
// each unsuccessful connection (restored after a successful one)
addrFailDropLn = math.Ln2
// responseScoreTC and delayScoreTC are exponential decay time constants for
// calculating selection chances from response times and block delay times
responseScoreTC = time.Millisecond * 100
delayScoreTC = time.Second * 5
timeoutPow = 10
// peerSelectMinWeight is added to calculated weights at request peer selection
// to give poorly performing peers a little chance of coming back
peerSelectMinWeight = 0.005
// initStatsWeight is used to initialize previously unknown peers with good
// statistics to give a chance to prove themselves
initStatsWeight = 1
)
// serverPool implements a pool for storing and selecting newly discovered and already
// known light server nodes. It received discovered nodes, stores statistics about
// known nodes and takes care of always having enough good quality servers connected.
type serverPool struct {
db ethdb.Database
dbKey []byte
server *p2p.Server
quit chan struct{}
wg *sync.WaitGroup
connWg sync.WaitGroup
discSetPeriod chan time.Duration
discNodes chan *discv5.Node
discLookups chan bool
entries map[discover.NodeID]*poolEntry
lock sync.Mutex
timeout, enableRetry chan *poolEntry
adjustStats chan poolStatAdjust
knownQueue, newQueue poolEntryQueue
knownSelect, newSelect *weightedRandomSelect
knownSelected, newSelected int
fastDiscover bool
}
// newServerPool creates a new serverPool instance
func newServerPool(db ethdb.Database, dbPrefix []byte, server *p2p.Server, topic discv5.Topic, quit chan struct{}, wg *sync.WaitGroup) *serverPool {
pool := &serverPool{
db: db,
dbKey: append(dbPrefix, []byte(topic)...),
server: server,
quit: quit,
wg: wg,
entries: make(map[discover.NodeID]*poolEntry),
timeout: make(chan *poolEntry, 1),
adjustStats: make(chan poolStatAdjust, 100),
enableRetry: make(chan *poolEntry, 1),
knownSelect: newWeightedRandomSelect(),
newSelect: newWeightedRandomSelect(),
fastDiscover: true,
}
pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry)
pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry)
wg.Add(1)
pool.loadNodes()
pool.checkDial()
if pool.server.DiscV5 != nil {
pool.discSetPeriod = make(chan time.Duration, 1)
pool.discNodes = make(chan *discv5.Node, 100)
pool.discLookups = make(chan bool, 100)
go pool.server.DiscV5.SearchTopic(topic, pool.discSetPeriod, pool.discNodes, pool.discLookups)
}
go pool.eventLoop()
return pool
}
// connect should be called upon any incoming connection. If the connection has been
// dialed by the server pool recently, the appropriate pool entry is returned.
// Otherwise, the connection should be rejected.
// Note that whenever a connection has been accepted and a pool entry has been returned,
// disconnect should also always be called.
func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry {
pool.lock.Lock()
defer pool.lock.Unlock()
entry := pool.entries[p.ID()]
if entry == nil {
return nil
}
glog.V(logger.Debug).Infof("connecting to %v, state: %v", p.id, entry.state)
if entry.state != psDialed {
return nil
}
pool.connWg.Add(1)
entry.peer = p
entry.state = psConnected
addr := &poolEntryAddress{
ip: ip,
port: port,
lastSeen: mclock.Now(),
}
entry.lastConnected = addr
entry.addr = make(map[string]*poolEntryAddress)
entry.addr[addr.strKey()] = addr
entry.addrSelect = *newWeightedRandomSelect()
entry.addrSelect.update(addr)
return entry
}
// registered should be called after a successful handshake
func (pool *serverPool) registered(entry *poolEntry) {
glog.V(logger.Debug).Infof("registered %v", entry.id.String())
pool.lock.Lock()
defer pool.lock.Unlock()
entry.state = psRegistered
entry.regTime = mclock.Now()
if !entry.known {
pool.newQueue.remove(entry)
entry.known = true
}
pool.knownQueue.setLatest(entry)
entry.shortRetry = shortRetryCnt
}
// disconnect should be called when ending a connection. Service quality statistics
// can be updated optionally (not updated if no registration happened, in this case
// only connection statistics are updated, just like in case of timeout)
func (pool *serverPool) disconnect(entry *poolEntry) {
glog.V(logger.Debug).Infof("disconnected %v", entry.id.String())
pool.lock.Lock()
defer pool.lock.Unlock()
if entry.state == psRegistered {
connTime := mclock.Now() - entry.regTime
connAdjust := float64(connTime) / float64(targetConnTime)
if connAdjust > 1 {
connAdjust = 1
}
stopped := false
select {
case <-pool.quit:
stopped = true
default:
}
if stopped {
entry.connectStats.add(1, connAdjust)
} else {
entry.connectStats.add(connAdjust, 1)
}
}
entry.state = psNotConnected
if entry.knownSelected {
pool.knownSelected--
} else {
pool.newSelected--
}
pool.setRetryDial(entry)
pool.connWg.Done()
}
const (
pseBlockDelay = iota
pseResponseTime
pseResponseTimeout
)
// poolStatAdjust records are sent to adjust peer block delay/response time statistics
type poolStatAdjust struct {
adjustType int
entry *poolEntry
time time.Duration
}
// adjustBlockDelay adjusts the block announce delay statistics of a node
func (pool *serverPool) adjustBlockDelay(entry *poolEntry, time time.Duration) {
pool.adjustStats <- poolStatAdjust{pseBlockDelay, entry, time}
}
// adjustResponseTime adjusts the request response time statistics of a node
func (pool *serverPool) adjustResponseTime(entry *poolEntry, time time.Duration, timeout bool) {
if timeout {
pool.adjustStats <- poolStatAdjust{pseResponseTimeout, entry, time}
} else {
pool.adjustStats <- poolStatAdjust{pseResponseTime, entry, time}
}
}
type selectPeerItem struct {
peer *peer
weight int64
}
func (sp selectPeerItem) Weight() int64 {
return sp.weight
}
// selectPeer selects a suitable peer for a request
func (pool *serverPool) selectPeer(canSend func(*peer) (bool, uint64)) *peer {
pool.lock.Lock()
defer pool.lock.Unlock()
sel := newWeightedRandomSelect()
for _, entry := range pool.entries {
if entry.state == psRegistered {
p := entry.peer
ok, cost := canSend(p)
if ok {
w := int64(1000000000 * (peerSelectMinWeight + math.Exp(-(entry.responseStats.recentAvg()+float64(cost))/float64(responseScoreTC))*math.Pow((1-entry.timeoutStats.recentAvg()), timeoutPow)))
sel.update(selectPeerItem{peer: p, weight: w})
}
}
}
choice := sel.choose()
if choice == nil {
return nil
}
return choice.(selectPeerItem).peer
}
// eventLoop handles pool events and mutex locking for all internal functions
func (pool *serverPool) eventLoop() {
lookupCnt := 0
var convTime mclock.AbsTime
pool.discSetPeriod <- time.Millisecond * 100
for {
select {
case entry := <-pool.timeout:
pool.lock.Lock()
if !entry.removed {
pool.checkDialTimeout(entry)
}
pool.lock.Unlock()
case entry := <-pool.enableRetry:
pool.lock.Lock()
if !entry.removed {
entry.delayedRetry = false
pool.updateCheckDial(entry)
}
pool.lock.Unlock()
case adj := <-pool.adjustStats:
pool.lock.Lock()
switch adj.adjustType {
case pseBlockDelay:
adj.entry.delayStats.add(float64(adj.time), 1)
case pseResponseTime:
adj.entry.responseStats.add(float64(adj.time), 1)
adj.entry.timeoutStats.add(0, 1)
case pseResponseTimeout:
adj.entry.timeoutStats.add(1, 1)
}
pool.lock.Unlock()
case node := <-pool.discNodes:
pool.lock.Lock()
now := mclock.Now()
id := discover.NodeID(node.ID)
entry := pool.entries[id]
if entry == nil {
glog.V(logger.Debug).Infof("discovered %v", node.String())
entry = &poolEntry{
id: id,
addr: make(map[string]*poolEntryAddress),
addrSelect: *newWeightedRandomSelect(),
shortRetry: shortRetryCnt,
}
pool.entries[id] = entry
// initialize previously unknown peers with good statistics to give a chance to prove themselves
entry.connectStats.add(1, initStatsWeight)
entry.delayStats.add(0, initStatsWeight)
entry.responseStats.add(0, initStatsWeight)
entry.timeoutStats.add(0, initStatsWeight)
}
entry.lastDiscovered = now
addr := &poolEntryAddress{
ip: node.IP,
port: node.TCP,
}
if a, ok := entry.addr[addr.strKey()]; ok {
addr = a
} else {
entry.addr[addr.strKey()] = addr
}
addr.lastSeen = now
entry.addrSelect.update(addr)
if !entry.known {
pool.newQueue.setLatest(entry)
}
pool.updateCheckDial(entry)
pool.lock.Unlock()
case conv := <-pool.discLookups:
if conv {
if lookupCnt == 0 {
convTime = mclock.Now()
}
lookupCnt++
if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) {
pool.fastDiscover = false
pool.discSetPeriod <- time.Minute
}
}
case <-pool.quit:
close(pool.discSetPeriod)
pool.connWg.Wait()
pool.saveNodes()
pool.wg.Done()
return
}
}
}
// loadNodes loads known nodes and their statistics from the database
func (pool *serverPool) loadNodes() {
enc, err := pool.db.Get(pool.dbKey)
if err != nil {
return
}
var list []*poolEntry
err = rlp.DecodeBytes(enc, &list)
if err != nil {
glog.V(logger.Debug).Infof("node list decode error: %v", err)
return
}
for _, e := range list {
glog.V(logger.Debug).Infof("loaded server stats %016x fails: %v connStats: %v / %v delayStats: %v / %v responseStats: %v / %v timeoutStats: %v / %v", e.id[0:8], e.lastConnected.fails, e.connectStats.avg, e.connectStats.weight, time.Duration(e.delayStats.avg), e.delayStats.weight, time.Duration(e.responseStats.avg), e.responseStats.weight, e.timeoutStats.avg, e.timeoutStats.weight)
pool.entries[e.id] = e
pool.knownQueue.setLatest(e)
pool.knownSelect.update((*knownEntry)(e))
}
}
// saveNodes saves known nodes and their statistics into the database. Nodes are
// ordered from least to most recently connected.
func (pool *serverPool) saveNodes() {
list := make([]*poolEntry, len(pool.knownQueue.queue))
for i, _ := range list {
list[i] = pool.knownQueue.fetchOldest()
}
enc, err := rlp.EncodeToBytes(list)
if err == nil {
pool.db.Put(pool.dbKey, enc)
}
}
// removeEntry removes a pool entry when the entry count limit is reached.
// Note that it is called by the new/known queues from which the entry has already
// been removed so removing it from the queues is not necessary.
func (pool *serverPool) removeEntry(entry *poolEntry) {
pool.newSelect.remove((*discoveredEntry)(entry))
pool.knownSelect.remove((*knownEntry)(entry))
entry.removed = true
delete(pool.entries, entry.id)
}
// setRetryDial starts the timer which will enable dialing a certain node again
func (pool *serverPool) setRetryDial(entry *poolEntry) {
delay := longRetryDelay
if entry.shortRetry > 0 {
entry.shortRetry--
delay = shortRetryDelay
}
delay += time.Duration(rand.Int63n(int64(delay) + 1))
entry.delayedRetry = true
go func() {
select {
case <-pool.quit:
case <-time.After(delay):
select {
case <-pool.quit:
case pool.enableRetry <- entry:
}
}
}()
}
// updateCheckDial is called when an entry can potentially be dialed again. It updates
// its selection weights and checks if new dials can/should be made.
func (pool *serverPool) updateCheckDial(entry *poolEntry) {
pool.newSelect.update((*discoveredEntry)(entry))
pool.knownSelect.update((*knownEntry)(entry))
pool.checkDial()
}
// checkDial checks if new dials can/should be made. It tries to select servers both
// based on good statistics and recent discovery.
func (pool *serverPool) checkDial() {
fillWithKnownSelects := !pool.fastDiscover
for pool.knownSelected < targetKnownSelect {
entry := pool.knownSelect.choose()
if entry == nil {
fillWithKnownSelects = false
break
}
pool.dial((*poolEntry)(entry.(*knownEntry)), true)
}
for pool.knownSelected+pool.newSelected < targetServerCount {
entry := pool.newSelect.choose()
if entry == nil {
break
}
pool.dial((*poolEntry)(entry.(*discoveredEntry)), false)
}
if fillWithKnownSelects {
// no more newly discovered nodes to select and since fast discover period
// is over, we probably won't find more in the near future so select more
// known entries if possible
for pool.knownSelected < targetServerCount {
entry := pool.knownSelect.choose()
if entry == nil {
break
}
pool.dial((*poolEntry)(entry.(*knownEntry)), true)
}
}
}
// dial initiates a new connection
func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) {
if entry.state != psNotConnected {
return
}
entry.state = psDialed
entry.knownSelected = knownSelected
if knownSelected {
pool.knownSelected++
} else {
pool.newSelected++
}
addr := entry.addrSelect.choose().(*poolEntryAddress)
glog.V(logger.Debug).Infof("dialing %v out of %v, known: %v", entry.id.String()+"@"+addr.strKey(), len(entry.addr), knownSelected)
entry.dialed = addr
go func() {
pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port))
select {
case <-pool.quit:
case <-time.After(dialTimeout):
select {
case <-pool.quit:
case pool.timeout <- entry:
}
}
}()
}
// checkDialTimeout checks if the node is still in dialed state and if so, resets it
// and adjusts connection statistics accordingly.
func (pool *serverPool) checkDialTimeout(entry *poolEntry) {
if entry.state != psDialed {
return
}
glog.V(logger.Debug).Infof("timeout %v", entry.id.String()+"@"+entry.dialed.strKey())
entry.state = psNotConnected
if entry.knownSelected {
pool.knownSelected--
} else {
pool.newSelected--
}
entry.connectStats.add(0, 1)
entry.dialed.fails++
pool.setRetryDial(entry)
}
const (
psNotConnected = iota
psDialed
psConnected
psRegistered
)
// poolEntry represents a server node and stores its current state and statistics.
type poolEntry struct {
peer *peer
id discover.NodeID
addr map[string]*poolEntryAddress
lastConnected, dialed *poolEntryAddress
addrSelect weightedRandomSelect
lastDiscovered mclock.AbsTime
known, knownSelected bool
connectStats, delayStats poolStats
responseStats, timeoutStats poolStats
state int
regTime mclock.AbsTime
queueIdx int
removed bool
delayedRetry bool
shortRetry int
}
func (e *poolEntry) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats})
}
func (e *poolEntry) DecodeRLP(s *rlp.Stream) error {
var entry struct {
ID discover.NodeID
IP net.IP
Port uint16
Fails uint
CStat, DStat, RStat, TStat poolStats
}
if err := s.Decode(&entry); err != nil {
return err
}
addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()}
e.id = entry.ID
e.addr = make(map[string]*poolEntryAddress)
e.addr[addr.strKey()] = addr
e.addrSelect = *newWeightedRandomSelect()
e.addrSelect.update(addr)
e.lastConnected = addr
e.connectStats = entry.CStat
e.delayStats = entry.DStat
e.responseStats = entry.RStat
e.timeoutStats = entry.TStat
e.shortRetry = shortRetryCnt
e.known = true
return nil
}
// discoveredEntry implements wrsItem
type discoveredEntry poolEntry
// Weight calculates random selection weight for newly discovered entries
func (e *discoveredEntry) Weight() int64 {
if e.state != psNotConnected || e.delayedRetry {
return 0
}
t := time.Duration(mclock.Now() - e.lastDiscovered)
if t <= discoverExpireStart {
return 1000000000
} else {
return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst)))
}
}
// knownEntry implements wrsItem
type knownEntry poolEntry
// Weight calculates random selection weight for known entries
func (e *knownEntry) Weight() int64 {
if e.state != psNotConnected || !e.known || e.delayedRetry {
return 0
}
return int64(1000000000 * e.connectStats.recentAvg() * math.Exp(-float64(e.lastConnected.fails)*failDropLn-e.responseStats.recentAvg()/float64(responseScoreTC)-e.delayStats.recentAvg()/float64(delayScoreTC)) * math.Pow((1-e.timeoutStats.recentAvg()), timeoutPow))
}
// poolEntryAddress is a separate object because currently it is necessary to remember
// multiple potential network addresses for a pool entry. This will be removed after
// the final implementation of v5 discovery which will retrieve signed and serial
// numbered advertisements, making it clear which IP/port is the latest one.
type poolEntryAddress struct {
ip net.IP
port uint16
lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db
fails uint // connection failures since last successful connection (persistent)
}
func (a *poolEntryAddress) Weight() int64 {
t := time.Duration(mclock.Now() - a.lastSeen)
return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1
}
func (a *poolEntryAddress) strKey() string {
return a.ip.String() + ":" + strconv.Itoa(int(a.port))
}
// poolStats implement statistics for a certain quantity with a long term average
// and a short term value which is adjusted exponentially with a factor of
// pstatRecentAdjust with each update and also returned exponentially to the
// average with the time constant pstatReturnToMeanTC
type poolStats struct {
sum, weight, avg, recent float64
lastRecalc mclock.AbsTime
}
// init initializes stats with a long term sum/update count pair retrieved from the database
func (s *poolStats) init(sum, weight float64) {
s.sum = sum
s.weight = weight
var avg float64
if weight > 0 {
avg = s.sum / weight
}
s.avg = avg
s.recent = avg
s.lastRecalc = mclock.Now()
}
// recalc recalculates recent value return-to-mean and long term average
func (s *poolStats) recalc() {
now := mclock.Now()
s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC))
if s.sum == 0 {
s.avg = 0
} else {
if s.sum > s.weight*1e30 {
s.avg = 1e30
} else {
s.avg = s.sum / s.weight
}
}
s.lastRecalc = now
}
// add updates the stats with a new value
func (s *poolStats) add(value, weight float64) {
s.weight += weight
s.sum += value * weight
s.recalc()
}
// recentAvg returns the short-term adjusted average
func (s *poolStats) recentAvg() float64 {
s.recalc()
return s.recent
}
func (s *poolStats) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)})
}
func (s *poolStats) DecodeRLP(st *rlp.Stream) error {
var stats struct {
SumUint, WeightUint uint64
}
if err := st.Decode(&stats); err != nil {
return err
}
s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint))
return nil
}
// poolEntryQueue keeps track of its least recently accessed entries and removes
// them when the number of entries reaches the limit
type poolEntryQueue struct {
queue map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value
newPtr, oldPtr, maxCnt int
removeFromPool func(*poolEntry)
}
// newPoolEntryQueue returns a new poolEntryQueue
func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue {
return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool}
}
// fetchOldest returns and removes the least recently accessed entry
func (q *poolEntryQueue) fetchOldest() *poolEntry {
if len(q.queue) == 0 {
return nil
}
for {
if e := q.queue[q.oldPtr]; e != nil {
delete(q.queue, q.oldPtr)
q.oldPtr++
return e
}
q.oldPtr++
}
}
// remove removes an entry from the queue
func (q *poolEntryQueue) remove(entry *poolEntry) {
if q.queue[entry.queueIdx] == entry {
delete(q.queue, entry.queueIdx)
}
}
// setLatest adds or updates a recently accessed entry. It also checks if an old entry
// needs to be removed and removes it from the parent pool too with a callback function.
func (q *poolEntryQueue) setLatest(entry *poolEntry) {
if q.queue[entry.queueIdx] == entry {
delete(q.queue, entry.queueIdx)
} else {
if len(q.queue) == q.maxCnt {
e := q.fetchOldest()
q.remove(e)
q.removeFromPool(e)
}
}
entry.queueIdx = q.newPtr
q.queue[entry.queueIdx] = entry
q.newPtr++
}

View file

@ -505,3 +505,14 @@ func (self *LightChain) SyncCht(ctx context.Context) bool {
} }
return false return false
} }
// LockChain locks the chain mutex for reading so that multiple canonical hashes can be
// retrieved while it is guaranteed that they belong to the same version of the chain
func (self *LightChain) LockChain() {
self.chainmu.RLock()
}
// UnlockChain unlocks the chain mutex
func (self *LightChain) UnlockChain() {
self.chainmu.RUnlock()
}

View file

@ -48,6 +48,7 @@ type OdrRequest interface {
// TrieID identifies a state or account storage trie // TrieID identifies a state or account storage trie
type TrieID struct { type TrieID struct {
BlockHash, Root common.Hash BlockHash, Root common.Hash
BlockNumber uint64
AccKey []byte AccKey []byte
} }
@ -56,6 +57,7 @@ type TrieID struct {
func StateTrieID(header *types.Header) *TrieID { func StateTrieID(header *types.Header) *TrieID {
return &TrieID{ return &TrieID{
BlockHash: header.Hash(), BlockHash: header.Hash(),
BlockNumber: header.Number.Uint64(),
AccKey: nil, AccKey: nil,
Root: header.Root, Root: header.Root,
} }
@ -67,6 +69,7 @@ func StateTrieID(header *types.Header) *TrieID {
func StorageTrieID(state *TrieID, addr common.Address, root common.Hash) *TrieID { func StorageTrieID(state *TrieID, addr common.Address, root common.Hash) *TrieID {
return &TrieID{ return &TrieID{
BlockHash: state.BlockHash, BlockHash: state.BlockHash,
BlockNumber: state.BlockNumber,
AccKey: crypto.Keccak256(addr[:]), AccKey: crypto.Keccak256(addr[:]),
Root: root, Root: root,
} }

View file

@ -39,6 +39,7 @@ var (
ErrNoHeader = errors.New("Header not found") ErrNoHeader = errors.New("Header not found")
ChtFrequency = uint64(4096) ChtFrequency = uint64(4096)
ChtConfirmations = uint64(2048)
trustedChtKey = []byte("TrustedCHT") trustedChtKey = []byte("TrustedCHT")
) )

View file

@ -346,19 +346,8 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
if from, err = types.Sender(pool.signer, tx); err != nil { if from, err = types.Sender(pool.signer, tx); err != nil {
return core.ErrInvalidSender return core.ErrInvalidSender
} }
// Make sure the account exist. Non existent accounts
// haven't got funds and well therefor never pass.
currentState := pool.currentState()
if h, err := currentState.HasAccount(ctx, from); err == nil {
if !h {
return core.ErrNonExistentAccount
}
} else {
return err
}
// Last but not least check for nonce errors // Last but not least check for nonce errors
currentState := pool.currentState()
if n, err := currentState.GetNonce(ctx, from); err == nil { if n, err := currentState.GetNonce(ctx, from); err == nil {
if n > tx.Nonce() { if n > tx.Nonce() {
return core.ErrNonce return core.ErrNonce
@ -500,7 +489,7 @@ func (tp *TxPool) GetTransaction(hash common.Hash) *types.Transaction {
// GetTransactions returns all currently processable transactions. // GetTransactions returns all currently processable transactions.
// The returned slice may be modified by the caller. // The returned slice may be modified by the caller.
func (self *TxPool) GetTransactions() (txs types.Transactions) { func (self *TxPool) GetTransactions() (txs types.Transactions, err error) {
self.mu.RLock() self.mu.RLock()
defer self.mu.RUnlock() defer self.mu.RUnlock()
@ -510,7 +499,7 @@ func (self *TxPool) GetTransactions() (txs types.Transactions) {
txs[i] = tx txs[i] = tx
i++ i++
} }
return txs return txs, nil
} }
// Content retrieves the data content of the transaction pool, returning all the // Content retrieves the data content of the transaction pool, returning all the

View file

@ -119,15 +119,14 @@ func (m *Miner) SetGasPrice(price *big.Int) {
func (self *Miner) Start(coinbase common.Address, threads int) { func (self *Miner) Start(coinbase common.Address, threads int) {
atomic.StoreInt32(&self.shouldStart, 1) atomic.StoreInt32(&self.shouldStart, 1)
self.threads = threads self.worker.setEtherbase(coinbase)
self.worker.coinbase = coinbase
self.coinbase = coinbase self.coinbase = coinbase
self.threads = threads
if atomic.LoadInt32(&self.canStart) == 0 { if atomic.LoadInt32(&self.canStart) == 0 {
glog.V(logger.Info).Infoln("Can not start mining operation due to network sync (starts when finished)") glog.V(logger.Info).Infoln("Can not start mining operation due to network sync (starts when finished)")
return return
} }
atomic.StoreInt32(&self.mining, 1) atomic.StoreInt32(&self.mining, 1)
for i := 0; i < threads; i++ { for i := 0; i < threads; i++ {
@ -135,9 +134,7 @@ func (self *Miner) Start(coinbase common.Address, threads int) {
} }
glog.V(logger.Info).Infof("Starting mining operation (CPU=%d TOT=%d)\n", threads, len(self.worker.agents)) glog.V(logger.Info).Infof("Starting mining operation (CPU=%d TOT=%d)\n", threads, len(self.worker.agents))
self.worker.start() self.worker.start()
self.worker.commitNewWork() self.worker.commitNewWork()
} }
@ -177,8 +174,7 @@ func (self *Miner) SetExtra(extra []byte) error {
if uint64(len(extra)) > params.MaximumExtraDataSize.Uint64() { if uint64(len(extra)) > params.MaximumExtraDataSize.Uint64() {
return fmt.Errorf("Extra exceeds max length. %d > %v", len(extra), params.MaximumExtraDataSize) return fmt.Errorf("Extra exceeds max length. %d > %v", len(extra), params.MaximumExtraDataSize)
} }
self.worker.setExtra(extra)
self.worker.extra = extra
return nil return nil
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/pow"
) )
type hashrate struct { type hashrate struct {
@ -37,10 +38,11 @@ type hashrate struct {
type RemoteAgent struct { type RemoteAgent struct {
mu sync.Mutex mu sync.Mutex
quit chan struct{} quitCh chan struct{}
workCh chan *Work workCh chan *Work
returnCh chan<- *Result returnCh chan<- *Result
pow pow.PoW
currentWork *Work currentWork *Work
work map[common.Hash]*Work work map[common.Hash]*Work
@ -50,8 +52,9 @@ type RemoteAgent struct {
running int32 // running indicates whether the agent is active. Call atomically running int32 // running indicates whether the agent is active. Call atomically
} }
func NewRemoteAgent() *RemoteAgent { func NewRemoteAgent(pow pow.PoW) *RemoteAgent {
return &RemoteAgent{ return &RemoteAgent{
pow: pow,
work: make(map[common.Hash]*Work), work: make(map[common.Hash]*Work),
hashrate: make(map[common.Hash]hashrate), hashrate: make(map[common.Hash]hashrate),
} }
@ -76,18 +79,16 @@ func (a *RemoteAgent) Start() {
if !atomic.CompareAndSwapInt32(&a.running, 0, 1) { if !atomic.CompareAndSwapInt32(&a.running, 0, 1) {
return return
} }
a.quitCh = make(chan struct{})
a.quit = make(chan struct{})
a.workCh = make(chan *Work, 1) a.workCh = make(chan *Work, 1)
go a.maintainLoop() go a.loop(a.workCh, a.quitCh)
} }
func (a *RemoteAgent) Stop() { func (a *RemoteAgent) Stop() {
if !atomic.CompareAndSwapInt32(&a.running, 1, 0) { if !atomic.CompareAndSwapInt32(&a.running, 1, 0) {
return return
} }
close(a.quitCh)
close(a.quit)
close(a.workCh) close(a.workCh)
} }
@ -128,35 +129,46 @@ func (a *RemoteAgent) GetWork() ([3]string, error) {
return res, errors.New("No work available yet, don't panic.") return res, errors.New("No work available yet, don't panic.")
} }
// Returns true or false, but does not indicate if the PoW was correct // SubmitWork tries to inject a PoW solution tinto the remote agent, returning
// whether the solution was acceted or not (not can be both a bad PoW as well as
// any other error, like no work pending).
func (a *RemoteAgent) SubmitWork(nonce uint64, mixDigest, hash common.Hash) bool { func (a *RemoteAgent) SubmitWork(nonce uint64, mixDigest, hash common.Hash) bool {
a.mu.Lock() a.mu.Lock()
defer a.mu.Unlock() defer a.mu.Unlock()
// Make sure the work submitted is present // Make sure the work submitted is present
if a.work[hash] != nil { work := a.work[hash]
block := a.work[hash].Block.WithMiningResult(nonce, mixDigest) if work == nil {
a.returnCh <- &Result{a.work[hash], block} glog.V(logger.Info).Infof("Work was submitted for %x but no pending work found", hash)
return false
}
// Make sure the PoW solutions is indeed valid
block := work.Block.WithMiningResult(nonce, mixDigest)
if !a.pow.Verify(block) {
glog.V(logger.Warn).Infof("Invalid PoW submitted for %x", hash)
return false
}
// Solutions seems to be valid, return to the miner and notify acceptance
a.returnCh <- &Result{work, block}
delete(a.work, hash) delete(a.work, hash)
return true return true
} else {
glog.V(logger.Info).Infof("Work was submitted for %x but no pending work found\n", hash)
}
return false
} }
func (a *RemoteAgent) maintainLoop() { // loop monitors mining events on the work and quit channels, updating the internal
// state of the rmeote miner until a termination is requested.
//
// Note, the reason the work and quit channels are passed as parameters is because
// RemoteAgent.Start() constantly recreates these channels, so the loop code cannot
// assume data stability in these member fields.
func (a *RemoteAgent) loop(workCh chan *Work, quitCh chan struct{}) {
ticker := time.Tick(5 * time.Second) ticker := time.Tick(5 * time.Second)
out:
for { for {
select { select {
case <-a.quit: case <-quitCh:
break out return
case work := <-a.workCh: case work := <-workCh:
a.mu.Lock() a.mu.Lock()
a.currentWork = work a.currentWork = work
a.mu.Unlock() a.mu.Unlock()

118
miner/unconfirmed.go Normal file
View file

@ -0,0 +1,118 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package miner
import (
"container/ring"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
)
// headerRetriever is used by the unconfirmed block set to verify whether a previously
// mined block is part of the canonical chain or not.
type headerRetriever interface {
// GetHeaderByNumber retrieves the canonical header associated with a block number.
GetHeaderByNumber(number uint64) *types.Header
}
// unconfirmedBlock is a small collection of metadata about a locally mined block
// that is placed into a unconfirmed set for canonical chain inclusion tracking.
type unconfirmedBlock struct {
index uint64
hash common.Hash
}
// unconfirmedBlocks implements a data structure to maintain locally mined blocks
// have have not yet reached enough maturity to guarantee chain inclusion. It is
// used by the miner to provide logs to the user when a previously mined block
// has a high enough guarantee to not be reorged out of te canonical chain.
type unconfirmedBlocks struct {
chain headerRetriever // Blockchain to verify canonical status through
depth uint // Depth after which to discard previous blocks
blocks *ring.Ring // Block infos to allow canonical chain cross checks
lock sync.RWMutex // Protects the fields from concurrent access
}
// newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks.
func newUnconfirmedBlocks(chain headerRetriever, depth uint) *unconfirmedBlocks {
return &unconfirmedBlocks{
chain: chain,
depth: depth,
}
}
// Insert adds a new block to the set of unconfirmed ones.
func (set *unconfirmedBlocks) Insert(index uint64, hash common.Hash) {
// If a new block was mined locally, shift out any old enough blocks
set.Shift(index)
// Create the new item as its own ring
item := ring.New(1)
item.Value = &unconfirmedBlock{
index: index,
hash: hash,
}
// Set as the initial ring or append to the end
set.lock.Lock()
defer set.lock.Unlock()
if set.blocks == nil {
set.blocks = item
} else {
set.blocks.Move(-1).Link(item)
}
// Display a log for the user to notify of a new mined block unconfirmed
glog.V(logger.Info).Infof("🔨 mined potential block #%d [%x…], waiting for %d blocks to confirm", index, hash.Bytes()[:4], set.depth)
}
// Shift drops all unconfirmed blocks from the set which exceed the unconfirmed sets depth
// allowance, checking them against the canonical chain for inclusion or staleness
// report.
func (set *unconfirmedBlocks) Shift(height uint64) {
set.lock.Lock()
defer set.lock.Unlock()
for set.blocks != nil {
// Retrieve the next unconfirmed block and abort if too fresh
next := set.blocks.Value.(*unconfirmedBlock)
if next.index+uint64(set.depth) > height {
break
}
// Block seems to exceed depth allowance, check for canonical status
header := set.chain.GetHeaderByNumber(next.index)
switch {
case header == nil:
glog.V(logger.Warn).Infof("failed to retrieve header of mined block #%d [%x…]", next.index, next.hash.Bytes()[:4])
case header.Hash() == next.hash:
glog.V(logger.Info).Infof("🔗 mined block #%d [%x…] reached canonical chain", next.index, next.hash.Bytes()[:4])
default:
glog.V(logger.Info).Infof("⑂ mined block #%d [%x…] became a side fork", next.index, next.hash.Bytes()[:4])
}
// Drop the block out of the ring
if set.blocks.Value == set.blocks.Next().Value {
set.blocks = nil
} else {
set.blocks = set.blocks.Move(-1)
set.blocks.Unlink(1)
set.blocks = set.blocks.Move(1)
}
}
}

85
miner/unconfirmed_test.go Normal file
View file

@ -0,0 +1,85 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package miner
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// noopHeaderRetriever is an implementation of headerRetriever that always
// returns nil for any requested headers.
type noopHeaderRetriever struct{}
func (r *noopHeaderRetriever) GetHeaderByNumber(number uint64) *types.Header {
return nil
}
// Tests that inserting blocks into the unconfirmed set accumulates them until
// the desired depth is reached, after which they begin to be dropped.
func TestUnconfirmedInsertBounds(t *testing.T) {
limit := uint(10)
pool := newUnconfirmedBlocks(new(noopHeaderRetriever), limit)
for depth := uint64(0); depth < 2*uint64(limit); depth++ {
// Insert multiple blocks for the same level just to stress it
for i := 0; i < int(depth); i++ {
pool.Insert(depth, common.Hash([32]byte{byte(depth), byte(i)}))
}
// Validate that no blocks below the depth allowance are left in
pool.blocks.Do(func(block interface{}) {
if block := block.(*unconfirmedBlock); block.index+uint64(limit) <= depth {
t.Errorf("depth %d: block %x not dropped", depth, block.hash)
}
})
}
}
// Tests that shifting blocks out of the unconfirmed set works both for normal
// cases as well as for corner cases such as empty sets, empty shifts or full
// shifts.
func TestUnconfirmedShifts(t *testing.T) {
// Create a pool with a few blocks on various depths
limit, start := uint(10), uint64(25)
pool := newUnconfirmedBlocks(new(noopHeaderRetriever), limit)
for depth := start; depth < start+uint64(limit); depth++ {
pool.Insert(depth, common.Hash([32]byte{byte(depth)}))
}
// Try to shift below the limit and ensure no blocks are dropped
pool.Shift(start + uint64(limit) - 1)
if n := pool.blocks.Len(); n != int(limit) {
t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit)
}
// Try to shift half the blocks out and verify remainder
pool.Shift(start + uint64(limit) - 1 + uint64(limit/2))
if n := pool.blocks.Len(); n != int(limit)/2 {
t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit/2)
}
// Try to shift all the remaining blocks out and verify emptyness
pool.Shift(start + 2*uint64(limit))
if n := pool.blocks.Len(); n != 0 {
t.Errorf("unconfirmed count mismatch: have %d, want %d", n, 0)
}
// Try to shift out from the empty set and make sure it doesn't break
pool.Shift(start + 3*uint64(limit))
if n := pool.blocks.Len(); n != 0 {
t.Errorf("unconfirmed count mismatch: have %d, want %d", n, 0)
}
}

View file

@ -55,11 +55,6 @@ type Agent interface {
GetHashRate() int64 GetHashRate() int64
} }
type uint64RingBuffer struct {
ints []uint64 //array of all integers in buffer
next int //where is the next insertion? assert 0 <= next < len(ints)
}
// Work is the workers current environment and holds // Work is the workers current environment and holds
// all of the current state information // all of the current state information
type Work struct { type Work struct {
@ -74,7 +69,6 @@ type Work struct {
ownedAccounts *set.Set ownedAccounts *set.Set
lowGasTxs types.Transactions lowGasTxs types.Transactions
failedTxs types.Transactions failedTxs types.Transactions
localMinedBlocks *uint64RingBuffer // the most recent block numbers that were mined locally (used to check block inclusion)
Block *types.Block // the new block Block *types.Block // the new block
@ -123,6 +117,8 @@ type worker struct {
txQueueMu sync.Mutex txQueueMu sync.Mutex
txQueue map[common.Hash]*types.Transaction txQueue map[common.Hash]*types.Transaction
unconfirmed *unconfirmedBlocks // set of locally mined blocks pending canonicalness confirmations
// atomic status counters // atomic status counters
mining int32 mining int32
atWork int32 atWork int32
@ -144,6 +140,7 @@ func newWorker(config *params.ChainConfig, coinbase common.Address, eth Backend,
coinbase: coinbase, coinbase: coinbase,
txQueue: make(map[common.Hash]*types.Transaction), txQueue: make(map[common.Hash]*types.Transaction),
agents: make(map[Agent]struct{}), agents: make(map[Agent]struct{}),
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), 5),
fullValidation: false, fullValidation: false,
} }
worker.events = worker.mux.Subscribe(core.ChainHeadEvent{}, core.ChainSideEvent{}, core.TxPreEvent{}) worker.events = worker.mux.Subscribe(core.ChainHeadEvent{}, core.ChainSideEvent{}, core.TxPreEvent{})
@ -161,6 +158,12 @@ func (self *worker) setEtherbase(addr common.Address) {
self.coinbase = addr self.coinbase = addr
} }
func (self *worker) setExtra(extra []byte) {
self.mu.Lock()
defer self.mu.Unlock()
self.extra = extra
}
func (self *worker) pending() (*types.Block, *state.StateDB) { func (self *worker) pending() (*types.Block, *state.StateDB) {
self.currentMu.Lock() self.currentMu.Lock()
defer self.currentMu.Unlock() defer self.currentMu.Unlock()
@ -263,18 +266,6 @@ func (self *worker) update() {
} }
} }
func newLocalMinedBlock(blockNumber uint64, prevMinedBlocks *uint64RingBuffer) (minedBlocks *uint64RingBuffer) {
if prevMinedBlocks == nil {
minedBlocks = &uint64RingBuffer{next: 0, ints: make([]uint64, miningLogAtDepth+1)}
} else {
minedBlocks = prevMinedBlocks
}
minedBlocks.ints[minedBlocks.next] = blockNumber
minedBlocks.next = (minedBlocks.next + 1) % len(minedBlocks.ints)
return minedBlocks
}
func (self *worker) wait() { func (self *worker) wait() {
for { for {
mustCommitNewWork := true mustCommitNewWork := true
@ -349,17 +340,8 @@ func (self *worker) wait() {
} }
}(block, work.state.Logs(), work.receipts) }(block, work.state.Logs(), work.receipts)
} }
// Insert the block into the set of pending ones to wait for confirmations
// check staleness and display confirmation self.unconfirmed.Insert(block.NumberU64(), block.Hash())
var stale, confirm string
canonBlock := self.chain.GetBlockByNumber(block.NumberU64())
if canonBlock != nil && canonBlock.Hash() != block.Hash() {
stale = "stale "
} else {
confirm = "Wait 5 blocks for confirmation"
work.localMinedBlocks = newLocalMinedBlock(block.Number().Uint64(), work.localMinedBlocks)
}
glog.V(logger.Info).Infof("🔨 Mined %sblock (#%v / %x). %s", stale, block.Number(), block.Hash().Bytes()[:4], confirm)
if mustCommitNewWork { if mustCommitNewWork {
self.commitNewWork() self.commitNewWork()
@ -411,9 +393,6 @@ func (self *worker) makeCurrent(parent *types.Block, header *types.Header) error
// Keep track of transactions which return errors so they can be removed // Keep track of transactions which return errors so they can be removed
work.tcount = 0 work.tcount = 0
work.ownedAccounts = accountAddressesSet(accounts) work.ownedAccounts = accountAddressesSet(accounts)
if self.current != nil {
work.localMinedBlocks = self.current.localMinedBlocks
}
self.current = work self.current = work
return nil return nil
} }
@ -429,38 +408,6 @@ func (w *worker) setGasPrice(p *big.Int) {
w.mux.Post(core.GasPriceChanged{Price: w.gasPrice}) w.mux.Post(core.GasPriceChanged{Price: w.gasPrice})
} }
func (self *worker) isBlockLocallyMined(current *Work, deepBlockNum uint64) bool {
//Did this instance mine a block at {deepBlockNum} ?
var isLocal = false
for idx, blockNum := range current.localMinedBlocks.ints {
if deepBlockNum == blockNum {
isLocal = true
current.localMinedBlocks.ints[idx] = 0 //prevent showing duplicate logs
break
}
}
//Short-circuit on false, because the previous and following tests must both be true
if !isLocal {
return false
}
//Does the block at {deepBlockNum} send earnings to my coinbase?
var block = self.chain.GetBlockByNumber(deepBlockNum)
return block != nil && block.Coinbase() == self.coinbase
}
func (self *worker) logLocalMinedBlocks(current, previous *Work) {
if previous != nil && current.localMinedBlocks != nil {
nextBlockNum := current.Block.NumberU64()
for checkBlockNum := previous.Block.NumberU64(); checkBlockNum < nextBlockNum; checkBlockNum++ {
inspectBlockNum := checkBlockNum - miningLogAtDepth
if self.isBlockLocallyMined(current, inspectBlockNum) {
glog.V(logger.Info).Infof("🔨 🔗 Mined %d blocks back: block #%v", miningLogAtDepth, inspectBlockNum)
}
}
}
}
func (self *worker) commitNewWork() { func (self *worker) commitNewWork() {
self.mu.Lock() self.mu.Lock()
defer self.mu.Unlock() defer self.mu.Unlock()
@ -507,7 +454,6 @@ func (self *worker) commitNewWork() {
} }
} }
} }
previous := self.current
// Could potentially happen if starting to mine in an odd state. // Could potentially happen if starting to mine in an odd state.
err := self.makeCurrent(parent, header) err := self.makeCurrent(parent, header)
if err != nil { if err != nil {
@ -519,7 +465,14 @@ func (self *worker) commitNewWork() {
if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 { if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 {
core.ApplyDAOHardFork(work.state) core.ApplyDAOHardFork(work.state)
} }
txs := types.NewTransactionsByPriceAndNonce(self.eth.TxPool().Pending())
pending, err := self.eth.TxPool().Pending()
if err != nil {
glog.Errorf("Could not fetch pending transactions: %v", err)
return
}
txs := types.NewTransactionsByPriceAndNonce(pending)
work.commitTransactions(self.mux, txs, self.gasPrice, self.chain) work.commitTransactions(self.mux, txs, self.gasPrice, self.chain)
self.eth.TxPool().RemoveBatch(work.lowGasTxs) self.eth.TxPool().RemoveBatch(work.lowGasTxs)
@ -561,7 +514,7 @@ func (self *worker) commitNewWork() {
// We only care about logging if we're actually mining. // We only care about logging if we're actually mining.
if atomic.LoadInt32(&self.mining) == 1 { if atomic.LoadInt32(&self.mining) == 1 {
glog.V(logger.Info).Infof("commit new work on block %v with %d txs & %d uncles. Took %v\n", work.Block.Number(), work.tcount, len(uncles), time.Since(tstart)) glog.V(logger.Info).Infof("commit new work on block %v with %d txs & %d uncles. Took %v\n", work.Block.Number(), work.tcount, len(uncles), time.Since(tstart))
self.logLocalMinedBlocks(work, previous) self.unconfirmed.Shift(work.Block.NumberU64() - 1)
} }
self.push(work) self.push(work)
} }

View file

@ -50,7 +50,7 @@ func TestnetChainConfig() *ChainConfig {
ChainID: params.TestNetChainID.Int64(), ChainID: params.TestNetChainID.Int64(),
HomesteadBlock: params.TestNetHomesteadBlock.Int64(), HomesteadBlock: params.TestNetHomesteadBlock.Int64(),
DAOForkBlock: 0, DAOForkBlock: 0,
DAOForkSupport: true, DAOForkSupport: false,
EIP150Block: params.TestNetHomesteadGasRepriceBlock.Int64(), EIP150Block: params.TestNetHomesteadGasRepriceBlock.Int64(),
EIP150Hash: Hash{params.TestNetHomesteadGasRepriceHash}, EIP150Hash: Hash{params.TestNetHomesteadGasRepriceHash},
EIP155Block: params.TestNetSpuriousDragon.Int64(), EIP155Block: params.TestNetSpuriousDragon.Int64(),

View file

@ -374,7 +374,7 @@ func TestUDP_successfulPing(t *testing.T) {
if n.ID != rid { if n.ID != rid {
t.Errorf("node has wrong ID: got %v, want %v", n.ID, rid) t.Errorf("node has wrong ID: got %v, want %v", n.ID, rid)
} }
if !bytes.Equal(n.IP, test.remoteaddr.IP) { if !n.IP.Equal(test.remoteaddr.IP) {
t.Errorf("node has wrong IP: got %v, want: %v", n.IP, test.remoteaddr.IP) t.Errorf("node has wrong IP: got %v, want: %v", n.IP, test.remoteaddr.IP)
} }
if int(n.UDP) != test.remoteaddr.Port { if int(n.UDP) != test.remoteaddr.Port {

View file

@ -127,7 +127,14 @@ type topicRegisterReq struct {
type topicSearchReq struct { type topicSearchReq struct {
topic Topic topic Topic
found chan<- string found chan<- *Node
lookup chan<- bool
delay time.Duration
}
type topicSearchResult struct {
target lookupInfo
nodes []*Node
} }
type timeoutEvent struct { type timeoutEvent struct {
@ -263,7 +270,9 @@ func (net *Network) lookup(target common.Hash, stopOnMatch bool) []*Node {
break break
} }
// Wait for the next reply. // Wait for the next reply.
for _, n := range <-reply { select {
case nodes := <-reply:
for _, n := range nodes {
if n != nil && !seen[n.ID] { if n != nil && !seen[n.ID] {
seen[n.ID] = true seen[n.ID] = true
result.push(n, bucketSize) result.push(n, bucketSize)
@ -273,6 +282,11 @@ func (net *Network) lookup(target common.Hash, stopOnMatch bool) []*Node {
} }
} }
pendingQueries-- pendingQueries--
case <-time.After(respTimeout):
// forget all pending requests, start new ones
pendingQueries = 0
reply = make(chan []*Node, alpha)
}
} }
return result.entries return result.entries
} }
@ -293,18 +307,20 @@ func (net *Network) RegisterTopic(topic Topic, stop <-chan struct{}) {
} }
} }
func (net *Network) SearchTopic(topic Topic, stop <-chan struct{}, found chan<- string) { func (net *Network) SearchTopic(topic Topic, setPeriod <-chan time.Duration, found chan<- *Node, lookup chan<- bool) {
for {
select { select {
case net.topicSearchReq <- topicSearchReq{topic, found}: case <-net.closed:
return
case delay, ok := <-setPeriod:
select {
case net.topicSearchReq <- topicSearchReq{topic: topic, found: found, lookup: lookup, delay: delay}:
case <-net.closed: case <-net.closed:
return return
} }
select { if !ok {
case <-net.closed: return
case <-stop: }
select {
case net.topicSearchReq <- topicSearchReq{topic, nil}:
case <-net.closed:
} }
} }
} }
@ -347,6 +363,13 @@ func (net *Network) reqTableOp(f func()) (called bool) {
// TODO: external address handling. // TODO: external address handling.
type topicSearchInfo struct {
lookupChn chan<- bool
period time.Duration
}
const maxSearchCount = 5
func (net *Network) loop() { func (net *Network) loop() {
var ( var (
refreshTimer = time.NewTicker(autoRefreshInterval) refreshTimer = time.NewTicker(autoRefreshInterval)
@ -385,10 +408,12 @@ func (net *Network) loop() {
topicRegisterLookupTarget lookupInfo topicRegisterLookupTarget lookupInfo
topicRegisterLookupDone chan []*Node topicRegisterLookupDone chan []*Node
topicRegisterLookupTick = time.NewTimer(0) topicRegisterLookupTick = time.NewTimer(0)
topicSearchLookupTarget lookupInfo
searchReqWhenRefreshDone []topicSearchReq searchReqWhenRefreshDone []topicSearchReq
searchInfo = make(map[Topic]topicSearchInfo)
activeSearchCount int
) )
topicSearchLookupDone := make(chan []*Node, 1) topicSearchLookupDone := make(chan topicSearchResult, 100)
topicSearch := make(chan Topic, 100)
<-topicRegisterLookupTick.C <-topicRegisterLookupTick.C
statsDump := time.NewTicker(10 * time.Second) statsDump := time.NewTicker(10 * time.Second)
@ -504,21 +529,52 @@ loop:
case req := <-net.topicSearchReq: case req := <-net.topicSearchReq:
if refreshDone == nil { if refreshDone == nil {
debugLog("<-net.topicSearchReq") debugLog("<-net.topicSearchReq")
if req.found == nil { info, ok := searchInfo[req.topic]
if ok {
if req.delay == time.Duration(0) {
delete(searchInfo, req.topic)
net.ticketStore.removeSearchTopic(req.topic) net.ticketStore.removeSearchTopic(req.topic)
} else {
info.period = req.delay
searchInfo[req.topic] = info
}
continue continue
} }
if req.delay != time.Duration(0) {
var info topicSearchInfo
info.period = req.delay
info.lookupChn = req.lookup
searchInfo[req.topic] = info
net.ticketStore.addSearchTopic(req.topic, req.found) net.ticketStore.addSearchTopic(req.topic, req.found)
if (topicSearchLookupTarget.target == common.Hash{}) { topicSearch <- req.topic
topicSearchLookupDone <- nil
} }
} else { } else {
searchReqWhenRefreshDone = append(searchReqWhenRefreshDone, req) searchReqWhenRefreshDone = append(searchReqWhenRefreshDone, req)
} }
case nodes := <-topicSearchLookupDone: case topic := <-topicSearch:
debugLog("<-topicSearchLookupDone") if activeSearchCount < maxSearchCount {
net.ticketStore.searchLookupDone(topicSearchLookupTarget, nodes, func(n *Node) []byte { activeSearchCount++
target := net.ticketStore.nextSearchLookup(topic)
go func() {
nodes := net.lookup(target.target, false)
topicSearchLookupDone <- topicSearchResult{target: target, nodes: nodes}
}()
}
period := searchInfo[topic].period
if period != time.Duration(0) {
go func() {
time.Sleep(period)
topicSearch <- topic
}()
}
case res := <-topicSearchLookupDone:
activeSearchCount--
if lookupChn := searchInfo[res.target.topic].lookupChn; lookupChn != nil {
lookupChn <- net.ticketStore.radius[res.target.topic].converged
}
net.ticketStore.searchLookupDone(res.target, res.nodes, func(n *Node) []byte {
net.ping(n, n.addr()) net.ping(n, n.addr())
return n.pingEcho return n.pingEcho
}, func(n *Node, topic Topic) []byte { }, func(n *Node, topic Topic) []byte {
@ -531,11 +587,6 @@ loop:
return nil return nil
} }
}) })
topicSearchLookupTarget = net.ticketStore.nextSearchLookup()
target := topicSearchLookupTarget.target
if (target != common.Hash{}) {
go func() { topicSearchLookupDone <- net.lookup(target, false) }()
}
case <-statsDump.C: case <-statsDump.C:
debugLog("<-statsDump.C") debugLog("<-statsDump.C")
@ -708,7 +759,7 @@ func (net *Network) internNodeFromNeighbours(sender *net.UDPAddr, rn rpcNode) (n
} }
return n, err return n, err
} }
if !bytes.Equal(n.IP, rn.IP) || n.UDP != rn.UDP || n.TCP != rn.TCP { if !n.IP.Equal(rn.IP) || n.UDP != rn.UDP || n.TCP != rn.TCP {
err = fmt.Errorf("metadata mismatch: got %v, want %v", rn, n) err = fmt.Errorf("metadata mismatch: got %v, want %v", rn, n)
} }
return n, err return n, err

View file

@ -17,7 +17,6 @@
package discv5 package discv5
import ( import (
"bytes"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"encoding/hex" "encoding/hex"
@ -81,7 +80,7 @@ func (n *Node) addrEqual(a *net.UDPAddr) bool {
if ipv4 := a.IP.To4(); ipv4 != nil { if ipv4 := a.IP.To4(); ipv4 != nil {
ip = ipv4 ip = ipv4
} }
return n.UDP == uint16(a.Port) && bytes.Equal(n.IP, ip) return n.UDP == uint16(a.Port) && n.IP.Equal(ip)
} }
// Incomplete returns true for nodes with no IP address. // Incomplete returns true for nodes with no IP address.

View file

@ -138,16 +138,12 @@ type ticketStore struct {
nextTicketReg mclock.AbsTime nextTicketReg mclock.AbsTime
searchTopicMap map[Topic]searchTopic searchTopicMap map[Topic]searchTopic
searchTopicList []Topic
searchTopicPtr int
nextTopicQueryCleanup mclock.AbsTime nextTopicQueryCleanup mclock.AbsTime
queriesSent map[*Node]map[common.Hash]sentQuery queriesSent map[*Node]map[common.Hash]sentQuery
radiusLookupCnt int
} }
type searchTopic struct { type searchTopic struct {
foundChn chan<- string foundChn chan<- *Node
listIdx int
} }
type sentQuery struct { type sentQuery struct {
@ -183,23 +179,15 @@ func (s *ticketStore) addTopic(t Topic, register bool) {
} }
} }
func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- string) { func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- *Node) {
s.addTopic(t, false) s.addTopic(t, false)
if s.searchTopicMap[t].foundChn == nil { if s.searchTopicMap[t].foundChn == nil {
s.searchTopicList = append(s.searchTopicList, t) s.searchTopicMap[t] = searchTopic{foundChn: foundChn}
s.searchTopicMap[t] = searchTopic{foundChn: foundChn, listIdx: len(s.searchTopicList) - 1}
} }
} }
func (s *ticketStore) removeSearchTopic(t Topic) { func (s *ticketStore) removeSearchTopic(t Topic) {
if st := s.searchTopicMap[t]; st.foundChn != nil { if st := s.searchTopicMap[t]; st.foundChn != nil {
lastIdx := len(s.searchTopicList) - 1
lastTopic := s.searchTopicList[lastIdx]
s.searchTopicList[st.listIdx] = lastTopic
sl := s.searchTopicMap[lastTopic]
sl.listIdx = st.listIdx
s.searchTopicMap[lastTopic] = sl
s.searchTopicList = s.searchTopicList[:lastIdx]
delete(s.searchTopicMap, t) delete(s.searchTopicMap, t)
} }
} }
@ -247,20 +235,13 @@ func (s *ticketStore) nextRegisterLookup() (lookup lookupInfo, delay time.Durati
return lookupInfo{}, 40 * time.Second return lookupInfo{}, 40 * time.Second
} }
func (s *ticketStore) nextSearchLookup() lookupInfo { func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo {
if len(s.searchTopicList) == 0 { tr := s.radius[topic]
return lookupInfo{} target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery)
}
if s.searchTopicPtr >= len(s.searchTopicList) {
s.searchTopicPtr = 0
}
topic := s.searchTopicList[s.searchTopicPtr]
s.searchTopicPtr++
target := s.radius[topic].nextTarget(s.radiusLookupCnt >= searchForceQuery)
if target.radiusLookup { if target.radiusLookup {
s.radiusLookupCnt++ tr.radiusLookupCnt++
} else { } else {
s.radiusLookupCnt = 0 tr.radiusLookupCnt = 0
} }
return target return target
} }
@ -662,9 +643,9 @@ func (s *ticketStore) gotTopicNodes(from *Node, hash common.Hash, nodes []rpcNod
if ip.IsUnspecified() || ip.IsLoopback() { if ip.IsUnspecified() || ip.IsLoopback() {
ip = from.IP ip = from.IP
} }
enode := NewNode(node.ID, ip, node.UDP-1, node.TCP-1).String() // subtract one from port while discv5 is running in test mode on UDPport+1 n := NewNode(node.ID, ip, node.UDP-1, node.TCP-1) // subtract one from port while discv5 is running in test mode on UDPport+1
select { select {
case chn <- enode: case chn <- n:
default: default:
return false return false
} }
@ -677,6 +658,8 @@ type topicRadius struct {
topicHashPrefix uint64 topicHashPrefix uint64
radius, minRadius uint64 radius, minRadius uint64
buckets []topicRadiusBucket buckets []topicRadiusBucket
converged bool
radiusLookupCnt int
} }
type topicRadiusEvent int type topicRadiusEvent int
@ -706,7 +689,7 @@ func (b *topicRadiusBucket) update(now mclock.AbsTime) {
b.lastTime = now b.lastTime = now
for target, tm := range b.lookupSent { for target, tm := range b.lookupSent {
if now-tm > mclock.AbsTime(pingTimeout) { if now-tm > mclock.AbsTime(respTimeout) {
b.weights[trNoAdjust] += 1 b.weights[trNoAdjust] += 1
delete(b.lookupSent, target) delete(b.lookupSent, target)
} }
@ -906,6 +889,7 @@ func (r *topicRadius) recalcRadius() (radius uint64, radiusLookup int) {
if radiusLookup == -1 { if radiusLookup == -1 {
// no more radius lookups needed at the moment, return a radius // no more radius lookups needed at the moment, return a radius
r.converged = true
rad := maxBucket rad := maxBucket
if minRadBucket < rad { if minRadBucket < rad {
rad = minRadBucket rad = minRadBucket

View file

@ -196,7 +196,7 @@ func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint {
} }
func (e1 rpcEndpoint) equal(e2 rpcEndpoint) bool { func (e1 rpcEndpoint) equal(e2 rpcEndpoint) bool {
return e1.UDP == e2.UDP && e1.TCP == e2.TCP && bytes.Equal(e1.IP, e2.IP) return e1.UDP == e2.UDP && e1.TCP == e2.TCP && e1.IP.Equal(e2.IP)
} }
func nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) { func nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) {

View file

@ -17,7 +17,6 @@
package nat package nat
import ( import (
"bytes"
"net" "net"
"testing" "testing"
"time" "time"
@ -56,7 +55,7 @@ func TestAutoDiscRace(t *testing.T) {
t.Errorf("result %d: unexpected error: %v", i, rval.err) t.Errorf("result %d: unexpected error: %v", i, rval.err)
} }
wantIP := net.IP{33, 44, 55, 66} wantIP := net.IP{33, 44, 55, 66}
if !bytes.Equal(rval.ip, wantIP) { if !rval.ip.Equal(wantIP) {
t.Errorf("result %d: got IP %v, want %v", i, rval.ip, wantIP) t.Errorf("result %d: got IP %v, want %v", i, rval.ip, wantIP)
} }
} }

View file

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

View file

@ -51,7 +51,7 @@ const (
Version = 0 Version = 0
ProtocolLength = uint64(8) ProtocolLength = uint64(8)
ProtocolMaxMsgSize = 10 * 1024 * 1024 ProtocolMaxMsgSize = 10 * 1024 * 1024
NetworkId = 322 NetworkId = 3
) )
const ( const (

View file

@ -55,6 +55,22 @@ func APIs() []rpc.API {
} }
} }
// Start starts the Whisper worker threads.
func (api *PublicWhisperAPI) Start() error {
if api.whisper == nil {
return whisperOffLineErr
}
return api.whisper.Start(nil)
}
// Stop stops the Whisper worker threads.
func (api *PublicWhisperAPI) Stop() error {
if api.whisper == nil {
return whisperOffLineErr
}
return api.whisper.Stop()
}
// Version returns the Whisper version this node offers. // Version returns the Whisper version this node offers.
func (api *PublicWhisperAPI) Version() (*rpc.HexNumber, error) { func (api *PublicWhisperAPI) Version() (*rpc.HexNumber, error) {
if api.whisper == nil { if api.whisper == nil {

View file

@ -277,6 +277,9 @@ func TestIntegrationAsym(t *testing.T) {
t.Fatalf("failed to create API.") t.Fatalf("failed to create API.")
} }
api.Start()
defer api.Stop()
sig, err := api.NewIdentity() sig, err := api.NewIdentity()
if err != nil { if err != nil {
t.Fatalf("failed NewIdentity: %s.", err) t.Fatalf("failed NewIdentity: %s.", err)
@ -375,6 +378,9 @@ func TestIntegrationSym(t *testing.T) {
t.Fatalf("failed to create API.") t.Fatalf("failed to create API.")
} }
api.Start()
defer api.Stop()
keyname := "schluessel" keyname := "schluessel"
err := api.GenerateSymKey(keyname) err := api.GenerateSymKey(keyname)
if err != nil { if err != nil {
@ -471,6 +477,9 @@ func TestIntegrationSymWithFilter(t *testing.T) {
t.Fatalf("failed to create API.") t.Fatalf("failed to create API.")
} }
api.Start()
defer api.Stop()
keyname := "schluessel" keyname := "schluessel"
err := api.GenerateSymKey(keyname) err := api.GenerateSymKey(keyname)
if err != nil { if err != nil {

View file

@ -15,9 +15,7 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
/* /*
Package whisper implements the Whisper PoC-1. Package whisper implements the Whisper protocol (version 5).
(https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec)
Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP). Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP).
As such it may be likened and compared to both, not dissimilar to the As such it may be likened and compared to both, not dissimilar to the
@ -42,11 +40,11 @@ const (
ProtocolVersionStr = "5.0" ProtocolVersionStr = "5.0"
ProtocolName = "shh" ProtocolName = "shh"
statusCode = 0 statusCode = 0 // used by whisper protocol
messagesCode = 1 messagesCode = 1 // normal whisper message
p2pCode = 2 p2pCode = 2 // peer-to-peer message (to be consumed by the peer, but not forwarded any futher)
mailRequestCode = 3 p2pRequestCode = 3 // peer-to-peer message, used by Dapp protocol
NumberOfMessageCodes = 32 NumberOfMessageCodes = 64
paddingMask = byte(3) paddingMask = byte(3)
signatureFlag = byte(4) signatureFlag = byte(4)
@ -57,11 +55,12 @@ const (
saltLength = 12 saltLength = 12
AESNonceMaxLength = 12 AESNonceMaxLength = 12
MaxMessageLength = 0xFFFF // todo: remove this restriction after testing in morden and analizing stats. this should be regulated by MinimumPoW. MaxMessageLength = 0xFFFF // todo: remove this restriction after testing. this should be regulated by PoW.
MinimumPoW = 10.0 // todo: review MinimumPoW = 1.0 // todo: review after testing.
padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature
padSizeLimitUpper = 256 // just an arbitrary number, could be changed without losing compatibility padSizeLimitUpper = 256 // just an arbitrary number, could be changed without losing compatibility
messageQueueLimit = 1024
expirationCycle = time.Second expirationCycle = time.Second
transmissionCycle = 300 * time.Millisecond transmissionCycle = 300 * time.Millisecond

View file

@ -14,14 +14,14 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains the Whisper protocol Envelope element. For formal details please see // Contains the Whisper protocol Envelope element.
// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#envelopes.
package whisperv5 package whisperv5
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"math" "math"
"time" "time"
@ -86,8 +86,8 @@ func (e *Envelope) Ver() uint64 {
// Seal closes the envelope by spending the requested amount of time as a proof // Seal closes the envelope by spending the requested amount of time as a proof
// of work on hashing the data. // of work on hashing the data.
func (e *Envelope) Seal(options *MessageParams) { func (e *Envelope) Seal(options *MessageParams) error {
var target int var target, bestBit int
if options.PoW == 0 { if options.PoW == 0 {
// adjust for the duration of Seal() execution only if execution time is predefined unconditionally // adjust for the duration of Seal() execution only if execution time is predefined unconditionally
e.Expiry += options.WorkTime e.Expiry += options.WorkTime
@ -99,7 +99,7 @@ func (e *Envelope) Seal(options *MessageParams) {
h := crypto.Keccak256(e.rlpWithoutNonce()) h := crypto.Keccak256(e.rlpWithoutNonce())
copy(buf[:32], h) copy(buf[:32], h)
finish, bestBit := time.Now().Add(time.Duration(options.WorkTime)*time.Second).UnixNano(), 0 finish := time.Now().Add(time.Duration(options.WorkTime) * time.Second).UnixNano()
for nonce := uint64(0); time.Now().UnixNano() < finish; { for nonce := uint64(0); time.Now().UnixNano() < finish; {
for i := 0; i < 1024; i++ { for i := 0; i < 1024; i++ {
binary.BigEndian.PutUint64(buf[56:], nonce) binary.BigEndian.PutUint64(buf[56:], nonce)
@ -108,12 +108,18 @@ func (e *Envelope) Seal(options *MessageParams) {
if firstBit > bestBit { if firstBit > bestBit {
e.EnvNonce, bestBit = nonce, firstBit e.EnvNonce, bestBit = nonce, firstBit
if target > 0 && bestBit >= target { if target > 0 && bestBit >= target {
return return nil
} }
} }
nonce++ nonce++
} }
} }
if target > 0 && bestBit < target {
return errors.New("Failed to reach the PoW target")
}
return nil
} }
func (e *Envelope) PoW() float64 { func (e *Envelope) PoW() float64 {

View file

@ -21,6 +21,8 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
) )
type Filter struct { type Filter struct {
@ -75,11 +77,12 @@ func (fs *Filters) Get(i uint32) *Filter {
return fs.watchers[i] return fs.watchers[i]
} }
func (fs *Filters) NotifyWatchers(env *Envelope, messageCode uint64) { func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
fs.mutex.RLock() fs.mutex.RLock()
var msg *ReceivedMessage var msg *ReceivedMessage
for _, watcher := range fs.watchers { for j, watcher := range fs.watchers {
if messageCode == p2pCode && !watcher.AcceptP2P { if p2pMessage && !watcher.AcceptP2P {
glog.V(logger.Detail).Infof("msg [%x], filter [%d]: p2p messages are not allowed \n", env.Hash(), j)
continue continue
} }
@ -90,6 +93,11 @@ func (fs *Filters) NotifyWatchers(env *Envelope, messageCode uint64) {
match = watcher.MatchEnvelope(env) match = watcher.MatchEnvelope(env)
if match { if match {
msg = env.Open(watcher) msg = env.Open(watcher)
if msg == nil {
glog.V(logger.Detail).Infof("msg [%x], filter [%d]: failed to open \n", env.Hash(), j)
}
} else {
glog.V(logger.Detail).Infof("msg [%x], filter [%d]: does not match \n", env.Hash(), j)
} }
} }
@ -137,12 +145,12 @@ func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
if f.PoW > 0 && msg.PoW < f.PoW { if f.PoW > 0 && msg.PoW < f.PoW {
return false return false
} }
if f.Src != nil && !isPubKeyEqual(msg.Src, f.Src) { if f.Src != nil && !IsPubKeyEqual(msg.Src, f.Src) {
return false return false
} }
if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() { if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() {
return isPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst) && f.MatchTopic(msg.Topic) return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst) && f.MatchTopic(msg.Topic)
} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() { } else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
return f.SymKeyHash == msg.SymKeyHash && f.MatchTopic(msg.Topic) return f.SymKeyHash == msg.SymKeyHash && f.MatchTopic(msg.Topic)
} }
@ -176,7 +184,7 @@ func (f *Filter) MatchTopic(topic TopicType) bool {
return false return false
} }
func isPubKeyEqual(a, b *ecdsa.PublicKey) bool { func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
if !ValidatePublicKey(a) { if !ValidatePublicKey(a) {
return false return false
} else if !ValidatePublicKey(b) { } else if !ValidatePublicKey(b) {

View file

@ -139,7 +139,7 @@ func TestComparePubKey(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to generate second key with seed %d: %s.", seed, err) t.Fatalf("failed to generate second key with seed %d: %s.", seed, err)
} }
if isPubKeyEqual(&key1.PublicKey, &key2.PublicKey) { if IsPubKeyEqual(&key1.PublicKey, &key2.PublicKey) {
t.Fatalf("public keys are equal, seed %d.", seed) t.Fatalf("public keys are equal, seed %d.", seed)
} }
@ -149,7 +149,7 @@ func TestComparePubKey(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to generate third key with seed %d: %s.", seed, err) t.Fatalf("failed to generate third key with seed %d: %s.", seed, err)
} }
if isPubKeyEqual(&key1.PublicKey, &key3.PublicKey) { if IsPubKeyEqual(&key1.PublicKey, &key3.PublicKey) {
t.Fatalf("key1 == key3, seed %d.", seed) t.Fatalf("key1 == key3, seed %d.", seed)
} }
} }
@ -540,7 +540,7 @@ func TestWatchers(t *testing.T) {
} }
for i = 0; i < NumMessages; i++ { for i = 0; i < NumMessages; i++ {
filters.NotifyWatchers(envelopes[i], messagesCode) filters.NotifyWatchers(envelopes[i], false)
} }
var total int var total int
@ -593,7 +593,7 @@ func TestWatchers(t *testing.T) {
} }
for i = 0; i < NumMessages; i++ { for i = 0; i < NumMessages; i++ {
filters.NotifyWatchers(envelopes[i], messagesCode) filters.NotifyWatchers(envelopes[i], false)
} }
for i = 0; i < NumFilters; i++ { for i = 0; i < NumFilters; i++ {
@ -629,7 +629,7 @@ func TestWatchers(t *testing.T) {
// test AcceptP2P // test AcceptP2P
total = 0 total = 0
filters.NotifyWatchers(envelopes[0], p2pCode) filters.NotifyWatchers(envelopes[0], true)
for i = 0; i < NumFilters; i++ { for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve() mail = tst[i].f.Retrieve()
@ -646,7 +646,7 @@ func TestWatchers(t *testing.T) {
} }
f.AcceptP2P = true f.AcceptP2P = true
total = 0 total = 0
filters.NotifyWatchers(envelopes[0], p2pCode) filters.NotifyWatchers(envelopes[0], true)
for i = 0; i < NumFilters; i++ { for i = 0; i < NumFilters; i++ {
mail = tst[i].f.Retrieve() mail = tst[i].f.Retrieve()

View file

@ -14,9 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains the Whisper protocol Message element. For formal details please see // Contains the Whisper protocol Message element.
// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#messages.
// todo: fix the spec link, and move it to doc.go
package whisperv5 package whisperv5
@ -256,7 +254,11 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
} }
envelope = NewEnvelope(options.TTL, options.Topic, salt, nonce, msg) envelope = NewEnvelope(options.TTL, options.Topic, salt, nonce, msg)
envelope.Seal(options) err = envelope.Seal(options)
if err != nil {
return nil, err
}
return envelope, nil return envelope, nil
} }

View file

@ -30,11 +30,15 @@ func copyFromBuf(dst []byte, src []byte, beg int) int {
} }
func generateMessageParams() (*MessageParams, error) { func generateMessageParams() (*MessageParams, error) {
// set all the parameters except p.Dst
buf := make([]byte, 1024) buf := make([]byte, 1024)
randomize(buf) randomize(buf)
sz := rand.Intn(400) sz := rand.Intn(400)
var p MessageParams var p MessageParams
p.PoW = 0.01
p.WorkTime = 1
p.TTL = uint32(rand.Intn(1024)) p.TTL = uint32(rand.Intn(1024))
p.Payload = make([]byte, sz) p.Payload = make([]byte, sz)
p.Padding = make([]byte, padSizeLimitUpper) p.Padding = make([]byte, padSizeLimitUpper)
@ -52,8 +56,6 @@ func generateMessageParams() (*MessageParams, error) {
return nil, err return nil, err
} }
// p.Dst, p.PoW, p.WorkTime are not set
p.PoW = 0.01
return &p, nil return &p, nil
} }
@ -114,7 +116,7 @@ func singleMessageTest(t *testing.T, symmetric bool) {
if len(decrypted.Signature) != signatureLength { if len(decrypted.Signature) != signatureLength {
t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature)) t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature))
} }
if !isPubKeyEqual(decrypted.Src, &params.Src.PublicKey) { if !IsPubKeyEqual(decrypted.Src, &params.Src.PublicKey) {
t.Fatalf("failed with seed %d: signature mismatch.", seed) t.Fatalf("failed with seed %d: signature mismatch.", seed)
} }
} }
@ -152,6 +154,16 @@ func TestMessageWrap(t *testing.T) {
if pow < target { if pow < target {
t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target) t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target)
} }
// set PoW target too high, expect error
msg2 := NewSentMessage(params)
params.TTL = 1000000
params.WorkTime = 1
params.PoW = 10000000.0
env, err = msg2.Wrap(params)
if err == nil {
t.Fatalf("unexpectedly reached the PoW target with seed %d.", seed)
}
} }
func TestMessageSeal(t *testing.T) { func TestMessageSeal(t *testing.T) {
@ -256,7 +268,7 @@ func singleEnvelopeOpenTest(t *testing.T, symmetric bool) {
if len(decrypted.Signature) != signatureLength { if len(decrypted.Signature) != signatureLength {
t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature)) t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature))
} }
if !isPubKeyEqual(decrypted.Src, &params.Src.PublicKey) { if !IsPubKeyEqual(decrypted.Src, &params.Src.PublicKey) {
t.Fatalf("failed with seed %d: signature mismatch.", seed) t.Fatalf("failed with seed %d: signature mismatch.", seed)
} }
if decrypted.isAsymmetricEncryption() == symmetric { if decrypted.isAsymmetricEncryption() == symmetric {
@ -269,8 +281,37 @@ func singleEnvelopeOpenTest(t *testing.T, symmetric bool) {
if decrypted.Dst == nil { if decrypted.Dst == nil {
t.Fatalf("failed with seed %d: dst is nil.", seed) t.Fatalf("failed with seed %d: dst is nil.", seed)
} }
if !isPubKeyEqual(decrypted.Dst, &key.PublicKey) { if !IsPubKeyEqual(decrypted.Dst, &key.PublicKey) {
t.Fatalf("failed with seed %d: Dst.", seed) t.Fatalf("failed with seed %d: Dst.", seed)
} }
} }
} }
func TestEncryptWithZeroKey(t *testing.T) {
InitSingleTest()
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
msg := NewSentMessage(params)
params.KeySym = make([]byte, aesKeyLength)
_, err = msg.Wrap(params)
if err == nil {
t.Fatalf("wrapped with zero key, seed: %d.", seed)
}
params.KeySym = make([]byte, 0)
_, err = msg.Wrap(params)
if err == nil {
t.Fatalf("wrapped with empty key, seed: %d.", seed)
}
params.KeySym = nil
_, err = msg.Wrap(params)
if err == nil {
t.Fatalf("wrapped with nil key, seed: %d.", seed)
}
}

View file

@ -118,6 +118,7 @@ func initialize(t *testing.T) {
var node TestNode var node TestNode
node.shh = NewWhisper(nil) node.shh = NewWhisper(nil)
node.shh.test = true node.shh.test = true
node.shh.Start(nil)
topics := make([]TopicType, 0) topics := make([]TopicType, 0)
topics = append(topics, sharedTopic) topics = append(topics, sharedTopic)
f := Filter{KeySym: sharedKey, Topics: topics} f := Filter{KeySym: sharedKey, Topics: topics}
@ -166,6 +167,7 @@ func stopServers() {
n := nodes[i] n := nodes[i]
if n != nil { if n != nil {
n.shh.Unwatch(n.filerId) n.shh.Unwatch(n.filerId)
n.shh.Stop()
n.server.Stop() n.server.Stop()
} }
} }

View file

@ -14,8 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains the Whisper protocol Topic element. For formal details please see // Contains the Whisper protocol Topic element.
// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#topics.
package whisperv5 package whisperv5

View file

@ -22,6 +22,7 @@ import (
crand "crypto/rand" crand "crypto/rand"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"runtime"
"sync" "sync"
"time" "time"
@ -45,7 +46,7 @@ type Whisper struct {
symKeys map[string][]byte symKeys map[string][]byte
keyMu sync.RWMutex keyMu sync.RWMutex
envelopes map[common.Hash]*Envelope // Pool of messages currently tracked by this node envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node
messages map[common.Hash]*ReceivedMessage // Pool of successfully decrypted messages, which are not expired yet messages map[common.Hash]*ReceivedMessage // Pool of successfully decrypted messages, which are not expired yet
expirations map[uint32]*set.SetNonTS // Message expiration pool expirations map[uint32]*set.SetNonTS // Message expiration pool
poolMu sync.RWMutex // Mutex to sync the message and expiration pools poolMu sync.RWMutex // Mutex to sync the message and expiration pools
@ -55,7 +56,11 @@ type Whisper struct {
mailServer MailServer mailServer MailServer
messageQueue chan *Envelope
p2pMsgQueue chan *Envelope
quit chan struct{} quit chan struct{}
overflow bool
test bool test bool
} }
@ -70,6 +75,8 @@ func NewWhisper(server MailServer) *Whisper {
expirations: make(map[uint32]*set.SetNonTS), expirations: make(map[uint32]*set.SetNonTS),
peers: make(map[*Peer]struct{}), peers: make(map[*Peer]struct{}),
mailServer: server, mailServer: server,
messageQueue: make(chan *Envelope, messageQueueLimit),
p2pMsgQueue: make(chan *Envelope, messageQueueLimit),
quit: make(chan struct{}), quit: make(chan struct{}),
} }
whisper.filters = NewFilters(whisper) whisper.filters = NewFilters(whisper)
@ -124,7 +131,7 @@ func (w *Whisper) RequestHistoricMessages(peerID []byte, data []byte) error {
return err return err
} }
p.trusted = true p.trusted = true
return p2p.Send(p.ws, mailRequestCode, data) return p2p.Send(p.ws, p2pRequestCode, data)
} }
func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error { func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error {
@ -270,6 +277,12 @@ func (w *Whisper) Send(envelope *Envelope) error {
func (w *Whisper) Start(*p2p.Server) error { func (w *Whisper) Start(*p2p.Server) error {
glog.V(logger.Info).Infoln("Whisper started") glog.V(logger.Info).Infoln("Whisper started")
go w.update() go w.update()
numCPU := runtime.NumCPU()
for i := 0; i < numCPU; i++ {
go w.processQueue()
}
return nil return nil
} }
@ -350,10 +363,10 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
return fmt.Errorf("garbage received (directMessage)") return fmt.Errorf("garbage received (directMessage)")
} }
for _, envelope := range envelopes { for _, envelope := range envelopes {
wh.postEvent(envelope, p2pCode) wh.postEvent(envelope, true)
} }
} }
case mailRequestCode: case p2pRequestCode:
// Must be processed if mail server is implemented. Otherwise ignore. // Must be processed if mail server is implemented. Otherwise ignore.
if wh.mailServer != nil { if wh.mailServer != nil {
s := rlp.NewStream(packet.Payload, uint64(packet.Size)) s := rlp.NewStream(packet.Payload, uint64(packet.Size))
@ -382,7 +395,7 @@ func (wh *Whisper) add(envelope *Envelope) error {
if sent > now { if sent > now {
if sent-SynchAllowance > now { if sent-SynchAllowance > now {
return fmt.Errorf("message created in the future") return fmt.Errorf("envelope created in the future [%x]", envelope.Hash())
} else { } else {
// recalculate PoW, adjusted for the time difference, plus one second for latency // recalculate PoW, adjusted for the time difference, plus one second for latency
envelope.calculatePoW(sent - now + 1) envelope.calculatePoW(sent - now + 1)
@ -393,30 +406,31 @@ func (wh *Whisper) add(envelope *Envelope) error {
if envelope.Expiry+SynchAllowance*2 < now { if envelope.Expiry+SynchAllowance*2 < now {
return fmt.Errorf("very old message") return fmt.Errorf("very old message")
} else { } else {
glog.V(logger.Debug).Infof("expired envelope dropped [%x]", envelope.Hash())
return nil // drop envelope without error return nil // drop envelope without error
} }
} }
if len(envelope.Data) > MaxMessageLength { if len(envelope.Data) > MaxMessageLength {
return fmt.Errorf("huge messages are not allowed") return fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash())
} }
if len(envelope.Version) > 4 { if len(envelope.Version) > 4 {
return fmt.Errorf("oversized Version") return fmt.Errorf("oversized version [%x]", envelope.Hash())
} }
if len(envelope.AESNonce) > AESNonceMaxLength { if len(envelope.AESNonce) > AESNonceMaxLength {
// the standard AES GSM nonce size is 12, // the standard AES GSM nonce size is 12,
// but const gcmStandardNonceSize cannot be accessed directly // but const gcmStandardNonceSize cannot be accessed directly
return fmt.Errorf("oversized AESNonce") return fmt.Errorf("oversized AESNonce [%x]", envelope.Hash())
} }
if len(envelope.Salt) > saltLength { if len(envelope.Salt) > saltLength {
return fmt.Errorf("oversized Salt") return fmt.Errorf("oversized salt [%x]", envelope.Hash())
} }
if envelope.PoW() < MinimumPoW && !wh.test { if envelope.PoW() < MinimumPoW && !wh.test {
glog.V(logger.Debug).Infof("envelope with low PoW dropped: %f", envelope.PoW()) glog.V(logger.Debug).Infof("envelope with low PoW dropped: %f [%x]", envelope.PoW(), envelope.Hash())
return nil // drop envelope without error return nil // drop envelope without error
} }
@ -436,22 +450,59 @@ func (wh *Whisper) add(envelope *Envelope) error {
wh.poolMu.Unlock() wh.poolMu.Unlock()
if alreadyCached { if alreadyCached {
glog.V(logger.Detail).Infof("whisper envelope already cached: %x\n", envelope) glog.V(logger.Detail).Infof("whisper envelope already cached [%x]\n", envelope.Hash())
} else { } else {
wh.postEvent(envelope, messagesCode) // notify the local node about the new message glog.V(logger.Detail).Infof("cached whisper envelope [%x]: %v\n", envelope.Hash(), envelope)
glog.V(logger.Detail).Infof("cached whisper envelope %v\n", envelope) wh.postEvent(envelope, false) // notify the local node about the new message
} }
return nil return nil
} }
// postEvent delivers the message to the watchers. // postEvent queues the message for further processing.
func (w *Whisper) postEvent(envelope *Envelope, messageCode uint64) { func (w *Whisper) postEvent(envelope *Envelope, isP2P bool) {
// if the version of incoming message is higher than // if the version of incoming message is higher than
// currently supported version, we can not decrypt it, // currently supported version, we can not decrypt it,
// and therefore just ignore this message // and therefore just ignore this message
if envelope.Ver() <= EnvelopeVersion { if envelope.Ver() <= EnvelopeVersion {
// todo: review if you need an additional thread here if isP2P {
go w.filters.NotifyWatchers(envelope, messageCode) w.p2pMsgQueue <- envelope
} else {
w.checkOverflow()
w.messageQueue <- envelope
}
}
}
// checkOverflow checks if message queue overflow occurs and reports it if necessary.
func (w *Whisper) checkOverflow() {
queueSize := len(w.messageQueue)
if queueSize == messageQueueLimit {
if !w.overflow {
w.overflow = true
glog.V(logger.Warn).Infoln("message queue overflow")
}
} else if queueSize <= messageQueueLimit/2 {
if w.overflow {
w.overflow = false
}
}
}
// processQueue delivers the messages to the watchers during the lifetime of the whisper node.
func (w *Whisper) processQueue() {
var e *Envelope
for {
select {
case <-w.quit:
return
case e = <-w.messageQueue:
w.filters.NotifyWatchers(e, false)
case e = <-w.p2pMsgQueue:
w.filters.NotifyWatchers(e, true)
}
} }
} }

View file

@ -19,6 +19,7 @@ package whisperv5
import ( import (
"bytes" "bytes"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -309,3 +310,56 @@ func TestWhisperSymKeyManagement(t *testing.T) {
t.Fatalf("failed to delete second key: second key is not nil.") t.Fatalf("failed to delete second key: second key is not nil.")
} }
} }
func TestExpiry(t *testing.T) {
InitSingleTest()
w := NewWhisper(nil)
w.test = true
w.Start(nil)
defer w.Stop()
params, err := generateMessageParams()
if err != nil {
t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
}
params.TTL = 1
msg := NewSentMessage(params)
env, err := msg.Wrap(params)
if err != nil {
t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
}
err = w.Send(env)
if err != nil {
t.Fatalf("failed to send envelope with seed %d: %s.", seed, err)
}
// wait till received or timeout
var received, expired bool
for j := 0; j < 20; j++ {
time.Sleep(100 * time.Millisecond)
if len(w.Envelopes()) > 0 {
received = true
break
}
}
if !received {
t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
}
// wait till expired or timeout
for j := 0; j < 20; j++ {
time.Sleep(100 * time.Millisecond)
if len(w.Envelopes()) == 0 {
expired = true
break
}
}
if !expired {
t.Fatalf("expire failed, seed: %d.", seed)
}
}