aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml8
-rw-r--r--README.md4
-rw-r--r--VERSION2
-rw-r--r--accounts/abi/bind/backends/simulated.go6
-rw-r--r--appveyor.yml4
-rw-r--r--build/ci.go22
-rw-r--r--build/nsis.geth.nsi5
-rw-r--r--build/nsis.install.nsh5
-rw-r--r--build/nsis.pathupdate.nsh153
-rw-r--r--build/nsis.uninstall.nsh3
-rw-r--r--cmd/evm/main.go139
-rw-r--r--cmd/geth/library.c24
-rw-r--r--cmd/geth/library.go46
-rw-r--r--cmd/geth/library_android.go56
-rw-r--r--cmd/swarm/hash.go (renamed from cmd/bzzhash/main.go)19
-rw-r--r--cmd/swarm/main.go (renamed from cmd/bzzd/main.go)105
-rw-r--r--cmd/swarm/upload.go (renamed from cmd/bzzup/main.go)108
-rw-r--r--cmd/utils/cmd.go31
-rw-r--r--core/blockchain.go61
-rw-r--r--core/events.go1
-rw-r--r--core/evm.go73
-rw-r--r--core/execution.go217
-rw-r--r--core/headerchain.go11
-rw-r--r--core/state/state_object.go2
-rw-r--r--core/state/statedb.go9
-rw-r--r--core/state_processor.go22
-rw-r--r--core/state_transition.go51
-rw-r--r--core/tx_pool.go87
-rw-r--r--core/tx_pool_test.go99
-rw-r--r--core/vm/contract.go13
-rw-r--r--core/vm/environment.go363
-rw-r--r--core/vm/errors.go11
-rw-r--r--core/vm/instructions.go202
-rw-r--r--core/vm/interface.go97
-rw-r--r--core/vm/jit.go18
-rw-r--r--core/vm/jit_test.go66
-rw-r--r--core/vm/log.go142
-rw-r--r--core/vm/log_test.go73
-rw-r--r--core/vm/logger.go8
-rw-r--r--core/vm/logger_test.go29
-rw-r--r--core/vm/noop.go68
-rw-r--r--core/vm/runtime/env.go98
-rw-r--r--core/vm/runtime/runtime.go29
-rw-r--r--core/vm/segments.go4
-rw-r--r--core/vm/util_test.go32
-rw-r--r--core/vm/vm.go60
-rw-r--r--core/vm/vm_jit_fake.go7
-rw-r--r--core/vm_env.go101
-rw-r--r--eth/api.go27
-rw-r--r--eth/api_backend.go17
-rw-r--r--eth/backend.go2
-rw-r--r--eth/filters/api.go19
-rw-r--r--eth/filters/filter.go27
-rw-r--r--eth/filters/filter_system.go76
-rw-r--r--eth/filters/filter_system_test.go50
-rw-r--r--eth/handler.go4
-rw-r--r--eth/helper_test.go8
-rw-r--r--eth/protocol.go4
-rw-r--r--eth/sync.go12
-rw-r--r--ethclient/ethclient.go56
-rw-r--r--ethclient/ethclient_test.go2
-rw-r--r--ethstats/ethstats.go235
-rw-r--r--interfaces.go39
-rw-r--r--internal/ethapi/api.go59
-rw-r--r--internal/ethapi/backend.go4
-rw-r--r--internal/ethapi/tracer.go6
-rw-r--r--internal/ethapi/tracer_test.go65
-rw-r--r--les/api_backend.go10
-rw-r--r--les/fetcher.go766
-rw-r--r--les/handler.go117
-rw-r--r--les/helper_test.go11
-rw-r--r--les/odr.go73
-rw-r--r--les/odr_peerset.go120
-rw-r--r--les/odr_requests.go29
-rw-r--r--les/odr_test.go23
-rw-r--r--les/peer.go84
-rw-r--r--les/randselect.go173
-rw-r--r--les/randselect_test.go67
-rw-r--r--les/request_test.go7
-rw-r--r--les/server.go37
-rw-r--r--les/serverpool.go766
-rw-r--r--light/lightchain.go11
-rw-r--r--light/odr.go15
-rw-r--r--light/odr_test.go13
-rw-r--r--light/odr_util.go5
-rw-r--r--light/state.go9
-rw-r--r--light/state_object.go2
-rw-r--r--light/txpool.go17
-rw-r--r--light/vm_env.go119
-rw-r--r--miner/miner.go16
-rw-r--r--miner/remote_agent.go25
-rw-r--r--miner/unconfirmed.go118
-rw-r--r--miner/unconfirmed_test.go85
-rw-r--r--miner/worker.go103
-rw-r--r--mobile/accounts.go44
-rw-r--r--mobile/big.go2
-rw-r--r--mobile/bind.go22
-rw-r--r--mobile/common.go20
-rw-r--r--mobile/discover.go4
-rw-r--r--mobile/doc.go4
-rw-r--r--mobile/ethclient.go157
-rw-r--r--mobile/ethereum.go2
-rw-r--r--mobile/geth.go14
-rw-r--r--mobile/interface.go2
-rw-r--r--mobile/p2p.go2
-rw-r--r--mobile/primitives.go2
-rw-r--r--mobile/types.go20
-rw-r--r--mobile/vm.go2
-rw-r--r--p2p/discover/udp_test.go2
-rw-r--r--p2p/discv5/net.go121
-rw-r--r--p2p/discv5/node.go3
-rw-r--r--p2p/discv5/ticket.go44
-rw-r--r--p2p/discv5/udp.go2
-rw-r--r--p2p/nat/nat_test.go3
-rw-r--r--params/version.go2
-rw-r--r--swarm/network/protocol.go2
-rw-r--r--tests/state_test_util.go29
-rw-r--r--tests/util.go171
-rw-r--r--tests/vm_test_util.go27
119 files changed, 4414 insertions, 2616 deletions
diff --git a/.travis.yml b/.travis.yml
index 87725251b..d13b77c17 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,15 +13,15 @@ matrix:
go: 1.6.2
- os: linux
dist: trusty
- go: 1.7
+ go: 1.7.4
- os: osx
- go: 1.7
+ go: 1.7.4
# This builder does the Ubuntu PPA and Linux Azure uploads
- os: linux
dist: trusty
sudo: required
- go: 1.7
+ go: 1.7.4
env:
- ubuntu-ppa
- azure-linux
@@ -55,7 +55,7 @@ matrix:
# This builder does the OSX Azure, Android Maven and Azure and iOS CocoaPods and Azure uploads
- os: osx
- go: 1.7
+ go: 1.7.4
env:
- azure-osx
- mobile
diff --git a/README.md b/README.md
index 770546010..66f59470f 100644
--- a/README.md
+++ b/README.md
@@ -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`). |
| `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`). |
-| `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. |
-| `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 |
+| `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. |
## Running geth
diff --git a/VERSION b/VERSION
index 9075be495..eac1e0ada 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.5.5
+1.5.6
diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index 00a8cd3e9..06bd13cae 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -225,7 +225,11 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
from.SetBalance(common.MaxBig)
// Execute the call.
msg := callmsg{call}
- vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{})
+
+ evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain)
+ // Create a new environment which holds all relevant information
+ // about the transaction and calling mechanisms.
+ vmenv := vm.NewEnvironment(evmContext, statedb, chainConfig, vm.Config{})
gaspool := new(core.GasPool).AddGas(common.MaxBig)
ret, gasUsed, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
return ret, gasUsed, err
diff --git a/appveyor.yml b/appveyor.yml
index dbdda9b6c..f5115f6f9 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -22,8 +22,8 @@ environment:
install:
- rmdir C:\go /s /q
- - appveyor DownloadFile https://storage.googleapis.com/golang/go1.7.3.windows-%GETH_ARCH%.zip
- - 7z x go1.7.3.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
+ - appveyor DownloadFile https://storage.googleapis.com/golang/go1.7.4.windows-%GETH_ARCH%.zip
+ - 7z x go1.7.4.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
- go version
- gcc --version
diff --git a/build/ci.go b/build/ci.go
index c202480b2..867fc3732 100644
--- a/build/ci.go
+++ b/build/ci.go
@@ -72,9 +72,7 @@ var (
executablePath("abigen"),
executablePath("evm"),
executablePath("geth"),
- executablePath("bzzd"),
- executablePath("bzzhash"),
- executablePath("bzzup"),
+ executablePath("swarm"),
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.",
},
{
- Name: "bzzd",
- Description: "Ethereum Swarm daemon",
- },
- {
- Name: "bzzup",
- Description: "Ethereum Swarm command line file/directory uploader",
- },
- {
- Name: "bzzhash",
- Description: "Ethereum Swarm file/directory hash calculator",
+ Name: "swarm",
+ Description: "Ethereum Swarm daemon and tools",
},
{
Name: "abigen",
@@ -112,7 +102,8 @@ var (
// Distros for which packages are created.
// 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"))
@@ -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.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.pathupdate.nsh", filepath.Join(*workdir, "PathUpdate.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, "COPYING"), "COPYING", 0755)
@@ -800,7 +792,7 @@ func doXCodeFramework(cmdline []string) {
// Build the iOS XCode framework
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile"))
build.MustRun(gomobileTool("init"))
- bind := gomobileTool("bind", "--target", "ios", "--tags", "ios", "--prefix", "GE", "-v", "github.com/ethereum/go-ethereum/mobile")
+ bind := gomobileTool("bind", "--target", "ios", "--tags", "ios", "--prefix", "i", "-v", "github.com/ethereum/go-ethereum/mobile")
if *local {
// If we're building locally, use the build folder and stop afterwards
diff --git a/build/nsis.geth.nsi b/build/nsis.geth.nsi
index dbeb9319c..1034f3023 100644
--- a/build/nsis.geth.nsi
+++ b/build/nsis.geth.nsi
@@ -17,8 +17,12 @@
#
# Requirements:
# - 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)
#
+# 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
#
# TODO:
@@ -37,6 +41,7 @@ RequestExecutionLevel admin
SetCompressor /SOLID lzma
!include LogicLib.nsh
+!include PathUpdate.nsh
!include EnvVarUpdate.nsh
!macro VerifyUserIsAdmin
diff --git a/build/nsis.install.nsh b/build/nsis.install.nsh
index f9ad8e95e..57ef5a37c 100644
--- a/build/nsis.install.nsh
+++ b/build/nsis.install.nsh
@@ -37,8 +37,9 @@ Section "Geth" GETH_IDX
${EnvVarUpdate} $0 "ETHEREUM_SOCKET" "R" "HKLM" "\\.\pipe\geth.ipc"
${EnvVarUpdate} $0 "ETHEREUM_SOCKET" "A" "HKLM" "\\.\pipe\geth.ipc"
- # Add geth to PATH
- ${EnvVarUpdate} $0 "PATH" "A" "HKLM" $INSTDIR
+ # Add instdir to PATH
+ Push "$INSTDIR"
+ Call AddToPath
SectionEnd
# Install optional develop tools.
diff --git a/build/nsis.pathupdate.nsh b/build/nsis.pathupdate.nsh
new file mode 100644
index 000000000..f54b7e3e1
--- /dev/null
+++ b/build/nsis.pathupdate.nsh
@@ -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
+
+
diff --git a/build/nsis.uninstall.nsh b/build/nsis.uninstall.nsh
index ea7d5e298..6358faa74 100644
--- a/build/nsis.uninstall.nsh
+++ b/build/nsis.uninstall.nsh
@@ -25,7 +25,8 @@ Section "Uninstall"
${un.EnvVarUpdate} $0 "ETHEREUM_SOCKET" "R" "HKLM" "\\.\pipe\geth.ipc"
# Remove install directory from PATH
- ${un.EnvVarUpdate} $0 "PATH" "R" "HKLM" $INSTDIR
+ Push "$INSTDIR"
+ Call un.RemoveFromPath
# Cleanup registry (deletes all sub keys)
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}"
diff --git a/cmd/evm/main.go b/cmd/evm/main.go
index 2c4329fa5..993dd7659 100644
--- a/cmd/evm/main.go
+++ b/cmd/evm/main.go
@@ -20,21 +20,18 @@ package main
import (
"fmt"
"io/ioutil"
- "math/big"
"os"
- "runtime"
+ goruntime "runtime"
"time"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/core/vm/runtime"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/logger/glog"
- "github.com/ethereum/go-ethereum/params"
"gopkg.in/urfave/cli.v1"
)
@@ -129,13 +126,6 @@ func run(ctx *cli.Context) error {
logger := vm.NewStructLogger(nil)
- vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)), vm.Config{
- Debug: ctx.GlobalBool(DebugFlag.Name),
- ForceJit: ctx.GlobalBool(ForceJitFlag.Name),
- EnableJit: !ctx.GlobalBool(DisableJitFlag.Name),
- Tracer: logger,
- })
-
tstart := time.Now()
var (
@@ -168,25 +158,30 @@ func run(ctx *cli.Context) error {
if ctx.GlobalBool(CreateFlag.Name) {
input := append(code, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name))...)
- ret, _, err = vmenv.Create(
- sender,
- input,
- common.Big(ctx.GlobalString(GasFlag.Name)),
- common.Big(ctx.GlobalString(PriceFlag.Name)),
- common.Big(ctx.GlobalString(ValueFlag.Name)),
- )
+ ret, _, err = runtime.Create(input, &runtime.Config{
+ Origin: sender.Address(),
+ State: statedb,
+ GasLimit: common.Big(ctx.GlobalString(GasFlag.Name)),
+ GasPrice: common.Big(ctx.GlobalString(PriceFlag.Name)),
+ Value: common.Big(ctx.GlobalString(ValueFlag.Name)),
+ EVMConfig: vm.Config{
+ Tracer: logger,
+ },
+ })
} else {
receiver := statedb.CreateAccount(common.StringToAddress("receiver"))
-
receiver.SetCode(crypto.Keccak256Hash(code), code)
- ret, err = vmenv.Call(
- sender,
- receiver.Address(),
- common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)),
- common.Big(ctx.GlobalString(GasFlag.Name)),
- common.Big(ctx.GlobalString(PriceFlag.Name)),
- common.Big(ctx.GlobalString(ValueFlag.Name)),
- )
+
+ ret, err = runtime.Call(receiver.Address(), common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), &runtime.Config{
+ Origin: sender.Address(),
+ State: statedb,
+ GasLimit: common.Big(ctx.GlobalString(GasFlag.Name)),
+ GasPrice: common.Big(ctx.GlobalString(PriceFlag.Name)),
+ Value: common.Big(ctx.GlobalString(ValueFlag.Name)),
+ EVMConfig: vm.Config{
+ Tracer: logger,
+ },
+ })
}
vmdone := time.Since(tstart)
@@ -197,8 +192,8 @@ func run(ctx *cli.Context) error {
vm.StdErrFormat(logger.StructLogs())
if ctx.GlobalBool(SysStatFlag.Name) {
- var mem runtime.MemStats
- runtime.ReadMemStats(&mem)
+ var mem goruntime.MemStats
+ goruntime.ReadMemStats(&mem)
fmt.Printf("vm took %v\n", vmdone)
fmt.Printf(`alloc: %d
tot alloc: %d
@@ -223,87 +218,3 @@ func main() {
os.Exit(1)
}
}
-
-type VMEnv struct {
- state *state.StateDB
- block *types.Block
-
- transactor *common.Address
- value *big.Int
-
- depth int
- Gas *big.Int
- time *big.Int
- logs []vm.StructLog
-
- evm *vm.EVM
-}
-
-func NewEnv(state *state.StateDB, transactor common.Address, value *big.Int, cfg vm.Config) *VMEnv {
- env := &VMEnv{
- state: state,
- transactor: &transactor,
- value: value,
- time: big.NewInt(time.Now().Unix()),
- }
-
- env.evm = vm.New(env, cfg)
- return env
-}
-
-// ruleSet implements vm.ChainConfig and will always default to the homestead rule set.
-type ruleSet struct{}
-
-func (ruleSet) IsHomestead(*big.Int) bool { return true }
-func (ruleSet) GasTable(*big.Int) params.GasTable {
- return params.GasTableHomesteadGasRepriceFork
-}
-
-func (self *VMEnv) ChainConfig() *params.ChainConfig { return params.TestChainConfig }
-func (self *VMEnv) Vm() vm.Vm { return self.evm }
-func (self *VMEnv) Db() vm.Database { return self.state }
-func (self *VMEnv) SnapshotDatabase() int { return self.state.Snapshot() }
-func (self *VMEnv) RevertToSnapshot(snap int) { self.state.RevertToSnapshot(snap) }
-func (self *VMEnv) Origin() common.Address { return *self.transactor }
-func (self *VMEnv) BlockNumber() *big.Int { return common.Big0 }
-func (self *VMEnv) Coinbase() common.Address { return *self.transactor }
-func (self *VMEnv) Time() *big.Int { return self.time }
-func (self *VMEnv) Difficulty() *big.Int { return common.Big1 }
-func (self *VMEnv) BlockHash() []byte { return make([]byte, 32) }
-func (self *VMEnv) Value() *big.Int { return self.value }
-func (self *VMEnv) GasLimit() *big.Int { return big.NewInt(1000000000) }
-func (self *VMEnv) VmType() vm.Type { return vm.StdVmTy }
-func (self *VMEnv) Depth() int { return 0 }
-func (self *VMEnv) SetDepth(i int) { self.depth = i }
-func (self *VMEnv) GetHash(n uint64) common.Hash {
- if self.block.Number().Cmp(big.NewInt(int64(n))) == 0 {
- return self.block.Hash()
- }
- return common.Hash{}
-}
-func (self *VMEnv) AddLog(log *vm.Log) {
- self.state.AddLog(log)
-}
-func (self *VMEnv) CanTransfer(from common.Address, balance *big.Int) bool {
- return self.state.GetBalance(from).Cmp(balance) >= 0
-}
-func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) {
- core.Transfer(from, to, amount)
-}
-
-func (self *VMEnv) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- self.Gas = gas
- return core.Call(self, caller, addr, data, gas, price, value)
-}
-
-func (self *VMEnv) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return core.CallCode(self, caller, addr, data, gas, price, value)
-}
-
-func (self *VMEnv) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
- return core.DelegateCall(self, caller, addr, data, gas, price)
-}
-
-func (self *VMEnv) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
- return core.Create(self, caller, data, gas, price, value)
-}
diff --git a/cmd/geth/library.c b/cmd/geth/library.c
deleted file mode 100644
index f738621a8..000000000
--- a/cmd/geth/library.c
+++ /dev/null
@@ -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);
-}
diff --git a/cmd/geth/library.go b/cmd/geth/library.go
deleted file mode 100644
index b8ffaaed7..000000000
--- a/cmd/geth/library.go
+++ /dev/null
@@ -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
-}
diff --git a/cmd/geth/library_android.go b/cmd/geth/library_android.go
deleted file mode 100644
index fb021bfe0..000000000
--- a/cmd/geth/library_android.go
+++ /dev/null
@@ -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")
- }
- }()
-}
diff --git a/cmd/bzzhash/main.go b/cmd/swarm/hash.go
index 0ae99acc0..0a20bea82 100644
--- a/cmd/bzzhash/main.go
+++ b/cmd/swarm/hash.go
@@ -19,22 +19,21 @@ package main
import (
"fmt"
+ "log"
"os"
- "runtime"
"github.com/ethereum/go-ethereum/swarm/storage"
+ "gopkg.in/urfave/cli.v1"
)
-func main() {
- runtime.GOMAXPROCS(runtime.NumCPU())
-
- if len(os.Args) < 2 {
- fmt.Println("Usage: bzzhash <file name>")
- os.Exit(0)
+func hash(ctx *cli.Context) {
+ args := ctx.Args()
+ if len(args) < 1 {
+ log.Fatal("Usage: swarm hash <file name>")
}
- f, err := os.Open(os.Args[1])
+ f, err := os.Open(args[0])
if err != nil {
- fmt.Println("Error opening file " + os.Args[1])
+ fmt.Println("Error opening file " + args[1])
os.Exit(1)
}
@@ -42,7 +41,7 @@ func main() {
chunker := storage.NewTreeChunker(storage.NewChunkerParams())
key, err := chunker.Split(f, stat.Size(), nil, nil, nil)
if err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
+ log.Fatalf("%v\n", err)
} else {
fmt.Printf("%v\n", key)
}
diff --git a/cmd/bzzd/main.go b/cmd/swarm/main.go
index 4bb2ca04a..04930760e 100644
--- a/cmd/bzzd/main.go
+++ b/cmd/swarm/main.go
@@ -43,11 +43,21 @@ import (
"gopkg.in/urfave/cli.v1"
)
-const clientIdentifier = "bzzd"
+const (
+ clientIdentifier = "swarm"
+ versionString = "0.2"
+)
var (
- gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
- app = utils.NewApp(gitCommit, "Ethereum Swarm server daemon")
+ gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
+ 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 (
@@ -65,7 +75,7 @@ var (
}
SwarmNetworkIdFlag = cli.IntFlag{
Name: "bzznetworkid",
- Usage: "Network identifier (integer, default 322=swarm testnet)",
+ Usage: "Network identifier (integer, default 3=swarm testnet)",
Value: network.NetworkId,
}
SwarmConfigPathFlag = cli.StringFlag{
@@ -85,10 +95,25 @@ var (
Usage: "URL of the Ethereum API provider",
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() {
// Override flag defaults so bzzd can run alongside geth.
utils.ListenPortFlag.Value = 30399
@@ -96,8 +121,39 @@ func init() {
utils.IPCApiFlag.Value = "admin, bzz, chequebook, debug, rpc, web3"
// Set up the cli app.
- app.Commands = nil
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{
utils.IdentityFlag,
utils.DataDirFlag,
@@ -123,6 +179,11 @@ func init() {
SwarmAccountFlag,
SwarmNetworkIdFlag,
ChequebookAddrFlag,
+ // upload flags
+ SwarmApiFlag,
+ SwarmRecursiveUploadFlag,
+ SwarmWantManifestFlag,
+ SwarmUploadDefaultPath,
}
app.Flags = append(app.Flags, debug.Flags...)
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 {
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
registerBzzService(ctx, stack)
utils.StartNode(stack)
-
+ networkId := ctx.GlobalUint64(SwarmNetworkIdFlag.Name)
// Add bootnodes as initial peers.
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
bootnodes := strings.Split(ctx.GlobalString(utils.BootnodesFlag.Name), ",")
injectBootnodes(stack.Server(), bootnodes)
} else {
- injectBootnodes(stack.Server(), defaultBootnodes)
+ if networkId == 3 {
+ injectBootnodes(stack.Server(), testbetBootNodes)
+ }
}
stack.Wait()
@@ -182,13 +259,11 @@ func registerBzzService(ctx *cli.Context, stack *node.Node) {
boot := func(ctx *node.ServiceContext) (node.Service, error) {
var client *ethclient.Client
- if ethapi == "" {
- err = fmt.Errorf("use ethapi flag to connect to a an eth client and talk to the blockchain")
- } else {
+ if len(ethapi) > 0 {
client, err = ethclient.Dial(ethapi)
- }
- if err != nil {
- utils.Fatalf("Can't connect: %v", err)
+ if err != nil {
+ utils.Fatalf("Can't connect: %v", err)
+ }
}
return swarm.NewSwarm(ctx, client, bzzconfig, swapEnabled, syncEnabled)
}
diff --git a/cmd/bzzup/main.go b/cmd/swarm/upload.go
index 7d251aadb..d048bbc40 100644
--- a/cmd/bzzup/main.go
+++ b/cmd/swarm/upload.go
@@ -20,7 +20,6 @@ package main
import (
"bytes"
"encoding/json"
- "flag"
"fmt"
"io"
"io/ioutil"
@@ -28,58 +27,83 @@ import (
"mime"
"net/http"
"os"
+ "os/user"
+ "path"
"path/filepath"
"strings"
+
+ "gopkg.in/urfave/cli.v1"
)
-func main() {
+func upload(ctx *cli.Context) {
+ args := ctx.Args()
var (
- bzzapiFlag = flag.String("bzzapi", "http://127.0.0.1:8500", "Swarm HTTP endpoint")
- recursiveFlag = flag.Bool("recursive", false, "Upload directories recursively")
- manifestFlag = flag.Bool("manifest", true, "Skip automatic manifest upload")
+ bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
+ recursive = ctx.GlobalBool(SwarmRecursiveUploadFlag.Name)
+ wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name)
+ defaultPath = ctx.GlobalString(SwarmUploadDefaultPath.Name)
)
- log.SetOutput(os.Stderr)
- log.SetFlags(0)
- flag.Parse()
- if flag.NArg() != 1 {
+ if len(args) != 1 {
log.Fatal("need filename as the first and only argument")
}
var (
- file = flag.Arg(0)
- client = &client{api: *bzzapiFlag}
+ file = args[0]
+ client = &client{api: bzzapi}
mroot manifest
+ entry manifestEntry
)
- fi, err := os.Stat(file)
+ fi, err := os.Stat(expandPath(file))
if err != nil {
log.Fatal(err)
}
if fi.IsDir() {
- if !*recursiveFlag {
+ if !recursive {
log.Fatal("argument is a directory and recursive upload is disabled")
}
- mroot, err = client.uploadDirectory(file)
+ mroot, err = client.uploadDirectory(file, defaultPath)
} else {
- mroot, err = client.uploadFile(file, fi)
- if *manifestFlag {
- // Wrap the raw file entry in a proper manifest so both hashes get printed.
- mroot = manifest{Entries: []manifest{mroot}}
- }
+ entry, err = client.uploadFile(file, fi)
+ mroot = manifest{[]manifestEntry{entry}}
}
if err != nil {
log.Fatalln("upload failed:", err)
}
- if *manifestFlag {
- hash, err := client.uploadManifest(mroot)
- if err != nil {
- log.Fatalln("manifest upload failed:", err)
+ 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)
+ if err != nil {
+ log.Fatalln("manifest upload failed:", err)
+ }
+ fmt.Println(hash)
+}
+
+// Expands a file path
+// 1. replace tilde with users home dir
+// 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:]
}
- mroot.Hash = hash
}
+ return path.Clean(os.ExpandEnv(p))
+}
- // Print the manifest. This is the only output to stdout.
- mrootJSON, _ := json.MarshalIndent(mroot, "", " ")
- fmt.Println(string(mrootJSON))
+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.
@@ -88,24 +112,40 @@ type client struct {
}
// manifest is the JSON representation of a swarm manifest.
+type manifestEntry struct {
+ Hash string `json:"hash,omitempty"`
+ ContentType string `json:"contentType,omitempty"`
+ Path string `json:"path,omitempty"`
+}
+
+// manifest is the JSON representation of a swarm manifest.
type manifest struct {
- Hash string `json:"hash,omitempty"`
- ContentType string `json:"contentType,omitempty"`
- Path string `json:"path,omitempty"`
- Entries []manifest `json:"entries,omitempty"`
+ Entries []manifestEntry `json:"entries,omitempty"`
}
-func (c *client) uploadFile(file string, fi os.FileInfo) (manifest, error) {
+func (c *client) uploadFile(file string, fi os.FileInfo) (manifestEntry, error) {
hash, err := c.uploadFileContent(file, fi)
- m := manifest{
+ m := manifestEntry{
Hash: hash,
ContentType: mime.TypeByExtension(filepath.Ext(fi.Name())),
}
return m, err
}
-func (c *client) uploadDirectory(dir string) (manifest, error) {
+func (c *client) uploadDirectory(dir string, defaultPath string) (manifest, error) {
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)) + "/"
err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
if err != nil || fi.IsDir() {
diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go
index 584afc804..a56507e4d 100644
--- a/cmd/utils/cmd.go
+++ b/cmd/utils/cmd.go
@@ -18,12 +18,14 @@
package utils
import (
+ "compress/gzip"
"fmt"
"io"
"os"
"os/signal"
"regexp"
"runtime"
+ "strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
@@ -133,7 +135,15 @@ func ImportChain(chain *core.BlockChain, fn string) error {
return err
}
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.
blocks := make(types.Blocks, importBatchSize)
@@ -195,10 +205,18 @@ func ExportChain(blockchain *core.BlockChain, fn string) error {
return err
}
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
}
glog.Infoln("Exported blockchain to ", fn)
+
return nil
}
@@ -210,7 +228,14 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
return err
}
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
}
glog.Infoln("Exported blockchain to ", fn)
diff --git a/core/blockchain.go b/core/blockchain.go
index 2eb207d39..0de529480 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -601,7 +601,11 @@ func (self *BlockChain) procFutureBlocks() {
}
if len(blocks) > 0 {
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.
// XXX should this be moved to the test?
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)
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
// 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) {
+ // 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)
defer self.wg.Done()
@@ -916,10 +944,8 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
}
self.reportBlock(block, nil, err)
-
return i, err
}
-
// Create a new statedb using the parent block and report an
// error if it fails.
switch {
@@ -988,7 +1014,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
glog.Infof("inserted forked block #%d [%x…] (TD=%v) in %9v: %3d txs %d uncles.", block.Number(), block.Hash().Bytes()[0:4], block.Difficulty(), common.PrettyDuration(time.Since(bstart)), len(block.Transactions()), len(block.Uncles()))
}
blockInsertTimer.UpdateSince(bstart)
- events = append(events, ChainSideEvent{block, logs})
+ events = append(events, ChainSideEvent{block})
case SplitStatTy:
events = append(events, ChainSplitEvent{block, logs})
@@ -1062,24 +1088,25 @@ func countTransactions(chain []*types.Block) (c int) {
// event about them
func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
var (
- newChain types.Blocks
- oldChain types.Blocks
- commonBlock *types.Block
- oldStart = oldBlock
- newStart = newBlock
- deletedTxs types.Transactions
- deletedLogs vm.Logs
- deletedLogsByHash = make(map[common.Hash]vm.Logs)
+ newChain types.Blocks
+ oldChain types.Blocks
+ commonBlock *types.Block
+ oldStart = oldBlock
+ newStart = newBlock
+ deletedTxs types.Transactions
+ deletedLogs vm.Logs
// collectLogs collects the logs that were generated during the
// processing of the block that corresponds with the given hash.
// These logs are later announced as deleted.
collectLogs = func(h common.Hash) {
- // Coalesce logs
+ // Coalesce logs and set 'Removed'.
receipts := GetBlockReceipts(self.chainDb, h, self.hc.GetBlockNumber(h))
for _, receipt := range receipts {
- deletedLogs = append(deletedLogs, receipt.Logs...)
-
- deletedLogsByHash[h] = receipt.Logs
+ for _, log := range receipt.Logs {
+ del := *log
+ del.Removed = true
+ deletedLogs = append(deletedLogs, &del)
+ }
}
}
)
@@ -1173,7 +1200,7 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
if len(oldChain) > 0 {
go func() {
for _, block := range oldChain {
- self.eventMux.Post(ChainSideEvent{Block: block, Logs: deletedLogsByHash[block.Hash()]})
+ self.eventMux.Post(ChainSideEvent{Block: block})
}
}()
}
diff --git a/core/events.go b/core/events.go
index 322bcb769..414493fbf 100644
--- a/core/events.go
+++ b/core/events.go
@@ -61,7 +61,6 @@ type ChainEvent struct {
type ChainSideEvent struct {
Block *types.Block
- Logs vm.Logs
}
type PendingBlockEvent struct {
diff --git a/core/evm.go b/core/evm.go
new file mode 100644
index 000000000..6a5713075
--- /dev/null
+++ b/core/evm.go
@@ -0,0 +1,73 @@
+// Copyright 2014 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 core
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+)
+
+// BlockFetcher retrieves headers by their hash
+type HeaderFetcher interface {
+ // GetHeader returns the hash corresponding to their hash
+ GetHeader(common.Hash, uint64) *types.Header
+}
+
+// NewEVMContext creates a new context for use in the EVM.
+func NewEVMContext(msg Message, header *types.Header, chain HeaderFetcher) vm.Context {
+ return vm.Context{
+ CanTransfer: CanTransfer,
+ Transfer: Transfer,
+ GetHash: GetHashFn(header, chain),
+
+ Origin: msg.From(),
+ Coinbase: header.Coinbase,
+ BlockNumber: new(big.Int).Set(header.Number),
+ Time: new(big.Int).Set(header.Time),
+ Difficulty: new(big.Int).Set(header.Difficulty),
+ GasLimit: new(big.Int).Set(header.GasLimit),
+ GasPrice: new(big.Int).Set(msg.GasPrice()),
+ }
+}
+
+// GetHashFn returns a GetHashFunc which retrieves header hashes by number
+func GetHashFn(ref *types.Header, chain HeaderFetcher) func(n uint64) common.Hash {
+ return func(n uint64) common.Hash {
+ for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {
+ if header.Number.Uint64() == n {
+ return header.Hash()
+ }
+ }
+
+ return common.Hash{}
+ }
+}
+
+// CanTransfer checks wether there are enough funds in the address' account to make a transfer.
+// This does not take the necessary gas in to account to make the transfer valid.
+func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool {
+ return db.GetBalance(addr).Cmp(amount) >= 0
+}
+
+// Transfer subtracts amount from sender and adds amount to recipient using the given Db
+func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
+ db.SubBalance(sender, amount)
+ db.AddBalance(recipient, amount)
+}
diff --git a/core/execution.go b/core/execution.go
deleted file mode 100644
index e3ea1006c..000000000
--- a/core/execution.go
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright 2014 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 core
-
-import (
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/params"
-)
-
-// Call executes within the given contract
-func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) {
- // Depth check execution. Fail if we're trying to execute above the
- // limit.
- if env.Depth() > int(params.CallCreateDepth.Int64()) {
- caller.ReturnGas(gas, gasPrice)
-
- return nil, vm.DepthError
- }
- if !env.CanTransfer(caller.Address(), value) {
- caller.ReturnGas(gas, gasPrice)
-
- return nil, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address()))
- }
-
- snapshotPreTransfer := env.SnapshotDatabase()
- var (
- from = env.Db().GetAccount(caller.Address())
- to vm.Account
- )
- if !env.Db().Exist(addr) {
- if vm.Precompiled[addr.Str()] == nil && env.ChainConfig().IsEIP158(env.BlockNumber()) && value.BitLen() == 0 {
- caller.ReturnGas(gas, gasPrice)
- return nil, nil
- }
-
- to = env.Db().CreateAccount(addr)
- } else {
- to = env.Db().GetAccount(addr)
- }
- env.Transfer(from, to, value)
-
- // initialise a new contract and set the code that is to be used by the
- // EVM. The contract is a scoped environment for this execution context
- // only.
- contract := vm.NewContract(caller, to, value, gas, gasPrice)
- contract.SetCallCode(&addr, env.Db().GetCodeHash(addr), env.Db().GetCode(addr))
- defer contract.Finalise()
-
- ret, err = env.Vm().Run(contract, input)
- // When an error was returned by the EVM or when setting the creation code
- // above we revert to the snapshot and consume any gas remaining. Additionally
- // when we're in homestead this also counts for code storage gas errors.
- if err != nil {
- contract.UseGas(contract.Gas)
-
- env.RevertToSnapshot(snapshotPreTransfer)
- }
- return ret, err
-}
-
-// CallCode executes the given address' code as the given contract address
-func CallCode(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) {
- // Depth check execution. Fail if we're trying to execute above the
- // limit.
- if env.Depth() > int(params.CallCreateDepth.Int64()) {
- caller.ReturnGas(gas, gasPrice)
-
- return nil, vm.DepthError
- }
- if !env.CanTransfer(caller.Address(), value) {
- caller.ReturnGas(gas, gasPrice)
-
- return nil, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address()))
- }
-
- var (
- snapshotPreTransfer = env.SnapshotDatabase()
- to = env.Db().GetAccount(caller.Address())
- )
- // initialise a new contract and set the code that is to be used by the
- // EVM. The contract is a scoped environment for this execution context
- // only.
- contract := vm.NewContract(caller, to, value, gas, gasPrice)
- contract.SetCallCode(&addr, env.Db().GetCodeHash(addr), env.Db().GetCode(addr))
- defer contract.Finalise()
-
- ret, err = env.Vm().Run(contract, input)
- if err != nil {
- contract.UseGas(contract.Gas)
-
- env.RevertToSnapshot(snapshotPreTransfer)
- }
-
- return ret, err
-}
-
-// Create creates a new contract with the given code
-func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPrice, value *big.Int) (ret []byte, address common.Address, err error) {
- // Depth check execution. Fail if we're trying to execute above the
- // limit.
- if env.Depth() > int(params.CallCreateDepth.Int64()) {
- caller.ReturnGas(gas, gasPrice)
-
- return nil, common.Address{}, vm.DepthError
- }
- if !env.CanTransfer(caller.Address(), value) {
- caller.ReturnGas(gas, gasPrice)
-
- return nil, common.Address{}, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address()))
- }
-
- // Create a new account on the state
- nonce := env.Db().GetNonce(caller.Address())
- env.Db().SetNonce(caller.Address(), nonce+1)
-
- snapshotPreTransfer := env.SnapshotDatabase()
- var (
- addr = crypto.CreateAddress(caller.Address(), nonce)
- from = env.Db().GetAccount(caller.Address())
- to = env.Db().CreateAccount(addr)
- )
- if env.ChainConfig().IsEIP158(env.BlockNumber()) {
- env.Db().SetNonce(addr, 1)
- }
- env.Transfer(from, to, value)
-
- // initialise a new contract and set the code that is to be used by the
- // EVM. The contract is a scoped environment for this execution context
- // only.
- contract := vm.NewContract(caller, to, value, gas, gasPrice)
- contract.SetCallCode(&addr, crypto.Keccak256Hash(code), code)
- defer contract.Finalise()
-
- ret, err = env.Vm().Run(contract, nil)
- // check whether the max code size has been exceeded
- maxCodeSizeExceeded := len(ret) > params.MaxCodeSize
- // if the contract creation ran successfully and no errors were returned
- // calculate the gas required to store the code. If the code could not
- // be stored due to not enough gas set an error and let it be handled
- // by the error checking condition below.
- if err == nil && !maxCodeSizeExceeded {
- dataGas := big.NewInt(int64(len(ret)))
- dataGas.Mul(dataGas, params.CreateDataGas)
- if contract.UseGas(dataGas) {
- env.Db().SetCode(addr, ret)
- } else {
- err = vm.CodeStoreOutOfGasError
- }
- }
-
- // When an error was returned by the EVM or when setting the creation code
- // above we revert to the snapshot and consume any gas remaining. Additionally
- // when we're in homestead this also counts for code storage gas errors.
- if maxCodeSizeExceeded ||
- (err != nil && (env.ChainConfig().IsHomestead(env.BlockNumber()) || err != vm.CodeStoreOutOfGasError)) {
- contract.UseGas(contract.Gas)
- env.RevertToSnapshot(snapshotPreTransfer)
-
- // Nothing should be returned when an error is thrown.
- return nil, addr, err
- }
-
- return ret, addr, err
-}
-
-// DelegateCall is equivalent to CallCode except that sender and value propagates from parent scope to child scope
-func DelegateCall(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice *big.Int) (ret []byte, err error) {
- // Depth check execution. Fail if we're trying to execute above the
- // limit.
- if env.Depth() > int(params.CallCreateDepth.Int64()) {
- caller.ReturnGas(gas, gasPrice)
- return nil, vm.DepthError
- }
-
- var (
- snapshot = env.SnapshotDatabase()
- to = env.Db().GetAccount(caller.Address())
- )
-
- // Iinitialise a new contract and make initialise the delegate values
- contract := vm.NewContract(caller, to, caller.Value(), gas, gasPrice).AsDelegate()
- contract.SetCallCode(&addr, env.Db().GetCodeHash(addr), env.Db().GetCode(addr))
- defer contract.Finalise()
-
- ret, err = env.Vm().Run(contract, input)
- if err != nil {
- contract.UseGas(contract.Gas)
-
- env.RevertToSnapshot(snapshot)
- }
-
- return ret, err
-}
-
-// generic transfer method
-func Transfer(from, to vm.Account, amount *big.Int) {
- from.SubBalance(amount)
- to.AddBalance(amount)
-}
diff --git a/core/headerchain.go b/core/headerchain.go
index c53694571..ca630a4f7 100644
--- a/core/headerchain.go
+++ b/core/headerchain.go
@@ -224,6 +224,17 @@ type WhCallback func(*types.Header) error
// of the header retrieval mechanisms already need to verfy nonces, as well as
// because nonces can be verified sparsely, not needing to check each.
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
stats := struct{ processed, ignored int }{}
start := time.Now()
diff --git a/core/state/state_object.go b/core/state/state_object.go
index d40b42d83..87aa8ccd6 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -288,7 +288,7 @@ func (self *StateObject) setBalance(amount *big.Int) {
}
// Return the gas back to the origin. Used by the Virtual machine or Closures
-func (c *StateObject) ReturnGas(gas, price *big.Int) {}
+func (c *StateObject) ReturnGas(gas *big.Int) {}
func (self *StateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *StateObject {
stateObject := newObject(db, self.address, self.data, onDirty)
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 3742c178b..82e2ec7c1 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -293,6 +293,7 @@ func (self *StateDB) HasSuicided(addr common.Address) bool {
* SETTERS
*/
+// AddBalance adds amount to the account associated with addr
func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) {
stateObject := self.GetOrNewStateObject(addr)
if stateObject != nil {
@@ -300,6 +301,14 @@ func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) {
}
}
+// SubBalance subtracts amount from the account associated with addr
+func (self *StateDB) SubBalance(addr common.Address, amount *big.Int) {
+ stateObject := self.GetOrNewStateObject(addr)
+ if stateObject != nil {
+ stateObject.SubBalance(amount)
+ }
+}
+
func (self *StateDB) SetBalance(addr common.Address, amount *big.Int) {
stateObject := self.GetOrNewStateObject(addr)
if stateObject != nil {
diff --git a/core/state_processor.go b/core/state_processor.go
index e346917c3..67a7ad5a1 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -96,28 +96,36 @@ func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, gp *GasPool, s
if err != nil {
return nil, nil, nil, err
}
-
- _, gas, err := ApplyMessage(NewEnv(statedb, config, bc, msg, header, cfg), msg, gp)
+ // Create a new context to be used in the EVM environment
+ context := NewEVMContext(msg, header, bc)
+ // Create a new environment which holds all relevant information
+ // about the transaction and calling mechanisms.
+ vmenv := vm.NewEnvironment(context, statedb, config, vm.Config{})
+ // Apply the transaction to the current state (included in the env)
+ _, gas, err := ApplyMessage(vmenv, msg, gp)
if err != nil {
return nil, nil, nil, err
}
// Update the state with pending changes
usedGas.Add(usedGas, gas)
+ // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
+ // based on the eip phase, we're passing wether the root touch-delete accounts.
receipt := types.NewReceipt(statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes(), usedGas)
receipt.TxHash = tx.Hash()
receipt.GasUsed = new(big.Int).Set(gas)
- if MessageCreatesContract(msg) {
- receipt.ContractAddress = crypto.CreateAddress(msg.From(), tx.Nonce())
+ // if the transaction created a contract, store the creation address in the receipt.
+ if msg.To() == nil {
+ receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
}
- logs := statedb.GetLogs(tx.Hash())
- receipt.Logs = logs
+ // Set the receipt logs and create a bloom for filtering
+ receipt.Logs = statedb.GetLogs(tx.Hash())
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
glog.V(logger.Debug).Infoln(receipt)
- return receipt, logs, gas, err
+ return receipt, receipt.Logs, gas, err
}
// AccumulateRewards credits the coinbase of the given block with the
diff --git a/core/state_transition.go b/core/state_transition.go
index 8abe17b0a..48540be14 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -55,9 +55,9 @@ type StateTransition struct {
initialGas *big.Int
value *big.Int
data []byte
- state vm.Database
+ state vm.StateDB
- env vm.Environment
+ env *vm.Environment
}
// Message represents a message sent to a contract.
@@ -106,7 +106,7 @@ func IntrinsicGas(data []byte, contractCreation, homestead bool) *big.Int {
}
// NewStateTransition initialises and returns a new state transition object.
-func NewStateTransition(env vm.Environment, msg Message, gp *GasPool) *StateTransition {
+func NewStateTransition(env *vm.Environment, msg Message, gp *GasPool) *StateTransition {
return &StateTransition{
gp: gp,
env: env,
@@ -116,7 +116,7 @@ func NewStateTransition(env vm.Environment, msg Message, gp *GasPool) *StateTran
initialGas: new(big.Int),
value: msg.Value(),
data: msg.Data(),
- state: env.Db(),
+ state: env.StateDB,
}
}
@@ -127,7 +127,7 @@ func NewStateTransition(env vm.Environment, msg Message, gp *GasPool) *StateTran
// the gas used (which includes gas refunds) and an error if it failed. An error always
// indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block.
-func ApplyMessage(env vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) {
+func ApplyMessage(env *vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) {
st := NewStateTransition(env, msg, gp)
ret, _, gasUsed, err := st.TransitionDb()
@@ -217,47 +217,44 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b
msg := self.msg
sender := self.from() // err checked in preCheck
- homestead := self.env.ChainConfig().IsHomestead(self.env.BlockNumber())
+ homestead := self.env.ChainConfig().IsHomestead(self.env.BlockNumber)
contractCreation := MessageCreatesContract(msg)
// Pay intrinsic gas
if err = self.useGas(IntrinsicGas(self.data, contractCreation, homestead)); err != nil {
return nil, nil, nil, InvalidTxError(err)
}
- vmenv := self.env
- //var addr common.Address
+ var (
+ vmenv = self.env
+ // vm errors do not effect consensus and are therefor
+ // not assigned to err, except for insufficient balance
+ // error.
+ vmerr error
+ )
if contractCreation {
- ret, _, err = vmenv.Create(sender, self.data, self.gas, self.gasPrice, self.value)
+ ret, _, vmerr = vmenv.Create(sender, self.data, self.gas, self.value)
if homestead && err == vm.CodeStoreOutOfGasError {
self.gas = Big0
}
-
- if err != nil {
- ret = nil
- glog.V(logger.Core).Infoln("VM create err:", err)
- }
} else {
// Increment the nonce for the next transaction
self.state.SetNonce(sender.Address(), self.state.GetNonce(sender.Address())+1)
- ret, err = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.gasPrice, self.value)
- if err != nil {
- glog.V(logger.Core).Infoln("VM call err:", err)
- }
- }
-
- if err != nil && IsValueTransferErr(err) {
- return nil, nil, nil, InvalidTxError(err)
+ ret, vmerr = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.value)
}
-
- // We aren't interested in errors here. Errors returned by the VM are non-consensus errors and therefor shouldn't bubble up
- if err != nil {
- err = nil
+ if vmerr != nil {
+ glog.V(logger.Core).Infoln("vm returned with error:", err)
+ // The only possible consensus-error would be if there wasn't
+ // sufficient balance to make the transfer happen. The first
+ // balance transfer may never fail.
+ if vmerr == vm.ErrInsufficientBalance {
+ return nil, nil, nil, InvalidTxError(vmerr)
+ }
}
requiredGas = new(big.Int).Set(self.gasUsed())
self.refundGas()
- self.state.AddBalance(self.env.Coinbase(), new(big.Int).Mul(self.gasUsed(), self.gasPrice))
+ self.state.AddBalance(self.env.Coinbase, new(big.Int).Mul(self.gasUsed(), self.gasPrice))
return ret, requiredGas, self.gasUsed(), err
}
diff --git a/core/tx_pool.go b/core/tx_pool.go
index edcbc21eb..c5421fa02 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -37,15 +37,14 @@ import (
var (
// Transaction Pool Errors
- ErrInvalidSender = errors.New("Invalid sender")
- ErrNonce = errors.New("Nonce too low")
- ErrCheap = errors.New("Gas price too low for acceptance")
- 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")
- ErrIntrinsicGas = errors.New("Intrinsic gas too low")
- ErrGasLimit = errors.New("Exceeds block gas limit")
- ErrNegativeValue = errors.New("Negative value")
+ ErrInvalidSender = errors.New("Invalid sender")
+ ErrNonce = errors.New("Nonce too low")
+ ErrCheap = errors.New("Gas price too low for acceptance")
+ ErrBalance = errors.New("Insufficient balance")
+ ErrInsufficientFunds = errors.New("Insufficient funds for gas * price + value")
+ ErrIntrinsicGas = errors.New("Intrinsic gas too low")
+ ErrGasLimit = errors.New("Exceeds block gas limit")
+ ErrNegativeValue = errors.New("Negative value")
)
var (
@@ -124,6 +123,8 @@ func NewTxPool(config *params.ChainConfig, eventMux *event.TypeMux, currentState
quit: make(chan struct{}),
}
+ pool.resetState()
+
pool.wg.Add(2)
go pool.eventLoop()
go pool.expirationLoop()
@@ -176,7 +177,7 @@ func (pool *TxPool) resetState() {
// any transactions that have been included in the block or
// have been invalidated because of another transaction (e.g.
// higher gas price)
- pool.demoteUnexecutables()
+ pool.demoteUnexecutables(currentState)
// Update all accounts to the latest known pending nonce
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
// or remove those that have become invalid
- pool.promoteExecutables()
+ pool.promoteExecutables(currentState)
}
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
// account and sorted by nonce. The returned transaction set is a copy and can be
// 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()
defer pool.mu.Unlock()
+ state, err := pool.currentState()
+ if err != nil {
+ return nil, err
+ }
+
// check queue first
- pool.promoteExecutables()
+ pool.promoteExecutables(state)
// invalidate any txs
- pool.demoteUnexecutables()
+ pool.demoteUnexecutables(state)
pending := make(map[common.Address]types.Transactions)
for addr, list := range pool.pending {
pending[addr] = list.Flatten()
}
- return pending
+ return pending, nil
}
// SetLocal marks a transaction as local, skipping gas price
@@ -280,13 +286,6 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
if err != nil {
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
if currentState.GetNonce(from) > tx.Nonce() {
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!
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
if pool.pending[addr] == nil {
pool.pending[addr] = newTxList(true)
@@ -410,13 +405,19 @@ func (pool *TxPool) Add(tx *types.Transaction) error {
if err := pool.add(tx); err != nil {
return err
}
- pool.promoteExecutables()
+
+ state, err := pool.currentState()
+ if err != nil {
+ return err
+ }
+
+ pool.promoteExecutables(state)
return nil
}
// 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()
defer pool.mu.Unlock()
@@ -425,7 +426,15 @@ func (pool *TxPool) AddBatch(txs []*types.Transaction) {
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
@@ -499,17 +508,7 @@ func (pool *TxPool) removeTx(hash common.Hash) {
// promoteExecutables moves transactions that have become processable from the
// future queue to the set of pending transactions. During this process, all
// invalidated transactions (low nonce, low balance) are deleted.
-func (pool *TxPool) promoteExecutables() {
- // 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
- }
+func (pool *TxPool) promoteExecutables(state *state.StateDB) {
// Iterate over all accounts and promote any executable transactions
queued := uint64(0)
for addr, list := range pool.queue {
@@ -645,13 +644,7 @@ func (pool *TxPool) promoteExecutables() {
// demoteUnexecutables removes invalid and processed transactions from the pools
// executable/pending queue and any subsequent transactions that become unexecutable
// are moved back into the future queue.
-func (pool *TxPool) demoteUnexecutables() {
- // 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
- }
+func (pool *TxPool) demoteUnexecutables(state *state.StateDB) {
// Iterate over all accounts and demote any non-executable transactions
for addr, list := range pool.pending {
nonce := state.GetNonce(addr)
diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go
index 009d19886..f5fcac19f 100644
--- a/core/tx_pool_test.go
+++ b/core/tx_pool_test.go
@@ -51,14 +51,84 @@ func deriveSender(tx *types.Transaction) (common.Address, error) {
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) {
pool, key := setupTxPool()
tx := transaction(0, big.NewInt(100), key)
- if err := pool.Add(tx); err != ErrNonExistentAccount {
- t.Error("expected", ErrNonExistentAccount)
- }
-
from, _ := deriveSender(tx)
currentState, _ := pool.currentState()
currentState.AddBalance(from, big.NewInt(1))
@@ -97,9 +167,10 @@ func TestTransactionQueue(t *testing.T) {
from, _ := deriveSender(tx)
currentState, _ := pool.currentState()
currentState.AddBalance(from, big.NewInt(1000))
+ pool.resetState()
pool.enqueueTx(tx.Hash(), tx)
- pool.promoteExecutables()
+ pool.promoteExecutables(currentState)
if len(pool.pending) != 1 {
t.Error("expected valid txs to be 1 is", len(pool.pending))
}
@@ -108,7 +179,7 @@ func TestTransactionQueue(t *testing.T) {
from, _ = deriveSender(tx)
currentState.SetNonce(from, 2)
pool.enqueueTx(tx.Hash(), tx)
- pool.promoteExecutables()
+ pool.promoteExecutables(currentState)
if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
t.Error("expected transaction to be in tx pool")
}
@@ -124,11 +195,13 @@ func TestTransactionQueue(t *testing.T) {
from, _ = deriveSender(tx1)
currentState, _ = pool.currentState()
currentState.AddBalance(from, big.NewInt(1000))
+ pool.resetState()
+
pool.enqueueTx(tx1.Hash(), tx1)
pool.enqueueTx(tx2.Hash(), tx2)
pool.enqueueTx(tx3.Hash(), tx3)
- pool.promoteExecutables()
+ pool.promoteExecutables(currentState)
if len(pool.pending) != 1 {
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 {
t.Error("didn't expect error", err)
}
- pool.promoteExecutables()
+ state, _ := pool.currentState()
+ pool.promoteExecutables(state)
if pool.pending[addr].Len() != 1 {
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 {
t.Error("didn't expect error", err)
}
- pool.promoteExecutables()
+ pool.promoteExecutables(state)
if pool.pending[addr].Len() != 1 {
t.Error("expected 1 pending transactions, got", pool.pending[addr].Len())
}
@@ -295,6 +369,7 @@ func TestRemovedTxEvent(t *testing.T) {
from, _ := deriveSender(tx)
currentState, _ := pool.currentState()
currentState.AddBalance(from, big.NewInt(1000000000000))
+ pool.resetState()
pool.eventMux.Post(RemovedTransactionEvent{types.Transactions{tx}})
pool.eventMux.Post(ChainHeadEvent{nil})
if pool.pending[from].Len() != 1 {
@@ -452,6 +527,7 @@ func TestTransactionQueueAccountLimiting(t *testing.T) {
state, _ := pool.currentState()
state.AddBalance(account, big.NewInt(1000000))
+ pool.resetState()
// Keep queuing up transactions and make sure all above a limit are dropped
for i := uint64(1); i <= maxQueuedPerAccount+5; i++ {
@@ -564,6 +640,7 @@ func TestTransactionPendingLimiting(t *testing.T) {
state, _ := pool.currentState()
state.AddBalance(account, big.NewInt(1000000))
+ pool.resetState()
// Keep queuing up transactions and make sure all above a limit are dropped
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
b.ResetTimer()
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
b.ResetTimer()
for i := 0; i < b.N; i++ {
- pool.promoteExecutables()
+ pool.promoteExecutables(state)
}
}
diff --git a/core/vm/contract.go b/core/vm/contract.go
index 70455a4c2..dfa93ab18 100644
--- a/core/vm/contract.go
+++ b/core/vm/contract.go
@@ -24,7 +24,7 @@ import (
// ContractRef is a reference to the contract's backing object
type ContractRef interface {
- ReturnGas(*big.Int, *big.Int)
+ ReturnGas(*big.Int)
Address() common.Address
Value() *big.Int
SetCode(common.Hash, []byte)
@@ -48,7 +48,7 @@ type Contract struct {
CodeAddr *common.Address
Input []byte
- value, Gas, UsedGas, Price *big.Int
+ value, Gas, UsedGas *big.Int
Args []byte
@@ -56,7 +56,7 @@ type Contract struct {
}
// NewContract returns a new contract environment for the execution of EVM.
-func NewContract(caller ContractRef, object ContractRef, value, gas, price *big.Int) *Contract {
+func NewContract(caller ContractRef, object ContractRef, value, gas *big.Int) *Contract {
c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object, Args: nil}
if parent, ok := caller.(*Contract); ok {
@@ -70,9 +70,6 @@ func NewContract(caller ContractRef, object ContractRef, value, gas, price *big.
// This pointer will be off the state transition
c.Gas = gas //new(big.Int).Set(gas)
c.value = new(big.Int).Set(value)
- // In most cases price and value are pointers to transaction objects
- // and we don't want the transaction's values to change.
- c.Price = new(big.Int).Set(price)
c.UsedGas = new(big.Int)
return c
@@ -114,7 +111,7 @@ func (c *Contract) Caller() common.Address {
// caller.
func (c *Contract) Finalise() {
// Return the remaining gas to the caller
- c.caller.ReturnGas(c.Gas, c.Price)
+ c.caller.ReturnGas(c.Gas)
}
// UseGas attempts the use gas and subtracts it and returns true on success
@@ -127,7 +124,7 @@ func (c *Contract) UseGas(gas *big.Int) (ok bool) {
}
// ReturnGas adds the given gas back to itself.
-func (c *Contract) ReturnGas(gas, price *big.Int) {
+func (c *Contract) ReturnGas(gas *big.Int) {
// Return the gas to the context
c.Gas.Add(c.Gas, gas)
c.UsedGas.Sub(c.UsedGas, gas)
diff --git a/core/vm/environment.go b/core/vm/environment.go
index e97c1e58c..50a09d444 100644
--- a/core/vm/environment.go
+++ b/core/vm/environment.go
@@ -17,110 +17,299 @@
package vm
import (
+ "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
-// Environment is an EVM requirement and helper which allows access to outside
-// information such as states.
-type Environment interface {
- // The current ruleset
- ChainConfig() *params.ChainConfig
- // The state database
- Db() Database
- // Creates a restorable snapshot
- SnapshotDatabase() int
- // Set database to previous snapshot
- RevertToSnapshot(int)
- // Address of the original invoker (first occurrence of the VM invoker)
- Origin() common.Address
- // The block number this VM is invoked on
- BlockNumber() *big.Int
- // The n'th hash ago from this block number
- GetHash(uint64) common.Hash
- // The handler's address
- Coinbase() common.Address
- // The current time (block time)
- Time() *big.Int
- // Difficulty set on the current block
- Difficulty() *big.Int
- // The gas limit of the block
- GasLimit() *big.Int
- // Determines whether it's possible to transact
- CanTransfer(from common.Address, balance *big.Int) bool
- // Transfers amount from one account to the other
- Transfer(from, to Account, amount *big.Int)
- // Adds a LOG to the state
- AddLog(*Log)
- // Type of the VM
- Vm() Vm
- // Get the curret calling depth
- Depth() int
- // Set the current calling depth
- SetDepth(i int)
- // Call another contract
- Call(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error)
- // Take another's contract code and execute within our own context
- CallCode(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error)
- // Same as CallCode except sender and value is propagated from parent to child scope
- DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error)
- // Create a new contract
- Create(me ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error)
+type (
+ CanTransferFunc func(StateDB, common.Address, *big.Int) bool
+ TransferFunc func(StateDB, common.Address, common.Address, *big.Int)
+ // GetHashFunc returns the nth block hash in the blockchain
+ // and is used by the BLOCKHASH EVM op code.
+ GetHashFunc func(uint64) common.Hash
+)
+
+// Context provides the EVM with auxilary information. Once provided it shouldn't be modified.
+type Context struct {
+ // CanTransfer returns whether the account contains
+ // sufficient ether to transfer the value
+ CanTransfer CanTransferFunc
+ // Transfer transfers ether from one account to the other
+ Transfer TransferFunc
+ // GetHash returns the hash corresponding to n
+ GetHash GetHashFunc
+
+ // Message information
+ Origin common.Address // Provides information for ORIGIN
+ GasPrice *big.Int // Provides information for GASPRICE
+
+ // Block information
+ Coinbase common.Address // Provides information for COINBASE
+ GasLimit *big.Int // Provides information for GASLIMIT
+ BlockNumber *big.Int // Provides information for NUMBER
+ Time *big.Int // Provides information for TIME
+ Difficulty *big.Int // Provides information for DIFFICULTY
+}
+
+// Environment provides information about external sources for the EVM
+//
+// The Environment should never be reused and is not thread safe.
+type Environment struct {
+ // Context provides auxiliary blockchain related information
+ Context
+ // StateDB gives access to the underlying state
+ StateDB StateDB
+ // Depth is the current call stack
+ Depth int
+
+ // evm is the ethereum virtual machine
+ evm Vm
+ // chainConfig contains information about the current chain
+ chainConfig *params.ChainConfig
+ vmConfig Config
+}
+
+// NewEnvironment retutrns a new EVM environment.
+func NewEnvironment(context Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *Environment {
+ env := &Environment{
+ Context: context,
+ StateDB: statedb,
+ vmConfig: vmConfig,
+ chainConfig: chainConfig,
+ }
+ env.evm = New(env, vmConfig)
+ return env
+}
+
+// Call executes the contract associated with the addr with the given input as paramaters. It also handles any
+// necessary value transfer required and takes the necessary steps to create accounts and reverses the state in
+// case of an execution error or failed value transfer.
+func (env *Environment) Call(caller ContractRef, addr common.Address, input []byte, gas, value *big.Int) ([]byte, error) {
+ if env.vmConfig.NoRecursion && env.Depth > 0 {
+ caller.ReturnGas(gas)
+
+ return nil, nil
+ }
+
+ // Depth check execution. Fail if we're trying to execute above the
+ // limit.
+ if env.Depth > int(params.CallCreateDepth.Int64()) {
+ caller.ReturnGas(gas)
+
+ return nil, DepthError
+ }
+ if !env.Context.CanTransfer(env.StateDB, caller.Address(), value) {
+ caller.ReturnGas(gas)
+
+ return nil, ErrInsufficientBalance
+ }
+
+ var (
+ to Account
+ snapshotPreTransfer = env.StateDB.Snapshot()
+ )
+ if !env.StateDB.Exist(addr) {
+ if Precompiled[addr.Str()] == nil && env.ChainConfig().IsEIP158(env.BlockNumber) && value.BitLen() == 0 {
+ caller.ReturnGas(gas)
+ return nil, nil
+ }
+
+ to = env.StateDB.CreateAccount(addr)
+ } else {
+ to = env.StateDB.GetAccount(addr)
+ }
+ env.Transfer(env.StateDB, caller.Address(), to.Address(), value)
+
+ // initialise a new contract and set the code that is to be used by the
+ // E The contract is a scoped environment for this execution context
+ // only.
+ contract := NewContract(caller, to, value, gas)
+ contract.SetCallCode(&addr, env.StateDB.GetCodeHash(addr), env.StateDB.GetCode(addr))
+ defer contract.Finalise()
+
+ ret, err := env.EVM().Run(contract, input)
+ // When an error was returned by the EVM or when setting the creation code
+ // above we revert to the snapshot and consume any gas remaining. Additionally
+ // when we're in homestead this also counts for code storage gas errors.
+ if err != nil {
+ contract.UseGas(contract.Gas)
+
+ env.StateDB.RevertToSnapshot(snapshotPreTransfer)
+ }
+ return ret, err
}
-// Vm is the basic interface for an implementation of the EVM.
-type Vm interface {
- // Run should execute the given contract with the input given in in
- // and return the contract execution return bytes or an error if it
- // failed.
- Run(c *Contract, in []byte) ([]byte, error)
+// CallCode executes the contract associated with the addr with the given input as paramaters. It also handles any
+// necessary value transfer required and takes the necessary steps to create accounts and reverses the state in
+// case of an execution error or failed value transfer.
+//
+// CallCode differs from Call in the sense that it executes the given address' code with the caller as context.
+func (env *Environment) CallCode(caller ContractRef, addr common.Address, input []byte, gas, value *big.Int) ([]byte, error) {
+ if env.vmConfig.NoRecursion && env.Depth > 0 {
+ caller.ReturnGas(gas)
+
+ return nil, nil
+ }
+
+ // Depth check execution. Fail if we're trying to execute above the
+ // limit.
+ if env.Depth > int(params.CallCreateDepth.Int64()) {
+ caller.ReturnGas(gas)
+
+ return nil, DepthError
+ }
+ if !env.CanTransfer(env.StateDB, caller.Address(), value) {
+ caller.ReturnGas(gas)
+
+ return nil, fmt.Errorf("insufficient funds to transfer value. Req %v, has %v", value, env.StateDB.GetBalance(caller.Address()))
+ }
+
+ var (
+ snapshotPreTransfer = env.StateDB.Snapshot()
+ to = env.StateDB.GetAccount(caller.Address())
+ )
+ // initialise a new contract and set the code that is to be used by the
+ // E The contract is a scoped environment for this execution context
+ // only.
+ contract := NewContract(caller, to, value, gas)
+ contract.SetCallCode(&addr, env.StateDB.GetCodeHash(addr), env.StateDB.GetCode(addr))
+ defer contract.Finalise()
+
+ ret, err := env.EVM().Run(contract, input)
+ if err != nil {
+ contract.UseGas(contract.Gas)
+
+ env.StateDB.RevertToSnapshot(snapshotPreTransfer)
+ }
+
+ return ret, err
}
-// Database is a EVM database for full state querying.
-type Database interface {
- GetAccount(common.Address) Account
- CreateAccount(common.Address) Account
+// DelegateCall executes the contract associated with the addr with the given input as paramaters.
+// It reverses the state in case of an execution error.
+//
+// DelegateCall differs from CallCode in the sense that it executes the given address' code with the caller as context
+// and the caller is set to the caller of the caller.
+func (env *Environment) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas *big.Int) ([]byte, error) {
+ if env.vmConfig.NoRecursion && env.Depth > 0 {
+ caller.ReturnGas(gas)
- AddBalance(common.Address, *big.Int)
- GetBalance(common.Address) *big.Int
+ return nil, nil
+ }
- GetNonce(common.Address) uint64
- SetNonce(common.Address, uint64)
+ // Depth check execution. Fail if we're trying to execute above the
+ // limit.
+ if env.Depth > int(params.CallCreateDepth.Int64()) {
+ caller.ReturnGas(gas)
+ return nil, DepthError
+ }
- GetCodeHash(common.Address) common.Hash
- GetCodeSize(common.Address) int
- GetCode(common.Address) []byte
- SetCode(common.Address, []byte)
+ var (
+ snapshot = env.StateDB.Snapshot()
+ to = env.StateDB.GetAccount(caller.Address())
+ )
- AddRefund(*big.Int)
- GetRefund() *big.Int
+ // Iinitialise a new contract and make initialise the delegate values
+ contract := NewContract(caller, to, caller.Value(), gas).AsDelegate()
+ contract.SetCallCode(&addr, env.StateDB.GetCodeHash(addr), env.StateDB.GetCode(addr))
+ defer contract.Finalise()
- GetState(common.Address, common.Hash) common.Hash
- SetState(common.Address, common.Hash, common.Hash)
+ ret, err := env.EVM().Run(contract, input)
+ if err != nil {
+ contract.UseGas(contract.Gas)
- Suicide(common.Address) bool
- HasSuicided(common.Address) bool
+ env.StateDB.RevertToSnapshot(snapshot)
+ }
- // Exist reports whether the given account exists in state.
- // Notably this should also return true for suicided accounts.
- Exist(common.Address) bool
- // Empty returns whether the given account is empty. Empty
- // is defined according to EIP161 (balance = nonce = code = 0).
- Empty(common.Address) bool
+ return ret, err
}
-// Account represents a contract or basic ethereum account.
-type Account interface {
- SubBalance(amount *big.Int)
- AddBalance(amount *big.Int)
- SetBalance(*big.Int)
- SetNonce(uint64)
- Balance() *big.Int
- Address() common.Address
- ReturnGas(*big.Int, *big.Int)
- SetCode(common.Hash, []byte)
- ForEachStorage(cb func(key, value common.Hash) bool)
- Value() *big.Int
+// Create creates a new contract using code as deployment code.
+func (env *Environment) Create(caller ContractRef, code []byte, gas, value *big.Int) ([]byte, common.Address, error) {
+ if env.vmConfig.NoRecursion && env.Depth > 0 {
+ caller.ReturnGas(gas)
+
+ return nil, common.Address{}, nil
+ }
+
+ // Depth check execution. Fail if we're trying to execute above the
+ // limit.
+ if env.Depth > int(params.CallCreateDepth.Int64()) {
+ caller.ReturnGas(gas)
+
+ return nil, common.Address{}, DepthError
+ }
+ if !env.CanTransfer(env.StateDB, caller.Address(), value) {
+ caller.ReturnGas(gas)
+
+ return nil, common.Address{}, ErrInsufficientBalance
+ }
+
+ // Create a new account on the state
+ nonce := env.StateDB.GetNonce(caller.Address())
+ env.StateDB.SetNonce(caller.Address(), nonce+1)
+
+ snapshotPreTransfer := env.StateDB.Snapshot()
+ var (
+ addr = crypto.CreateAddress(caller.Address(), nonce)
+ to = env.StateDB.CreateAccount(addr)
+ )
+ if env.ChainConfig().IsEIP158(env.BlockNumber) {
+ env.StateDB.SetNonce(addr, 1)
+ }
+ env.Transfer(env.StateDB, caller.Address(), to.Address(), value)
+
+ // initialise a new contract and set the code that is to be used by the
+ // E The contract is a scoped environment for this execution context
+ // only.
+ contract := NewContract(caller, to, value, gas)
+ contract.SetCallCode(&addr, crypto.Keccak256Hash(code), code)
+ defer contract.Finalise()
+
+ ret, err := env.EVM().Run(contract, nil)
+ // check whether the max code size has been exceeded
+ maxCodeSizeExceeded := len(ret) > params.MaxCodeSize
+ // if the contract creation ran successfully and no errors were returned
+ // calculate the gas required to store the code. If the code could not
+ // be stored due to not enough gas set an error and let it be handled
+ // by the error checking condition below.
+ if err == nil && !maxCodeSizeExceeded {
+ dataGas := big.NewInt(int64(len(ret)))
+ dataGas.Mul(dataGas, params.CreateDataGas)
+ if contract.UseGas(dataGas) {
+ env.StateDB.SetCode(addr, ret)
+ } else {
+ err = CodeStoreOutOfGasError
+ }
+ }
+
+ // When an error was returned by the EVM or when setting the creation code
+ // above we revert to the snapshot and consume any gas remaining. Additionally
+ // when we're in homestead this also counts for code storage gas errors.
+ if maxCodeSizeExceeded ||
+ (err != nil && (env.ChainConfig().IsHomestead(env.BlockNumber) || err != CodeStoreOutOfGasError)) {
+ contract.UseGas(contract.Gas)
+ env.StateDB.RevertToSnapshot(snapshotPreTransfer)
+
+ // Nothing should be returned when an error is thrown.
+ return nil, addr, err
+ }
+ // If the vm returned with an error the return value should be set to nil.
+ // This isn't consensus critical but merely to for behaviour reasons such as
+ // tests, RPC calls, etc.
+ if err != nil {
+ ret = nil
+ }
+
+ return ret, addr, err
}
+
+// ChainConfig returns the environment's chain configuration
+func (env *Environment) ChainConfig() *params.ChainConfig { return env.chainConfig }
+
+// EVM returns the environments EVM
+func (env *Environment) EVM() Vm { return env.evm }
diff --git a/core/vm/errors.go b/core/vm/errors.go
index 1766bf9fb..f8d26b1f0 100644
--- a/core/vm/errors.go
+++ b/core/vm/errors.go
@@ -23,7 +23,10 @@ import (
"github.com/ethereum/go-ethereum/params"
)
-var OutOfGasError = errors.New("Out of gas")
-var CodeStoreOutOfGasError = errors.New("Contract creation code storage out of gas")
-var DepthError = fmt.Errorf("Max call depth exceeded (%d)", params.CallCreateDepth)
-var TraceLimitReachedError = errors.New("The number of logs reached the specified limit")
+var (
+ OutOfGasError = errors.New("Out of gas")
+ CodeStoreOutOfGasError = errors.New("Contract creation code storage out of gas")
+ DepthError = fmt.Errorf("Max call depth exceeded (%d)", params.CallCreateDepth)
+ TraceLimitReachedError = errors.New("The number of logs reached the specified limit")
+ ErrInsufficientBalance = errors.New("insufficient balance for transfer")
+)
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 4f98953b5..871c09e83 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -28,14 +28,14 @@ import (
type programInstruction interface {
// executes the program instruction and allows the instruction to modify the state of the program
- do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)
+ do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)
// returns whether the program instruction halts the execution of the JIT
halts() bool
// Returns the current op code (debugging purposes)
Op() OpCode
}
-type instrFn func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack)
+type instrFn func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack)
type instruction struct {
op OpCode
@@ -59,9 +59,9 @@ func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract
return mapping[to.Uint64()], nil
}
-func (instr instruction) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
+func (instr instruction) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// calculate the new memory size and gas price for the current executing opcode
- newMemSize, cost, err := jitCalculateGasAndSize(env, contract, instr, env.Db(), memory, stack)
+ newMemSize, cost, err := jitCalculateGasAndSize(env, contract, instr, memory, stack)
if err != nil {
return nil, err
}
@@ -115,26 +115,26 @@ func (instr instruction) Op() OpCode {
return instr.op
}
-func opStaticJump(instr instruction, pc *uint64, ret *big.Int, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opStaticJump(instr instruction, pc *uint64, ret *big.Int, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
ret.Set(instr.data)
}
-func opAdd(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opAdd(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
stack.push(U256(x.Add(x, y)))
}
-func opSub(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opSub(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
stack.push(U256(x.Sub(x, y)))
}
-func opMul(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opMul(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
stack.push(U256(x.Mul(x, y)))
}
-func opDiv(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opDiv(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
if y.Cmp(common.Big0) != 0 {
stack.push(U256(x.Div(x, y)))
@@ -143,7 +143,7 @@ func opDiv(instr instruction, pc *uint64, env Environment, contract *Contract, m
}
}
-func opSdiv(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opSdiv(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := S256(stack.pop()), S256(stack.pop())
if y.Cmp(common.Big0) == 0 {
stack.push(new(big.Int))
@@ -163,7 +163,7 @@ func opSdiv(instr instruction, pc *uint64, env Environment, contract *Contract,
}
}
-func opMod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opMod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
if y.Cmp(common.Big0) == 0 {
stack.push(new(big.Int))
@@ -172,7 +172,7 @@ func opMod(instr instruction, pc *uint64, env Environment, contract *Contract, m
}
}
-func opSmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opSmod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := S256(stack.pop()), S256(stack.pop())
if y.Cmp(common.Big0) == 0 {
@@ -192,12 +192,12 @@ func opSmod(instr instruction, pc *uint64, env Environment, contract *Contract,
}
}
-func opExp(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opExp(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
base, exponent := stack.pop(), stack.pop()
stack.push(math.Exp(base, exponent))
}
-func opSignExtend(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opSignExtend(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
back := stack.pop()
if back.Cmp(big.NewInt(31)) < 0 {
bit := uint(back.Uint64()*8 + 7)
@@ -214,12 +214,12 @@ func opSignExtend(instr instruction, pc *uint64, env Environment, contract *Cont
}
}
-func opNot(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opNot(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x := stack.pop()
stack.push(U256(x.Not(x)))
}
-func opLt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opLt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
if x.Cmp(y) < 0 {
stack.push(big.NewInt(1))
@@ -228,7 +228,7 @@ func opLt(instr instruction, pc *uint64, env Environment, contract *Contract, me
}
}
-func opGt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opGt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
if x.Cmp(y) > 0 {
stack.push(big.NewInt(1))
@@ -237,7 +237,7 @@ func opGt(instr instruction, pc *uint64, env Environment, contract *Contract, me
}
}
-func opSlt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opSlt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := S256(stack.pop()), S256(stack.pop())
if x.Cmp(S256(y)) < 0 {
stack.push(big.NewInt(1))
@@ -246,7 +246,7 @@ func opSlt(instr instruction, pc *uint64, env Environment, contract *Contract, m
}
}
-func opSgt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opSgt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := S256(stack.pop()), S256(stack.pop())
if x.Cmp(y) > 0 {
stack.push(big.NewInt(1))
@@ -255,7 +255,7 @@ func opSgt(instr instruction, pc *uint64, env Environment, contract *Contract, m
}
}
-func opEq(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opEq(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
if x.Cmp(y) == 0 {
stack.push(big.NewInt(1))
@@ -264,7 +264,7 @@ func opEq(instr instruction, pc *uint64, env Environment, contract *Contract, me
}
}
-func opIszero(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opIszero(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x := stack.pop()
if x.Cmp(common.Big0) > 0 {
stack.push(new(big.Int))
@@ -273,19 +273,19 @@ func opIszero(instr instruction, pc *uint64, env Environment, contract *Contract
}
}
-func opAnd(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opAnd(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
stack.push(x.And(x, y))
}
-func opOr(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opOr(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
stack.push(x.Or(x, y))
}
-func opXor(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opXor(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop()
stack.push(x.Xor(x, y))
}
-func opByte(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opByte(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
th, val := stack.pop(), stack.pop()
if th.Cmp(big.NewInt(32)) < 0 {
byte := big.NewInt(int64(common.LeftPadBytes(val.Bytes(), 32)[th.Int64()]))
@@ -294,7 +294,7 @@ func opByte(instr instruction, pc *uint64, env Environment, contract *Contract,
stack.push(new(big.Int))
}
}
-func opAddmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opAddmod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y, z := stack.pop(), stack.pop(), stack.pop()
if z.Cmp(Zero) > 0 {
add := x.Add(x, y)
@@ -304,7 +304,7 @@ func opAddmod(instr instruction, pc *uint64, env Environment, contract *Contract
stack.push(new(big.Int))
}
}
-func opMulmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opMulmod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y, z := stack.pop(), stack.pop(), stack.pop()
if z.Cmp(Zero) > 0 {
mul := x.Mul(x, y)
@@ -315,45 +315,45 @@ func opMulmod(instr instruction, pc *uint64, env Environment, contract *Contract
}
}
-func opSha3(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opSha3(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
offset, size := stack.pop(), stack.pop()
hash := crypto.Keccak256(memory.Get(offset.Int64(), size.Int64()))
stack.push(common.BytesToBig(hash))
}
-func opAddress(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opAddress(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(common.Bytes2Big(contract.Address().Bytes()))
}
-func opBalance(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opBalance(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
addr := common.BigToAddress(stack.pop())
- balance := env.Db().GetBalance(addr)
+ balance := env.StateDB.GetBalance(addr)
stack.push(new(big.Int).Set(balance))
}
-func opOrigin(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
- stack.push(env.Origin().Big())
+func opOrigin(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
+ stack.push(env.Origin.Big())
}
-func opCaller(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opCaller(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(contract.Caller().Big())
}
-func opCallValue(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opCallValue(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(new(big.Int).Set(contract.value))
}
-func opCalldataLoad(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opCalldataLoad(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(common.Bytes2Big(getData(contract.Input, stack.pop(), common.Big32)))
}
-func opCalldataSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opCalldataSize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(big.NewInt(int64(len(contract.Input))))
}
-func opCalldataCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opCalldataCopy(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
var (
mOff = stack.pop()
cOff = stack.pop()
@@ -362,18 +362,18 @@ func opCalldataCopy(instr instruction, pc *uint64, env Environment, contract *Co
memory.Set(mOff.Uint64(), l.Uint64(), getData(contract.Input, cOff, l))
}
-func opExtCodeSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opExtCodeSize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
addr := common.BigToAddress(stack.pop())
- l := big.NewInt(int64(env.Db().GetCodeSize(addr)))
+ l := big.NewInt(int64(env.StateDB.GetCodeSize(addr)))
stack.push(l)
}
-func opCodeSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opCodeSize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
l := big.NewInt(int64(len(contract.Code)))
stack.push(l)
}
-func opCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opCodeCopy(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
var (
mOff = stack.pop()
cOff = stack.pop()
@@ -384,70 +384,70 @@ func opCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contra
memory.Set(mOff.Uint64(), l.Uint64(), codeCopy)
}
-func opExtCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opExtCodeCopy(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
var (
addr = common.BigToAddress(stack.pop())
mOff = stack.pop()
cOff = stack.pop()
l = stack.pop()
)
- codeCopy := getData(env.Db().GetCode(addr), cOff, l)
+ codeCopy := getData(env.StateDB.GetCode(addr), cOff, l)
memory.Set(mOff.Uint64(), l.Uint64(), codeCopy)
}
-func opGasprice(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
- stack.push(new(big.Int).Set(contract.Price))
+func opGasprice(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
+ stack.push(new(big.Int).Set(env.GasPrice))
}
-func opBlockhash(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opBlockhash(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
num := stack.pop()
- n := new(big.Int).Sub(env.BlockNumber(), common.Big257)
- if num.Cmp(n) > 0 && num.Cmp(env.BlockNumber()) < 0 {
+ n := new(big.Int).Sub(env.BlockNumber, common.Big257)
+ if num.Cmp(n) > 0 && num.Cmp(env.BlockNumber) < 0 {
stack.push(env.GetHash(num.Uint64()).Big())
} else {
stack.push(new(big.Int))
}
}
-func opCoinbase(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
- stack.push(env.Coinbase().Big())
+func opCoinbase(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
+ stack.push(env.Coinbase.Big())
}
-func opTimestamp(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
- stack.push(U256(new(big.Int).Set(env.Time())))
+func opTimestamp(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
+ stack.push(U256(new(big.Int).Set(env.Time)))
}
-func opNumber(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
- stack.push(U256(new(big.Int).Set(env.BlockNumber())))
+func opNumber(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
+ stack.push(U256(new(big.Int).Set(env.BlockNumber)))
}
-func opDifficulty(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
- stack.push(U256(new(big.Int).Set(env.Difficulty())))
+func opDifficulty(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
+ stack.push(U256(new(big.Int).Set(env.Difficulty)))
}
-func opGasLimit(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
- stack.push(U256(new(big.Int).Set(env.GasLimit())))
+func opGasLimit(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
+ stack.push(U256(new(big.Int).Set(env.GasLimit)))
}
-func opPop(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opPop(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.pop()
}
-func opPush(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opPush(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(new(big.Int).Set(instr.data))
}
-func opDup(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opDup(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.dup(int(instr.data.Int64()))
}
-func opSwap(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opSwap(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.swap(int(instr.data.Int64()))
}
-func opLog(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opLog(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
n := int(instr.data.Int64())
topics := make([]common.Hash, n)
mStart, mSize := stack.pop(), stack.pop()
@@ -456,77 +456,77 @@ func opLog(instr instruction, pc *uint64, env Environment, contract *Contract, m
}
d := memory.Get(mStart.Int64(), mSize.Int64())
- log := NewLog(contract.Address(), topics, d, env.BlockNumber().Uint64())
- env.AddLog(log)
+ log := NewLog(contract.Address(), topics, d, env.BlockNumber.Uint64())
+ env.StateDB.AddLog(log)
}
-func opMload(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opMload(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
offset := stack.pop()
val := common.BigD(memory.Get(offset.Int64(), 32))
stack.push(val)
}
-func opMstore(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opMstore(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
// pop value of the stack
mStart, val := stack.pop(), stack.pop()
memory.Set(mStart.Uint64(), 32, common.BigToBytes(val, 256))
}
-func opMstore8(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opMstore8(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
off, val := stack.pop().Int64(), stack.pop().Int64()
memory.store[off] = byte(val & 0xff)
}
-func opSload(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opSload(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
loc := common.BigToHash(stack.pop())
- val := env.Db().GetState(contract.Address(), loc).Big()
+ val := env.StateDB.GetState(contract.Address(), loc).Big()
stack.push(val)
}
-func opSstore(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opSstore(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
loc := common.BigToHash(stack.pop())
val := stack.pop()
- env.Db().SetState(contract.Address(), loc, common.BigToHash(val))
+ env.StateDB.SetState(contract.Address(), loc, common.BigToHash(val))
}
-func opJump(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opJump(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
}
-func opJumpi(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opJumpi(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
}
-func opJumpdest(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opJumpdest(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
}
-func opPc(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opPc(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(new(big.Int).Set(instr.data))
}
-func opMsize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opMsize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(big.NewInt(int64(memory.Len())))
}
-func opGas(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opGas(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(new(big.Int).Set(contract.Gas))
}
-func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opCreate(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
var (
value = stack.pop()
offset, size = stack.pop(), stack.pop()
input = memory.Get(offset.Int64(), size.Int64())
gas = new(big.Int).Set(contract.Gas)
)
- if env.ChainConfig().IsEIP150(env.BlockNumber()) {
+ if env.ChainConfig().IsEIP150(env.BlockNumber) {
gas.Div(gas, n64)
gas = gas.Sub(contract.Gas, gas)
}
contract.UseGas(gas)
- _, addr, suberr := env.Create(contract, input, gas, contract.Price, value)
+ _, addr, suberr := env.Create(contract, input, gas, value)
// Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must
// ignore this error and pretend the operation was successful.
- if env.ChainConfig().IsHomestead(env.BlockNumber()) && suberr == CodeStoreOutOfGasError {
+ if env.ChainConfig().IsHomestead(env.BlockNumber) && suberr == CodeStoreOutOfGasError {
stack.push(new(big.Int))
} else if suberr != nil && suberr != CodeStoreOutOfGasError {
stack.push(new(big.Int))
@@ -535,7 +535,7 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract
}
}
-func opCall(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opCall(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
gas := stack.pop()
// pop gas and value of the stack.
addr, value := stack.pop(), stack.pop()
@@ -554,7 +554,7 @@ func opCall(instr instruction, pc *uint64, env Environment, contract *Contract,
gas.Add(gas, params.CallStipend)
}
- ret, err := env.Call(contract, address, args, gas, contract.Price, value)
+ ret, err := env.Call(contract, address, args, gas, value)
if err != nil {
stack.push(new(big.Int))
@@ -566,7 +566,7 @@ func opCall(instr instruction, pc *uint64, env Environment, contract *Contract,
}
}
-func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opCallCode(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
gas := stack.pop()
// pop gas and value of the stack.
addr, value := stack.pop(), stack.pop()
@@ -585,7 +585,7 @@ func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contra
gas.Add(gas, params.CallStipend)
}
- ret, err := env.CallCode(contract, address, args, gas, contract.Price, value)
+ ret, err := env.CallCode(contract, address, args, gas, value)
if err != nil {
stack.push(new(big.Int))
@@ -597,12 +597,12 @@ func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contra
}
}
-func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opDelegateCall(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
gas, to, inOffset, inSize, outOffset, outSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
toAddr := common.BigToAddress(to)
args := memory.Get(inOffset.Int64(), inSize.Int64())
- ret, err := env.DelegateCall(contract, toAddr, args, gas, contract.Price)
+ ret, err := env.DelegateCall(contract, toAddr, args, gas)
if err != nil {
stack.push(new(big.Int))
} else {
@@ -611,23 +611,23 @@ func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Co
}
}
-func opReturn(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opReturn(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
}
-func opStop(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+func opStop(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
}
-func opSuicide(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
- balance := env.Db().GetBalance(contract.Address())
- env.Db().AddBalance(common.BigToAddress(stack.pop()), balance)
+func opSuicide(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
+ balance := env.StateDB.GetBalance(contract.Address())
+ env.StateDB.AddBalance(common.BigToAddress(stack.pop()), balance)
- env.Db().Suicide(contract.Address())
+ env.StateDB.Suicide(contract.Address())
}
// following functions are used by the instruction jump table
// make log instruction function
func makeLog(size int) instrFn {
- return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+ return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
topics := make([]common.Hash, size)
mStart, mSize := stack.pop(), stack.pop()
for i := 0; i < size; i++ {
@@ -635,14 +635,14 @@ func makeLog(size int) instrFn {
}
d := memory.Get(mStart.Int64(), mSize.Int64())
- log := NewLog(contract.Address(), topics, d, env.BlockNumber().Uint64())
- env.AddLog(log)
+ log := NewLog(contract.Address(), topics, d, env.BlockNumber.Uint64())
+ env.StateDB.AddLog(log)
}
}
// make push instruction function
func makePush(size uint64, bsize *big.Int) instrFn {
- return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+ return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
byts := getData(contract.Code, new(big.Int).SetUint64(*pc+1), bsize)
stack.push(common.Bytes2Big(byts))
*pc += size
@@ -651,7 +651,7 @@ func makePush(size uint64, bsize *big.Int) instrFn {
// make push instruction function
func makeDup(size int64) instrFn {
- return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+ return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.dup(int(size))
}
}
@@ -660,7 +660,7 @@ func makeDup(size int64) instrFn {
func makeSwap(size int64) instrFn {
// switch n + 1 otherwise n would be swapped with n
size += 1
- return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) {
+ return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.swap(int(size))
}
}
diff --git a/core/vm/interface.go b/core/vm/interface.go
new file mode 100644
index 000000000..918fde85f
--- /dev/null
+++ b/core/vm/interface.go
@@ -0,0 +1,97 @@
+// Copyright 2014 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 vm
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// Vm is the basic interface for an implementation of the EVM.
+type Vm interface {
+ // Run should execute the given contract with the input given in in
+ // and return the contract execution return bytes or an error if it
+ // failed.
+ Run(c *Contract, in []byte) ([]byte, error)
+}
+
+// StateDB is an EVM database for full state querying.
+type StateDB interface {
+ GetAccount(common.Address) Account
+ CreateAccount(common.Address) Account
+
+ SubBalance(common.Address, *big.Int)
+ AddBalance(common.Address, *big.Int)
+ GetBalance(common.Address) *big.Int
+
+ GetNonce(common.Address) uint64
+ SetNonce(common.Address, uint64)
+
+ GetCodeHash(common.Address) common.Hash
+ GetCode(common.Address) []byte
+ SetCode(common.Address, []byte)
+ GetCodeSize(common.Address) int
+
+ AddRefund(*big.Int)
+ GetRefund() *big.Int
+
+ GetState(common.Address, common.Hash) common.Hash
+ SetState(common.Address, common.Hash, common.Hash)
+
+ Suicide(common.Address) bool
+ HasSuicided(common.Address) bool
+
+ // Exist reports whether the given account exists in state.
+ // Notably this should also return true for suicided accounts.
+ Exist(common.Address) bool
+ // Empty returns whether the given account is empty. Empty
+ // is defined according to EIP161 (balance = nonce = code = 0).
+ Empty(common.Address) bool
+
+ RevertToSnapshot(int)
+ Snapshot() int
+
+ AddLog(*Log)
+}
+
+// Account represents a contract or basic ethereum account.
+type Account interface {
+ SubBalance(amount *big.Int)
+ AddBalance(amount *big.Int)
+ SetBalance(*big.Int)
+ SetNonce(uint64)
+ Balance() *big.Int
+ Address() common.Address
+ ReturnGas(*big.Int)
+ SetCode(common.Hash, []byte)
+ ForEachStorage(cb func(key, value common.Hash) bool)
+ Value() *big.Int
+}
+
+// CallContext provides a basic interface for the EVM calling conventions. The EVM Environment
+// depends on this context being implemented for doing subcalls and initialising new EVM contracts.
+type CallContext interface {
+ // Call another contract
+ Call(env *Environment, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error)
+ // Take another's contract code and execute within our own context
+ CallCode(env *Environment, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error)
+ // Same as CallCode except sender and value is propagated from parent to child scope
+ DelegateCall(env *Environment, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error)
+ // Create a new contract
+ Create(env *Environment, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error)
+}
diff --git a/core/vm/jit.go b/core/vm/jit.go
index b75558d39..aabe4488b 100644
--- a/core/vm/jit.go
+++ b/core/vm/jit.go
@@ -299,11 +299,11 @@ func CompileProgram(program *Program) (err error) {
// RunProgram runs the program given the environment and contract and returns an
// error if the execution failed (non-consensus)
-func RunProgram(program *Program, env Environment, contract *Contract, input []byte) ([]byte, error) {
+func RunProgram(program *Program, env *Environment, contract *Contract, input []byte) ([]byte, error) {
return runProgram(program, 0, NewMemory(), newstack(), env, contract, input)
}
-func runProgram(program *Program, pcstart uint64, mem *Memory, stack *Stack, env Environment, contract *Contract, input []byte) ([]byte, error) {
+func runProgram(program *Program, pcstart uint64, mem *Memory, stack *Stack, env *Environment, contract *Contract, input []byte) ([]byte, error) {
contract.Input = input
var (
@@ -319,7 +319,7 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *Stack, env
}()
}
- homestead := env.ChainConfig().IsHomestead(env.BlockNumber())
+ homestead := env.ChainConfig().IsHomestead(env.BlockNumber)
for pc < uint64(len(program.instructions)) {
instrCount++
@@ -357,7 +357,7 @@ func validDest(dests map[uint64]struct{}, dest *big.Int) bool {
// jitCalculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for
// the operation. This does not reduce gas or resizes the memory.
-func jitCalculateGasAndSize(env Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) {
+func jitCalculateGasAndSize(env *Environment, contract *Contract, instr instruction, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) {
var (
gas = new(big.Int)
newMemSize *big.Int = new(big.Int)
@@ -408,7 +408,7 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi
var g *big.Int
y, x := stack.data[stack.len()-2], stack.data[stack.len()-1]
- val := statedb.GetState(contract.Address(), common.BigToHash(x))
+ val := env.StateDB.GetState(contract.Address(), common.BigToHash(x))
// This checks for 3 scenario's and calculates gas accordingly
// 1. From a zero-value address to a non-zero value (NEW VALUE)
@@ -417,7 +417,7 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi
if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) {
g = params.SstoreSetGas
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) {
- statedb.AddRefund(params.SstoreRefundGas)
+ env.StateDB.AddRefund(params.SstoreRefundGas)
g = params.SstoreClearGas
} else {
@@ -425,8 +425,8 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi
}
gas.Set(g)
case SUICIDE:
- if !statedb.HasSuicided(contract.Address()) {
- statedb.AddRefund(params.SuicideRefundGas)
+ if !env.StateDB.HasSuicided(contract.Address()) {
+ env.StateDB.AddRefund(params.SuicideRefundGas)
}
case MLOAD:
newMemSize = calcMemSize(stack.peek(), u256(32))
@@ -463,7 +463,7 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi
gas.Add(gas, stack.data[stack.len()-1])
if op == CALL {
- if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) {
+ if !env.StateDB.Exist(common.BigToAddress(stack.data[stack.len()-2])) {
gas.Add(gas, params.CallNewAccountGas)
}
}
diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go
index 6f7ba9250..79c389c05 100644
--- a/core/vm/jit_test.go
+++ b/core/vm/jit_test.go
@@ -19,10 +19,8 @@ package vm
import (
"math/big"
"testing"
- "time"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
@@ -86,8 +84,8 @@ func TestCompiling(t *testing.T) {
func TestResetInput(t *testing.T) {
var sender account
- env := NewEnv(&Config{EnableJit: true, ForceJit: true})
- contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0))
+ env := NewEnvironment(Context{}, nil, params.TestChainConfig, Config{})
+ contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000))
contract.CodeAddr = &common.Address{}
program := NewProgram([]byte{})
@@ -135,7 +133,7 @@ func (account) SetBalance(*big.Int) {}
func (account) SetNonce(uint64) {}
func (account) Balance() *big.Int { return nil }
func (account) Address() common.Address { return common.Address{} }
-func (account) ReturnGas(*big.Int, *big.Int) {}
+func (account) ReturnGas(*big.Int) {}
func (account) SetCode(common.Hash, []byte) {}
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
@@ -145,70 +143,18 @@ func runVmBench(test vmBench, b *testing.B) {
if test.precompile && !test.forcejit {
NewProgram(test.code)
}
- env := NewEnv(&Config{EnableJit: !test.nojit, ForceJit: test.forcejit})
+ env := NewEnvironment(Context{}, nil, params.TestChainConfig, Config{EnableJit: !test.nojit, ForceJit: test.forcejit})
b.ResetTimer()
for i := 0; i < b.N; i++ {
- context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0))
+ context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000))
context.Code = test.code
context.CodeAddr = &common.Address{}
- _, err := env.Vm().Run(context, test.input)
+ _, err := env.EVM().Run(context, test.input)
if err != nil {
b.Error(err)
b.FailNow()
}
}
}
-
-type Env struct {
- gasLimit *big.Int
- depth int
- evm *EVM
-}
-
-func NewEnv(config *Config) *Env {
- env := &Env{gasLimit: big.NewInt(10000), depth: 0}
- env.evm = New(env, *config)
- return env
-}
-
-func (self *Env) ChainConfig() *params.ChainConfig {
- return params.TestChainConfig
-}
-func (self *Env) Vm() Vm { return self.evm }
-func (self *Env) Origin() common.Address { return common.Address{} }
-func (self *Env) BlockNumber() *big.Int { return big.NewInt(0) }
-
-//func (self *Env) PrevHash() []byte { return self.parent }
-func (self *Env) Coinbase() common.Address { return common.Address{} }
-func (self *Env) SnapshotDatabase() int { return 0 }
-func (self *Env) RevertToSnapshot(int) {}
-func (self *Env) Time() *big.Int { return big.NewInt(time.Now().Unix()) }
-func (self *Env) Difficulty() *big.Int { return big.NewInt(0) }
-func (self *Env) Db() Database { return nil }
-func (self *Env) GasLimit() *big.Int { return self.gasLimit }
-func (self *Env) VmType() Type { return StdVmTy }
-func (self *Env) GetHash(n uint64) common.Hash {
- return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String())))
-}
-func (self *Env) AddLog(log *Log) {
-}
-func (self *Env) Depth() int { return self.depth }
-func (self *Env) SetDepth(i int) { self.depth = i }
-func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool {
- return true
-}
-func (self *Env) Transfer(from, to Account, amount *big.Int) {}
-func (self *Env) Call(caller ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return nil, nil
-}
-func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return nil, nil
-}
-func (self *Env) Create(caller ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
- return nil, common.Address{}, nil
-}
-func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
- return nil, nil
-}
diff --git a/core/vm/log.go b/core/vm/log.go
index 06f941703..347bd6e5d 100644
--- a/core/vm/log.go
+++ b/core/vm/log.go
@@ -29,20 +29,42 @@ import (
var errMissingLogFields = errors.New("missing required JSON log fields")
-// Log represents a contract log event. These events are generated by the LOG
-// opcode and stored/indexed by the node.
+// Log represents a contract log event. These events are generated by the LOG opcode and
+// stored/indexed by the node.
type Log struct {
// Consensus fields.
Address common.Address // address of the contract that generated the event
Topics []common.Hash // list of topics provided by the contract.
Data []byte // supplied by the contract, usually ABI-encoded
- // Derived fields (don't reorder!).
+ // Derived fields. These fields are filled in by the node
+ // but not secured by consensus.
BlockNumber uint64 // block in which the transaction was included
TxHash common.Hash // hash of the transaction
TxIndex uint // index of the transaction in the block
BlockHash common.Hash // hash of the block in which the transaction was included
Index uint // index of the log in the receipt
+
+ // The Removed field is true if this log was reverted due to a chain reorganisation.
+ // You must pay attention to this field if you receive logs through a filter query.
+ Removed bool
+}
+
+type rlpLog struct {
+ Address common.Address
+ Topics []common.Hash
+ Data []byte
+}
+
+type rlpStorageLog struct {
+ Address common.Address
+ Topics []common.Hash
+ Data []byte
+ BlockNumber uint64
+ TxHash common.Hash
+ TxIndex uint
+ BlockHash common.Hash
+ Index uint
}
type jsonLog struct {
@@ -54,27 +76,26 @@ type jsonLog struct {
TxHash *common.Hash `json:"transactionHash"`
BlockHash *common.Hash `json:"blockHash"`
Index *hexutil.Uint `json:"logIndex"`
+ Removed bool `json:"removed"`
}
func NewLog(address common.Address, topics []common.Hash, data []byte, number uint64) *Log {
return &Log{Address: address, Topics: topics, Data: data, BlockNumber: number}
}
+// EncodeRLP implements rlp.Encoder.
func (l *Log) EncodeRLP(w io.Writer) error {
- return rlp.Encode(w, []interface{}{l.Address, l.Topics, l.Data})
+ return rlp.Encode(w, rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data})
}
+// DecodeRLP implements rlp.Decoder.
func (l *Log) DecodeRLP(s *rlp.Stream) error {
- var log struct {
- Address common.Address
- Topics []common.Hash
- Data []byte
+ var dec rlpLog
+ err := s.Decode(&dec)
+ if err == nil {
+ l.Address, l.Topics, l.Data = dec.Address, dec.Topics, dec.Data
}
- if err := s.Decode(&log); err != nil {
- return err
- }
- l.Address, l.Topics, l.Data = log.Address, log.Topics, log.Data
- return nil
+ return err
}
func (l *Log) String() string {
@@ -82,45 +103,88 @@ func (l *Log) String() string {
}
// MarshalJSON implements json.Marshaler.
-func (r *Log) MarshalJSON() ([]byte, error) {
- return json.Marshal(&jsonLog{
- Address: &r.Address,
- Topics: &r.Topics,
- Data: (*hexutil.Bytes)(&r.Data),
- BlockNumber: (*hexutil.Uint64)(&r.BlockNumber),
- TxIndex: (*hexutil.Uint)(&r.TxIndex),
- TxHash: &r.TxHash,
- BlockHash: &r.BlockHash,
- Index: (*hexutil.Uint)(&r.Index),
- })
+func (l *Log) MarshalJSON() ([]byte, error) {
+ jslog := &jsonLog{
+ Address: &l.Address,
+ Topics: &l.Topics,
+ Data: (*hexutil.Bytes)(&l.Data),
+ TxIndex: (*hexutil.Uint)(&l.TxIndex),
+ TxHash: &l.TxHash,
+ Index: (*hexutil.Uint)(&l.Index),
+ Removed: l.Removed,
+ }
+ // Set block information for mined logs.
+ if (l.BlockHash != common.Hash{}) {
+ jslog.BlockHash = &l.BlockHash
+ jslog.BlockNumber = (*hexutil.Uint64)(&l.BlockNumber)
+ }
+ return json.Marshal(jslog)
}
// UnmarshalJSON implements json.Umarshaler.
-func (r *Log) UnmarshalJSON(input []byte) error {
+func (l *Log) UnmarshalJSON(input []byte) error {
var dec jsonLog
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
- if dec.Address == nil || dec.Topics == nil || dec.Data == nil || dec.BlockNumber == nil ||
- dec.TxIndex == nil || dec.TxHash == nil || dec.BlockHash == nil || dec.Index == nil {
+ if dec.Address == nil || dec.Topics == nil || dec.Data == nil ||
+ dec.TxIndex == nil || dec.TxHash == nil || dec.Index == nil {
return errMissingLogFields
}
- *r = Log{
- Address: *dec.Address,
- Topics: *dec.Topics,
- Data: *dec.Data,
- BlockNumber: uint64(*dec.BlockNumber),
- TxHash: *dec.TxHash,
- TxIndex: uint(*dec.TxIndex),
- BlockHash: *dec.BlockHash,
- Index: uint(*dec.Index),
+ declog := Log{
+ Address: *dec.Address,
+ Topics: *dec.Topics,
+ Data: *dec.Data,
+ TxHash: *dec.TxHash,
+ TxIndex: uint(*dec.TxIndex),
+ Index: uint(*dec.Index),
+ Removed: dec.Removed,
+ }
+ // Block information may be missing if the log is received through
+ // the pending log filter, so it's handled specially here.
+ if dec.BlockHash != nil && dec.BlockNumber != nil {
+ declog.BlockHash = *dec.BlockHash
+ declog.BlockNumber = uint64(*dec.BlockNumber)
}
+ *l = declog
return nil
}
type Logs []*Log
-// LogForStorage is a wrapper around a Log that flattens and parses the entire
-// content of a log, as opposed to only the consensus fields originally (by hiding
-// the rlp interface methods).
+// LogForStorage is a wrapper around a Log that flattens and parses the entire content of
+// a log including non-consensus fields.
type LogForStorage Log
+
+// EncodeRLP implements rlp.Encoder.
+func (l *LogForStorage) EncodeRLP(w io.Writer) error {
+ return rlp.Encode(w, rlpStorageLog{
+ Address: l.Address,
+ Topics: l.Topics,
+ Data: l.Data,
+ BlockNumber: l.BlockNumber,
+ TxHash: l.TxHash,
+ TxIndex: l.TxIndex,
+ BlockHash: l.BlockHash,
+ Index: l.Index,
+ })
+}
+
+// DecodeRLP implements rlp.Decoder.
+func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error {
+ var dec rlpStorageLog
+ err := s.Decode(&dec)
+ if err == nil {
+ *l = LogForStorage{
+ Address: dec.Address,
+ Topics: dec.Topics,
+ Data: dec.Data,
+ BlockNumber: dec.BlockNumber,
+ TxHash: dec.TxHash,
+ TxIndex: dec.TxIndex,
+ BlockHash: dec.BlockHash,
+ Index: dec.Index,
+ }
+ }
+ return err
+}
diff --git a/core/vm/log_test.go b/core/vm/log_test.go
index 4d3189558..994753c62 100644
--- a/core/vm/log_test.go
+++ b/core/vm/log_test.go
@@ -18,18 +18,81 @@ package vm
import (
"encoding/json"
+ "reflect"
"testing"
+
+ "github.com/davecgh/go-spew/spew"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
)
var unmarshalLogTests = map[string]struct {
input string
+ want *Log
wantError error
}{
"ok": {
- input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x000000000000000000000000000000000000000000000001a055690d9db80000","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
+ input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x000000000000000000000000000000000000000000000001a055690d9db80000","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
+ want: &Log{
+ Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
+ BlockHash: common.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"),
+ BlockNumber: 2019236,
+ Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000001a055690d9db80000"),
+ Index: 2,
+ TxIndex: 3,
+ TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
+ Topics: []common.Hash{
+ common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
+ common.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"),
+ },
+ },
},
"empty data": {
- input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
+ input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
+ want: &Log{
+ Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
+ BlockHash: common.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"),
+ BlockNumber: 2019236,
+ Data: []byte{},
+ Index: 2,
+ TxIndex: 3,
+ TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
+ Topics: []common.Hash{
+ common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
+ common.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"),
+ },
+ },
+ },
+ "missing block fields (pending logs)": {
+ input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","data":"0x","logIndex":"0x0","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
+ want: &Log{
+ Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
+ BlockHash: common.Hash{},
+ BlockNumber: 0,
+ Data: []byte{},
+ Index: 0,
+ TxIndex: 3,
+ TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
+ Topics: []common.Hash{
+ common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
+ },
+ },
+ },
+ "Removed: true": {
+ input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3","removed":true}`,
+ want: &Log{
+ Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
+ BlockHash: common.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"),
+ BlockNumber: 2019236,
+ Data: []byte{},
+ Index: 2,
+ TxIndex: 3,
+ TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
+ Topics: []common.Hash{
+ common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
+ },
+ Removed: true,
+ },
},
"missing data": {
input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
@@ -38,10 +101,16 @@ var unmarshalLogTests = map[string]struct {
}
func TestUnmarshalLog(t *testing.T) {
+ dumper := spew.ConfigState{DisableMethods: true, Indent: " "}
for name, test := range unmarshalLogTests {
var log *Log
err := json.Unmarshal([]byte(test.input), &log)
checkError(t, name, err, test.wantError)
+ if test.wantError == nil && err == nil {
+ if !reflect.DeepEqual(log, test.want) {
+ t.Errorf("test %q:\nGOT %sWANT %s", name, dumper.Sdump(log), dumper.Sdump(test.want))
+ }
+ }
}
}
diff --git a/core/vm/logger.go b/core/vm/logger.go
index 9e13d703b..6a605a59c 100644
--- a/core/vm/logger.go
+++ b/core/vm/logger.go
@@ -65,7 +65,7 @@ type StructLog struct {
// Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call.
type Tracer interface {
- CaptureState(env Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
+ CaptureState(env *Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
}
// StructLogger is an EVM state logger and implements Tracer.
@@ -94,7 +94,7 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
// captureState logs a new structured log message and pushes it out to the environment
//
// captureState also tracks SSTORE ops to track dirty values.
-func (l *StructLogger) CaptureState(env Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
+func (l *StructLogger) CaptureState(env *Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
// check if already accumulated the specified number of logs
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
return TraceLimitReachedError
@@ -144,7 +144,7 @@ func (l *StructLogger) CaptureState(env Environment, pc uint64, op OpCode, gas,
storage = make(Storage)
// Get the contract account and loop over each storage entry. This may involve looping over
// the trie and is a very expensive process.
- env.Db().GetAccount(contract.Address()).ForEachStorage(func(key, value common.Hash) bool {
+ env.StateDB.GetAccount(contract.Address()).ForEachStorage(func(key, value common.Hash) bool {
storage[key] = value
// Return true, indicating we'd like to continue.
return true
@@ -155,7 +155,7 @@ func (l *StructLogger) CaptureState(env Environment, pc uint64, op OpCode, gas,
}
}
// create a new snaptshot of the EVM.
- log := StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, env.Depth(), err}
+ log := StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, env.Depth, err}
l.logs = append(l.logs, log)
return nil
diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go
index d4d164eb6..05ad32fd8 100644
--- a/core/vm/logger_test.go
+++ b/core/vm/logger_test.go
@@ -21,16 +21,17 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params"
)
type dummyContractRef struct {
calledForEach bool
}
-func (dummyContractRef) ReturnGas(*big.Int, *big.Int) {}
-func (dummyContractRef) Address() common.Address { return common.Address{} }
-func (dummyContractRef) Value() *big.Int { return new(big.Int) }
-func (dummyContractRef) SetCode(common.Hash, []byte) {}
+func (dummyContractRef) ReturnGas(*big.Int) {}
+func (dummyContractRef) Address() common.Address { return common.Address{} }
+func (dummyContractRef) Value() *big.Int { return new(big.Int) }
+func (dummyContractRef) SetCode(common.Hash, []byte) {}
func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) {
d.calledForEach = true
}
@@ -40,28 +41,22 @@ func (d *dummyContractRef) SetBalance(*big.Int) {}
func (d *dummyContractRef) SetNonce(uint64) {}
func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
-type dummyEnv struct {
- *Env
+type dummyStateDB struct {
+ NoopStateDB
ref *dummyContractRef
}
-func newDummyEnv(ref *dummyContractRef) *dummyEnv {
- return &dummyEnv{
- Env: NewEnv(&Config{EnableJit: false, ForceJit: false}),
- ref: ref,
- }
-}
-func (d dummyEnv) GetAccount(common.Address) Account {
+func (d dummyStateDB) GetAccount(common.Address) Account {
return d.ref
}
func TestStoreCapture(t *testing.T) {
var (
- env = NewEnv(&Config{EnableJit: false, ForceJit: false})
+ env = NewEnvironment(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
logger = NewStructLogger(nil)
mem = NewMemory()
stack = newstack()
- contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), new(big.Int), new(big.Int))
+ contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), new(big.Int))
)
stack.push(big.NewInt(1))
stack.push(big.NewInt(0))
@@ -83,8 +78,8 @@ func TestStorageCapture(t *testing.T) {
t.Skip("implementing this function is difficult. it requires all sort of interfaces to be implemented which isn't trivial. The value (the actual test) isn't worth it")
var (
ref = &dummyContractRef{}
- contract = NewContract(ref, ref, new(big.Int), new(big.Int), new(big.Int))
- env = newDummyEnv(ref)
+ contract = NewContract(ref, ref, new(big.Int), new(big.Int))
+ env = NewEnvironment(Context{}, dummyStateDB{ref: ref}, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
logger = NewStructLogger(nil)
mem = NewMemory()
stack = newstack()
diff --git a/core/vm/noop.go b/core/vm/noop.go
new file mode 100644
index 000000000..ca7d1055a
--- /dev/null
+++ b/core/vm/noop.go
@@ -0,0 +1,68 @@
+// 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 vm
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+func NoopCanTransfer(db StateDB, from common.Address, balance *big.Int) bool {
+ return true
+}
+func NoopTransfer(db StateDB, from, to common.Address, amount *big.Int) {}
+
+type NoopEVMCallContext struct{}
+
+func (NoopEVMCallContext) Call(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
+ return nil, nil
+}
+func (NoopEVMCallContext) CallCode(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
+ return nil, nil
+}
+func (NoopEVMCallContext) Create(caller ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) {
+ return nil, common.Address{}, nil
+}
+func (NoopEVMCallContext) DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) {
+ return nil, nil
+}
+
+type NoopStateDB struct{}
+
+func (NoopStateDB) GetAccount(common.Address) Account { return nil }
+func (NoopStateDB) CreateAccount(common.Address) Account { return nil }
+func (NoopStateDB) SubBalance(common.Address, *big.Int) {}
+func (NoopStateDB) AddBalance(common.Address, *big.Int) {}
+func (NoopStateDB) GetBalance(common.Address) *big.Int { return nil }
+func (NoopStateDB) GetNonce(common.Address) uint64 { return 0 }
+func (NoopStateDB) SetNonce(common.Address, uint64) {}
+func (NoopStateDB) GetCodeHash(common.Address) common.Hash { return common.Hash{} }
+func (NoopStateDB) GetCode(common.Address) []byte { return nil }
+func (NoopStateDB) SetCode(common.Address, []byte) {}
+func (NoopStateDB) GetCodeSize(common.Address) int { return 0 }
+func (NoopStateDB) AddRefund(*big.Int) {}
+func (NoopStateDB) GetRefund() *big.Int { return nil }
+func (NoopStateDB) GetState(common.Address, common.Hash) common.Hash { return common.Hash{} }
+func (NoopStateDB) SetState(common.Address, common.Hash, common.Hash) {}
+func (NoopStateDB) Suicide(common.Address) bool { return false }
+func (NoopStateDB) HasSuicided(common.Address) bool { return false }
+func (NoopStateDB) Exist(common.Address) bool { return false }
+func (NoopStateDB) Empty(common.Address) bool { return false }
+func (NoopStateDB) RevertToSnapshot(int) {}
+func (NoopStateDB) Snapshot() int { return 0 }
+func (NoopStateDB) AddLog(*Log) {}
diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go
index f1a2b60d3..3cf0dd024 100644
--- a/core/vm/runtime/env.go
+++ b/core/vm/runtime/env.go
@@ -23,92 +23,22 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/params"
)
-// Env is a basic runtime environment required for running the EVM.
-type Env struct {
- chainConfig *params.ChainConfig
- depth int
- state *state.StateDB
-
- origin common.Address
- coinbase common.Address
-
- number *big.Int
- time *big.Int
- difficulty *big.Int
- gasLimit *big.Int
-
- getHashFn func(uint64) common.Hash
-
- evm *vm.EVM
-}
-
-// NewEnv returns a new vm.Environment
-func NewEnv(cfg *Config, state *state.StateDB) vm.Environment {
- env := &Env{
- chainConfig: cfg.ChainConfig,
- state: state,
- origin: cfg.Origin,
- coinbase: cfg.Coinbase,
- number: cfg.BlockNumber,
- time: cfg.Time,
- difficulty: cfg.Difficulty,
- gasLimit: cfg.GasLimit,
+func NewEnv(cfg *Config, state *state.StateDB) *vm.Environment {
+ context := vm.Context{
+ CanTransfer: core.CanTransfer,
+ Transfer: core.Transfer,
+ GetHash: func(uint64) common.Hash { return common.Hash{} },
+
+ Origin: cfg.Origin,
+ Coinbase: cfg.Coinbase,
+ BlockNumber: cfg.BlockNumber,
+ Time: cfg.Time,
+ Difficulty: cfg.Difficulty,
+ GasLimit: cfg.GasLimit,
+ GasPrice: new(big.Int),
}
- env.evm = vm.New(env, vm.Config{
- Debug: cfg.Debug,
- EnableJit: !cfg.DisableJit,
- ForceJit: !cfg.DisableJit,
- })
-
- return env
-}
-
-func (self *Env) ChainConfig() *params.ChainConfig { return self.chainConfig }
-func (self *Env) Vm() vm.Vm { return self.evm }
-func (self *Env) Origin() common.Address { return self.origin }
-func (self *Env) BlockNumber() *big.Int { return self.number }
-func (self *Env) Coinbase() common.Address { return self.coinbase }
-func (self *Env) Time() *big.Int { return self.time }
-func (self *Env) Difficulty() *big.Int { return self.difficulty }
-func (self *Env) Db() vm.Database { return self.state }
-func (self *Env) GasLimit() *big.Int { return self.gasLimit }
-func (self *Env) VmType() vm.Type { return vm.StdVmTy }
-func (self *Env) GetHash(n uint64) common.Hash {
- return self.getHashFn(n)
-}
-func (self *Env) AddLog(log *vm.Log) {
- self.state.AddLog(log)
-}
-func (self *Env) Depth() int { return self.depth }
-func (self *Env) SetDepth(i int) { self.depth = i }
-func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool {
- return self.state.GetBalance(from).Cmp(balance) >= 0
-}
-func (self *Env) SnapshotDatabase() int {
- return self.state.Snapshot()
-}
-func (self *Env) RevertToSnapshot(snapshot int) {
- self.state.RevertToSnapshot(snapshot)
-}
-
-func (self *Env) Transfer(from, to vm.Account, amount *big.Int) {
- core.Transfer(from, to, amount)
-}
-
-func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return core.Call(self, caller, addr, data, gas, price, value)
-}
-func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return core.CallCode(self, caller, addr, data, gas, price, value)
-}
-
-func (self *Env) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
- return core.DelegateCall(self, me, addr, data, gas, price)
-}
-func (self *Env) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
- return core.Create(self, caller, data, gas, price, value)
+ return vm.NewEnvironment(context, cfg.State, cfg.ChainConfig, cfg.EVMConfig)
}
diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go
index d51b435f8..3e99ed689 100644
--- a/core/vm/runtime/runtime.go
+++ b/core/vm/runtime/runtime.go
@@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
@@ -49,6 +50,7 @@ type Config struct {
Value *big.Int
DisableJit bool // "disable" so it's enabled by default
Debug bool
+ EVMConfig vm.Config
State *state.StateDB
GetHashFn func(n uint64) common.Hash
@@ -123,13 +125,37 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
receiver.Address(),
input,
cfg.GasLimit,
- cfg.GasPrice,
cfg.Value,
)
return ret, cfg.State, err
}
+// Create executes the code using the EVM create method
+func Create(input []byte, cfg *Config) ([]byte, common.Address, error) {
+ if cfg == nil {
+ cfg = new(Config)
+ }
+ setDefaults(cfg)
+
+ if cfg.State == nil {
+ db, _ := ethdb.NewMemDatabase()
+ cfg.State, _ = state.New(common.Hash{}, db)
+ }
+ var (
+ vmenv = NewEnv(cfg, cfg.State)
+ sender = cfg.State.CreateAccount(cfg.Origin)
+ )
+
+ // Call the code with the given configuration.
+ return vmenv.Create(
+ sender,
+ input,
+ cfg.GasLimit,
+ cfg.Value,
+ )
+}
+
// Call executes the code given by the contract's address. It will return the
// EVM's return value or an error if it failed.
//
@@ -147,7 +173,6 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) {
address,
input,
cfg.GasLimit,
- cfg.GasPrice,
cfg.Value,
)
diff --git a/core/vm/segments.go b/core/vm/segments.go
index 648d8a04a..47f535ab5 100644
--- a/core/vm/segments.go
+++ b/core/vm/segments.go
@@ -24,7 +24,7 @@ type jumpSeg struct {
gas *big.Int
}
-func (j jumpSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
+func (j jumpSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
if !contract.UseGas(j.gas) {
return nil, OutOfGasError
}
@@ -42,7 +42,7 @@ type pushSeg struct {
gas *big.Int
}
-func (s pushSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
+func (s pushSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// Use the calculated gas. When insufficient gas is present, use all gas and return an
// Out Of Gas error
if !contract.UseGas(s.gas) {
diff --git a/core/vm/util_test.go b/core/vm/util_test.go
deleted file mode 100644
index 5783ee015..000000000
--- a/core/vm/util_test.go
+++ /dev/null
@@ -1,32 +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 vm
-
-import (
- "math/big"
-
- "github.com/ethereum/go-ethereum/params"
-)
-
-type ruleSet struct {
- hs *big.Int
-}
-
-func (r ruleSet) IsHomestead(n *big.Int) bool { return n.Cmp(r.hs) >= 0 }
-func (r ruleSet) GasTable(*big.Int) params.GasTable {
- return params.GasTableHomestead
-}
diff --git a/core/vm/vm.go b/core/vm/vm.go
index 56aca6912..3521839df 100644
--- a/core/vm/vm.go
+++ b/core/vm/vm.go
@@ -30,10 +30,17 @@ import (
// Config are the configuration options for the EVM
type Config struct {
- Debug bool
+ // Debug enabled debugging EVM options
+ Debug bool
+ // EnableJit enabled the JIT VM
EnableJit bool
- ForceJit bool
- Tracer Tracer
+ // ForceJit forces the JIT VM
+ ForceJit bool
+ // Tracer is the op code logger
+ Tracer Tracer
+ // NoRecursion disabled EVM call, callcode,
+ // delegate call and create.
+ NoRecursion bool
}
// EVM is used to run Ethereum based contracts and will utilise the
@@ -41,26 +48,26 @@ type Config struct {
// The EVM will run the byte code VM or JIT VM based on the passed
// configuration.
type EVM struct {
- env Environment
+ env *Environment
jumpTable vmJumpTable
cfg Config
gasTable params.GasTable
}
// New returns a new instance of the EVM.
-func New(env Environment, cfg Config) *EVM {
+func New(env *Environment, cfg Config) *EVM {
return &EVM{
env: env,
- jumpTable: newJumpTable(env.ChainConfig(), env.BlockNumber()),
+ jumpTable: newJumpTable(env.ChainConfig(), env.BlockNumber),
cfg: cfg,
- gasTable: env.ChainConfig().GasTable(env.BlockNumber()),
+ gasTable: env.ChainConfig().GasTable(env.BlockNumber),
}
}
// Run loops and evaluates the contract's code with the given input data
func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
- evm.env.SetDepth(evm.env.Depth() + 1)
- defer evm.env.SetDepth(evm.env.Depth() - 1)
+ evm.env.Depth++
+ defer func() { evm.env.Depth-- }()
if contract.CodeAddr != nil {
if p := Precompiled[contract.CodeAddr.Str()]; p != nil {
@@ -117,10 +124,9 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
code = contract.Code
instrCount = 0
- op OpCode // current opcode
- mem = NewMemory() // bound memory
- stack = newstack() // local stack
- statedb = evm.env.Db() // current state
+ op OpCode // current opcode
+ mem = NewMemory() // bound memory
+ stack = newstack() // local stack
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC to be uint256. Practically much less so feasible.
pc = uint64(0) // program counter
@@ -146,7 +152,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
// User defer pattern to check for an error and, based on the error being nil or not, use all gas and return.
defer func() {
if err != nil && evm.cfg.Debug {
- evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth(), err)
+ evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth, err)
}
}()
@@ -174,7 +180,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
op = contract.GetOp(pc)
//fmt.Printf("OP %d %v\n", op, op)
// calculate the new memory size and gas price for the current executing opcode
- newMemSize, cost, err = calculateGasAndSize(evm.gasTable, evm.env, contract, caller, op, statedb, mem, stack)
+ newMemSize, cost, err = calculateGasAndSize(evm.gasTable, evm.env, contract, caller, op, mem, stack)
if err != nil {
return nil, err
}
@@ -189,7 +195,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
mem.Resize(newMemSize.Uint64())
// Add a log message
if evm.cfg.Debug {
- err = evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth(), nil)
+ err = evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth, nil)
if err != nil {
return nil, err
}
@@ -242,7 +248,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
// calculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for
// the operation. This does not reduce gas or resizes the memory.
-func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Contract, caller ContractRef, op OpCode, statedb Database, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) {
+func calculateGasAndSize(gasTable params.GasTable, env *Environment, contract *Contract, caller ContractRef, op OpCode, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) {
var (
gas = new(big.Int)
newMemSize *big.Int = new(big.Int)
@@ -260,21 +266,21 @@ func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Co
gas.Set(gasTable.Suicide)
var (
address = common.BigToAddress(stack.data[len(stack.data)-1])
- eip158 = env.ChainConfig().IsEIP158(env.BlockNumber())
+ eip158 = env.ChainConfig().IsEIP158(env.BlockNumber)
)
if eip158 {
// if empty and transfers value
- if env.Db().Empty(address) && statedb.GetBalance(contract.Address()).BitLen() > 0 {
+ if env.StateDB.Empty(address) && env.StateDB.GetBalance(contract.Address()).BitLen() > 0 {
gas.Add(gas, gasTable.CreateBySuicide)
}
- } else if !env.Db().Exist(address) {
+ } else if !env.StateDB.Exist(address) {
gas.Add(gas, gasTable.CreateBySuicide)
}
}
- if !statedb.HasSuicided(contract.Address()) {
- statedb.AddRefund(params.SuicideRefundGas)
+ if !env.StateDB.HasSuicided(contract.Address()) {
+ env.StateDB.AddRefund(params.SuicideRefundGas)
}
case EXTCODESIZE:
gas.Set(gasTable.ExtcodeSize)
@@ -323,7 +329,7 @@ func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Co
var g *big.Int
y, x := stack.data[stack.len()-2], stack.data[stack.len()-1]
- val := statedb.GetState(contract.Address(), common.BigToHash(x))
+ val := env.StateDB.GetState(contract.Address(), common.BigToHash(x))
// This checks for 3 scenario's and calculates gas accordingly
// 1. From a zero-value address to a non-zero value (NEW VALUE)
@@ -333,7 +339,7 @@ func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Co
// 0 => non 0
g = params.SstoreSetGas
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) {
- statedb.AddRefund(params.SstoreRefundGas)
+ env.StateDB.AddRefund(params.SstoreRefundGas)
g = params.SstoreClearGas
} else {
@@ -394,13 +400,13 @@ func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Co
if op == CALL {
var (
address = common.BigToAddress(stack.data[len(stack.data)-2])
- eip158 = env.ChainConfig().IsEIP158(env.BlockNumber())
+ eip158 = env.ChainConfig().IsEIP158(env.BlockNumber)
)
if eip158 {
- if env.Db().Empty(address) && transfersValue {
+ if env.StateDB.Empty(address) && transfersValue {
gas.Add(gas, params.CallNewAccountGas)
}
- } else if !env.Db().Exist(address) {
+ } else if !env.StateDB.Exist(address) {
gas.Add(gas, params.CallNewAccountGas)
}
}
diff --git a/core/vm/vm_jit_fake.go b/core/vm/vm_jit_fake.go
index 4fa98ccd9..44b60abf6 100644
--- a/core/vm/vm_jit_fake.go
+++ b/core/vm/vm_jit_fake.go
@@ -17,10 +17,3 @@
// +build !evmjit
package vm
-
-import "fmt"
-
-func NewJitVm(env Environment) VirtualMachine {
- fmt.Printf("Warning! EVM JIT not enabled.\n")
- return New(env, Config{})
-}
diff --git a/core/vm_env.go b/core/vm_env.go
index 43637bd13..58e71e305 100644
--- a/core/vm_env.go
+++ b/core/vm_env.go
@@ -15,104 +15,3 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core
-
-import (
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/params"
-)
-
-// GetHashFn returns a function for which the VM env can query block hashes through
-// up to the limit defined by the Yellow Paper and uses the given block chain
-// to query for information.
-func GetHashFn(ref common.Hash, chain *BlockChain) func(n uint64) common.Hash {
- return func(n uint64) common.Hash {
- for block := chain.GetBlockByHash(ref); block != nil; block = chain.GetBlock(block.ParentHash(), block.NumberU64()-1) {
- if block.NumberU64() == n {
- return block.Hash()
- }
- }
-
- return common.Hash{}
- }
-}
-
-type VMEnv struct {
- chainConfig *params.ChainConfig // Chain configuration
- state *state.StateDB // State to use for executing
- evm *vm.EVM // The Ethereum Virtual Machine
- depth int // Current execution depth
- msg Message // Message appliod
-
- header *types.Header // Header information
- chain *BlockChain // Blockchain handle
- getHashFn func(uint64) common.Hash // getHashFn callback is used to retrieve block hashes
-}
-
-func NewEnv(state *state.StateDB, chainConfig *params.ChainConfig, chain *BlockChain, msg Message, header *types.Header, cfg vm.Config) *VMEnv {
- env := &VMEnv{
- chainConfig: chainConfig,
- chain: chain,
- state: state,
- header: header,
- msg: msg,
- getHashFn: GetHashFn(header.ParentHash, chain),
- }
-
- env.evm = vm.New(env, cfg)
- return env
-}
-
-func (self *VMEnv) ChainConfig() *params.ChainConfig { return self.chainConfig }
-func (self *VMEnv) Vm() vm.Vm { return self.evm }
-func (self *VMEnv) Origin() common.Address { return self.msg.From() }
-func (self *VMEnv) BlockNumber() *big.Int { return self.header.Number }
-func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase }
-func (self *VMEnv) Time() *big.Int { return self.header.Time }
-func (self *VMEnv) Difficulty() *big.Int { return self.header.Difficulty }
-func (self *VMEnv) GasLimit() *big.Int { return self.header.GasLimit }
-func (self *VMEnv) Value() *big.Int { return self.msg.Value() }
-func (self *VMEnv) Db() vm.Database { return self.state }
-func (self *VMEnv) Depth() int { return self.depth }
-func (self *VMEnv) SetDepth(i int) { self.depth = i }
-func (self *VMEnv) GetHash(n uint64) common.Hash {
- return self.getHashFn(n)
-}
-
-func (self *VMEnv) AddLog(log *vm.Log) {
- self.state.AddLog(log)
-}
-func (self *VMEnv) CanTransfer(from common.Address, balance *big.Int) bool {
- return self.state.GetBalance(from).Cmp(balance) >= 0
-}
-
-func (self *VMEnv) SnapshotDatabase() int {
- return self.state.Snapshot()
-}
-
-func (self *VMEnv) RevertToSnapshot(snapshot int) {
- self.state.RevertToSnapshot(snapshot)
-}
-
-func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) {
- Transfer(from, to, amount)
-}
-
-func (self *VMEnv) Call(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return Call(self, me, addr, data, gas, price, value)
-}
-func (self *VMEnv) CallCode(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return CallCode(self, me, addr, data, gas, price, value)
-}
-
-func (self *VMEnv) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
- return DelegateCall(self, me, addr, data, gas, price)
-}
-
-func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
- return Create(self, me, data, gas, price, value)
-}
diff --git a/eth/api.go b/eth/api.go
index b3185c392..0a1d097e3 100644
--- a/eth/api.go
+++ b/eth/api.go
@@ -18,6 +18,7 @@ package eth
import (
"bytes"
+ "compress/gzip"
"errors"
"fmt"
"io"
@@ -25,6 +26,7 @@ import (
"math/big"
"os"
"runtime"
+ "strings"
"time"
"github.com/ethereum/ethash"
@@ -217,8 +219,14 @@ func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) {
}
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
- if err := api.eth.BlockChain().Export(out); err != nil {
+ if err := api.eth.BlockChain().Export(writer); err != nil {
return false, err
}
return true, nil
@@ -243,8 +251,15 @@ func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) {
}
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
- stream := rlp.NewStream(in, 0)
+ stream := rlp.NewStream(reader, 0)
blocks, index := make([]*types.Block, 0, 2500), 0
for batch := 0; ; batch++ {
@@ -515,9 +530,11 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common.
if err != nil {
return nil, fmt.Errorf("sender retrieval failed: %v", err)
}
+ context := core.NewEVMContext(msg, block.Header(), api.eth.BlockChain())
+
// Mutate the state if we haven't reached the tracing transaction yet
if uint64(idx) < txIndex {
- vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{})
+ vmenv := vm.NewEnvironment(context, stateDb, api.config, vm.Config{})
_, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
return nil, fmt.Errorf("mutation failed: %v", err)
@@ -525,8 +542,8 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common.
stateDb.DeleteSuicides()
continue
}
- // Otherwise trace the transaction and return
- vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{Debug: true, Tracer: tracer})
+
+ vmenv := vm.NewEnvironment(context, stateDb, api.config, vm.Config{Debug: true, Tracer: tracer})
ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
return nil, fmt.Errorf("tracing failed: %v", err)
diff --git a/eth/api_backend.go b/eth/api_backend.go
index 7858dee2e..f33b6f7e1 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -106,12 +106,14 @@ func (b *EthApiBackend) GetTd(blockHash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash)
}
-func (b *EthApiBackend) GetVMEnv(ctx context.Context, msg core.Message, state ethapi.State, header *types.Header) (vm.Environment, func() error, error) {
+func (b *EthApiBackend) GetVMEnv(ctx context.Context, msg core.Message, state ethapi.State, header *types.Header) (*vm.Environment, func() error, error) {
statedb := state.(EthApiState).state
from := statedb.GetOrNewStateObject(msg.From())
from.SetBalance(common.MaxBig)
vmError := func() error { return nil }
- return core.NewEnv(statedb, b.eth.chainConfig, b.eth.blockchain, msg, header, vm.Config{}), vmError, nil
+
+ context := core.NewEVMContext(msg, header, b.eth.BlockChain())
+ return vm.NewEnvironment(context, statedb, b.eth.chainConfig, vm.Config{}), vmError, nil
}
func (b *EthApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
@@ -129,15 +131,20 @@ func (b *EthApiBackend) RemoveTx(txHash common.Hash) {
b.eth.txPool.Remove(txHash)
}
-func (b *EthApiBackend) GetPoolTransactions() types.Transactions {
+func (b *EthApiBackend) GetPoolTransactions() (types.Transactions, error) {
b.eth.txMu.Lock()
defer b.eth.txMu.Unlock()
+ pending, err := b.eth.txPool.Pending()
+ if err != nil {
+ return nil, err
+ }
+
var txs types.Transactions
- for _, batch := range b.eth.txPool.Pending() {
+ for _, batch := range pending {
txs = append(txs, batch...)
}
- return txs
+ return txs, nil
}
func (b *EthApiBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {
diff --git a/eth/backend.go b/eth/backend.go
index d5b767b12..f98c9b724 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -104,6 +104,7 @@ type Config struct {
type LesServer interface {
Start(srvr *p2p.Server)
+ Synced()
Stop()
Protocols() []p2p.Protocol
}
@@ -145,6 +146,7 @@ type Ethereum struct {
func (s *Ethereum) AddLesServer(ls LesServer) {
s.lesServer = ls
+ s.protocolManager.lesServer = ls
}
// New creates a new Ethereum object (including the
diff --git a/eth/filters/api.go b/eth/filters/api.go
index d5dd57743..bbb34d3de 100644
--- a/eth/filters/api.go
+++ b/eth/filters/api.go
@@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
@@ -45,7 +46,7 @@ type filter struct {
deadline *time.Timer // filter is inactiv when deadline triggers
hashes []common.Hash
crit FilterCriteria
- logs []Log
+ logs []*vm.Log
s *Subscription // associated subscription in event system
}
@@ -241,7 +242,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc
var (
rpcSub = notifier.CreateSubscription()
- matchedLogs = make(chan []Log)
+ matchedLogs = make(chan []*vm.Log)
)
logsSub, err := api.events.SubscribeLogs(crit, matchedLogs)
@@ -292,14 +293,14 @@ type FilterCriteria struct {
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter
func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
- logs := make(chan []Log)
+ logs := make(chan []*vm.Log)
logsSub, err := api.events.SubscribeLogs(crit, logs)
if err != nil {
return rpc.ID(""), err
}
api.filtersMu.Lock()
- api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]Log, 0), s: logsSub}
+ api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*vm.Log, 0), s: logsSub}
api.filtersMu.Unlock()
go func() {
@@ -326,7 +327,7 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
// GetLogs returns logs matching the given argument that are stored within the state.
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs
-func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]Log, error) {
+func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*vm.Log, error) {
if crit.FromBlock == nil {
crit.FromBlock = big.NewInt(rpc.LatestBlockNumber.Int64())
}
@@ -365,7 +366,7 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool {
// If the filter could not be found an empty array of logs is returned.
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs
-func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]Log, error) {
+func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*vm.Log, error) {
api.filtersMu.Lock()
f, found := api.filters[id]
api.filtersMu.Unlock()
@@ -388,7 +389,7 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]Log
filter.SetAddresses(f.crit.Addresses)
filter.SetTopics(f.crit.Topics)
- logs, err:= filter.Find(ctx)
+ logs, err := filter.Find(ctx)
if err != nil {
return nil, err
}
@@ -440,9 +441,9 @@ func returnHashes(hashes []common.Hash) []common.Hash {
// returnLogs is a helper that will return an empty log array in case the given logs array is nil,
// otherwise the given logs array is returned.
-func returnLogs(logs []Log) []Log {
+func returnLogs(logs []*vm.Log) []*vm.Log {
if logs == nil {
- return []Log{}
+ return []*vm.Log{}
}
return logs
}
diff --git a/eth/filters/filter.go b/eth/filters/filter.go
index ce7383fb3..a695d7eb7 100644
--- a/eth/filters/filter.go
+++ b/eth/filters/filter.go
@@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
@@ -38,7 +39,7 @@ type Backend interface {
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
}
-// Filter can be used to retrieve and filter logs
+// Filter can be used to retrieve and filter logs.
type Filter struct {
backend Backend
useMipMap bool
@@ -85,7 +86,7 @@ func (f *Filter) SetTopics(topics [][]common.Hash) {
}
// Run filters logs with the current parameters set
-func (f *Filter) Find(ctx context.Context) ([]Log, error) {
+func (f *Filter) Find(ctx context.Context) ([]*vm.Log, error) {
head, _ := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
if head == nil {
return nil, nil
@@ -110,7 +111,7 @@ func (f *Filter) Find(ctx context.Context) ([]Log, error) {
return f.mipFind(beginBlockNo, endBlockNo, 0), nil
}
-func (f *Filter) mipFind(start, end uint64, depth int) (logs []Log) {
+func (f *Filter) mipFind(start, end uint64, depth int) (logs []*vm.Log) {
level := core.MIPMapLevels[depth]
// normalise numerator so we can work in level specific batches and
// work with the proper range checks
@@ -141,7 +142,7 @@ func (f *Filter) mipFind(start, end uint64, depth int) (logs []Log) {
return logs
}
-func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []Log, err error) {
+func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []*vm.Log, err error) {
for i := start; i <= end; i++ {
header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(i))
if header == nil || err != nil {
@@ -156,13 +157,9 @@ func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []Log, er
if err != nil {
return nil, err
}
- var unfiltered []Log
+ var unfiltered []*vm.Log
for _, receipt := range receipts {
- rl := make([]Log, len(receipt.Logs))
- for i, l := range receipt.Logs {
- rl[i] = Log{l, false}
- }
- unfiltered = append(unfiltered, rl...)
+ unfiltered = append(unfiltered, ([]*vm.Log)(receipt.Logs)...)
}
logs = append(logs, filterLogs(unfiltered, nil, nil, f.addresses, f.topics)...)
}
@@ -181,15 +178,15 @@ func includes(addresses []common.Address, a common.Address) bool {
return false
}
-func filterLogs(logs []Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []Log {
- var ret []Log
- // Filter the logs for interesting stuff
+// filterLogs creates a slice of logs matching the given criteria.
+func filterLogs(logs []*vm.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*vm.Log {
+ var ret []*vm.Log
Logs:
for _, log := range logs {
- if fromBlock != nil && fromBlock.Int64() >= 0 && uint64(fromBlock.Int64()) > log.BlockNumber {
+ if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
continue
}
- if toBlock != nil && toBlock.Int64() >= 0 && uint64(toBlock.Int64()) < log.BlockNumber {
+ if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
continue
}
diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go
index b59718aea..1b360cfdb 100644
--- a/eth/filters/filter_system.go
+++ b/eth/filters/filter_system.go
@@ -19,7 +19,6 @@
package filters
import (
- "encoding/json"
"errors"
"fmt"
"sync"
@@ -60,42 +59,12 @@ var (
ErrInvalidSubscriptionID = errors.New("invalid id")
)
-// Log is a helper that can hold additional information about vm.Log
-// necessary for the RPC interface.
-type Log struct {
- *vm.Log
- Removed bool `json:"removed"`
-}
-
-// MarshalJSON returns *l as the JSON encoding of l.
-func (l *Log) MarshalJSON() ([]byte, error) {
- fields := map[string]interface{}{
- "address": l.Address,
- "data": fmt.Sprintf("0x%x", l.Data),
- "blockNumber": nil,
- "logIndex": fmt.Sprintf("%#x", l.Index),
- "blockHash": nil,
- "transactionHash": l.TxHash,
- "transactionIndex": fmt.Sprintf("%#x", l.TxIndex),
- "topics": l.Topics,
- "removed": l.Removed,
- }
-
- // mined logs
- if l.BlockHash != (common.Hash{}) {
- fields["blockNumber"] = fmt.Sprintf("%#x", l.BlockNumber)
- fields["blockHash"] = l.BlockHash
- }
-
- return json.Marshal(fields)
-}
-
type subscription struct {
id rpc.ID
typ Type
created time.Time
logsCrit FilterCriteria
- logs chan []Log
+ logs chan []*vm.Log
hashes chan common.Hash
headers chan *types.Header
installed chan struct{} // closed when the filter is installed
@@ -182,7 +151,7 @@ func (es *EventSystem) subscribe(sub *subscription) *Subscription {
// SubscribeLogs creates a subscription that will write all logs matching the
// given criteria to the given logs channel. Default value for the from and to
// block is "latest". If the fromBlock > toBlock an error is returned.
-func (es *EventSystem) SubscribeLogs(crit FilterCriteria, logs chan []Log) (*Subscription, error) {
+func (es *EventSystem) SubscribeLogs(crit FilterCriteria, logs chan []*vm.Log) (*Subscription, error) {
var from, to rpc.BlockNumber
if crit.FromBlock == nil {
from = rpc.LatestBlockNumber
@@ -220,7 +189,7 @@ func (es *EventSystem) SubscribeLogs(crit FilterCriteria, logs chan []Log) (*Sub
// subscribeMinedPendingLogs creates a subscription that returned mined and
// pending logs that match the given criteria.
-func (es *EventSystem) subscribeMinedPendingLogs(crit FilterCriteria, logs chan []Log) *Subscription {
+func (es *EventSystem) subscribeMinedPendingLogs(crit FilterCriteria, logs chan []*vm.Log) *Subscription {
sub := &subscription{
id: rpc.NewID(),
typ: MinedAndPendingLogsSubscription,
@@ -238,7 +207,7 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit FilterCriteria, logs chan
// subscribeLogs creates a subscription that will write all logs matching the
// given criteria to the given logs channel.
-func (es *EventSystem) subscribeLogs(crit FilterCriteria, logs chan []Log) *Subscription {
+func (es *EventSystem) subscribeLogs(crit FilterCriteria, logs chan []*vm.Log) *Subscription {
sub := &subscription{
id: rpc.NewID(),
typ: LogsSubscription,
@@ -256,7 +225,7 @@ func (es *EventSystem) subscribeLogs(crit FilterCriteria, logs chan []Log) *Subs
// subscribePendingLogs creates a subscription that writes transaction hashes for
// transactions that enter the transaction pool.
-func (es *EventSystem) subscribePendingLogs(crit FilterCriteria, logs chan []Log) *Subscription {
+func (es *EventSystem) subscribePendingLogs(crit FilterCriteria, logs chan []*vm.Log) *Subscription {
sub := &subscription{
id: rpc.NewID(),
typ: PendingLogsSubscription,
@@ -279,7 +248,7 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti
id: rpc.NewID(),
typ: BlocksSubscription,
created: time.Now(),
- logs: make(chan []Log),
+ logs: make(chan []*vm.Log),
hashes: make(chan common.Hash),
headers: headers,
installed: make(chan struct{}),
@@ -296,7 +265,7 @@ func (es *EventSystem) SubscribePendingTxEvents(hashes chan common.Hash) *Subscr
id: rpc.NewID(),
typ: PendingTransactionsSubscription,
created: time.Now(),
- logs: make(chan []Log),
+ logs: make(chan []*vm.Log),
hashes: hashes,
headers: make(chan *types.Header),
installed: make(chan struct{}),
@@ -319,7 +288,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev *event.Event) {
if len(e) > 0 {
for _, f := range filters[LogsSubscription] {
if ev.Time.After(f.created) {
- if matchedLogs := filterLogs(convertLogs(e, false), f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
+ if matchedLogs := filterLogs(e, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
f.logs <- matchedLogs
}
}
@@ -328,7 +297,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev *event.Event) {
case core.RemovedLogsEvent:
for _, f := range filters[LogsSubscription] {
if ev.Time.After(f.created) {
- if matchedLogs := filterLogs(convertLogs(e.Logs, true), f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
+ if matchedLogs := filterLogs(e.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
f.logs <- matchedLogs
}
}
@@ -336,7 +305,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev *event.Event) {
case core.PendingLogsEvent:
for _, f := range filters[PendingLogsSubscription] {
if ev.Time.After(f.created) {
- if matchedLogs := filterLogs(convertLogs(e.Logs, false), nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
+ if matchedLogs := filterLogs(e.Logs, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics); len(matchedLogs) > 0 {
f.logs <- matchedLogs
}
}
@@ -401,25 +370,22 @@ func (es *EventSystem) lightFilterNewHead(newHeader *types.Header, callBack func
}
// filter logs of a single header in light client mode
-func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.Address, topics [][]common.Hash, remove bool) []Log {
- //fmt.Println("lightFilterLogs", header.Number.Uint64(), remove)
+func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.Address, topics [][]common.Hash, remove bool) []*vm.Log {
if bloomFilter(header.Bloom, addresses, topics) {
- //fmt.Println("bloom match")
// Get the logs of the block
ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
receipts, err := es.backend.GetReceipts(ctx, header.Hash())
if err != nil {
return nil
}
- var unfiltered []Log
+ var unfiltered []*vm.Log
for _, receipt := range receipts {
- rl := make([]Log, len(receipt.Logs))
- for i, l := range receipt.Logs {
- rl[i] = Log{l, remove}
+ for _, log := range receipt.Logs {
+ logcopy := *log
+ logcopy.Removed = remove
+ unfiltered = append(unfiltered, &logcopy)
}
- unfiltered = append(unfiltered, rl...)
}
-
logs := filterLogs(unfiltered, nil, nil, addresses, topics)
return logs
}
@@ -465,13 +431,3 @@ func (es *EventSystem) eventLoop() {
}
}
}
-
-// convertLogs is a helper utility that converts vm.Logs to []filter.Log.
-func convertLogs(in vm.Logs, removed bool) []Log {
-
- logs := make([]Log, len(in))
- for i, l := range in {
- logs[i] = Log{l, removed}
- }
- return logs
-}
diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go
index e8591a2e4..3ce0cf663 100644
--- a/eth/filters/filter_system_test.go
+++ b/eth/filters/filter_system_test.go
@@ -74,10 +74,10 @@ func TestBlockSubscription(t *testing.T) {
t.Parallel()
var (
- mux = new(event.TypeMux)
- db, _ = ethdb.NewMemDatabase()
+ mux = new(event.TypeMux)
+ db, _ = ethdb.NewMemDatabase()
backend = &testBackend{mux, db}
- api = NewPublicFilterAPI(backend, false)
+ api = NewPublicFilterAPI(backend, false)
genesis = core.WriteGenesisBlockForTesting(db)
chain, _ = core.GenerateChain(params.TestChainConfig, genesis, db, 10, func(i int, gen *core.BlockGen) {})
@@ -128,10 +128,10 @@ func TestPendingTxFilter(t *testing.T) {
t.Parallel()
var (
- mux = new(event.TypeMux)
- db, _ = ethdb.NewMemDatabase()
+ mux = new(event.TypeMux)
+ db, _ = ethdb.NewMemDatabase()
backend = &testBackend{mux, db}
- api = NewPublicFilterAPI(backend, false)
+ api = NewPublicFilterAPI(backend, false)
transactions = []*types.Transaction{
types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), new(big.Int), new(big.Int), nil),
@@ -178,10 +178,10 @@ func TestPendingTxFilter(t *testing.T) {
// If not it must return an error.
func TestLogFilterCreation(t *testing.T) {
var (
- mux = new(event.TypeMux)
- db, _ = ethdb.NewMemDatabase()
+ mux = new(event.TypeMux)
+ db, _ = ethdb.NewMemDatabase()
backend = &testBackend{mux, db}
- api = NewPublicFilterAPI(backend, false)
+ api = NewPublicFilterAPI(backend, false)
testCases = []struct {
crit FilterCriteria
@@ -223,10 +223,10 @@ func TestInvalidLogFilterCreation(t *testing.T) {
t.Parallel()
var (
- mux = new(event.TypeMux)
- db, _ = ethdb.NewMemDatabase()
+ mux = new(event.TypeMux)
+ db, _ = ethdb.NewMemDatabase()
backend = &testBackend{mux, db}
- api = NewPublicFilterAPI(backend, false)
+ api = NewPublicFilterAPI(backend, false)
)
// different situations where log filter creation should fail.
@@ -249,10 +249,10 @@ func TestLogFilter(t *testing.T) {
t.Parallel()
var (
- mux = new(event.TypeMux)
- db, _ = ethdb.NewMemDatabase()
+ mux = new(event.TypeMux)
+ db, _ = ethdb.NewMemDatabase()
backend = &testBackend{mux, db}
- api = NewPublicFilterAPI(backend, false)
+ api = NewPublicFilterAPI(backend, false)
firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111")
secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222")
@@ -321,14 +321,14 @@ func TestLogFilter(t *testing.T) {
}
for i, tt := range testCases {
- var fetched []Log
+ var fetched []*vm.Log
for { // fetch all expected logs
results, err := api.GetFilterChanges(tt.id)
if err != nil {
t.Fatalf("Unable to fetch logs: %v", err)
}
- fetched = append(fetched, results.([]Log)...)
+ fetched = append(fetched, results.([]*vm.Log)...)
if len(fetched) >= len(tt.expected) {
break
}
@@ -345,7 +345,7 @@ func TestLogFilter(t *testing.T) {
if fetched[l].Removed {
t.Errorf("expected log not to be removed for log %d in case %d", l, i)
}
- if !reflect.DeepEqual(fetched[l].Log, tt.expected[l]) {
+ if !reflect.DeepEqual(fetched[l], tt.expected[l]) {
t.Errorf("invalid log on index %d for case %d", l, i)
}
}
@@ -357,10 +357,10 @@ func TestPendingLogsSubscription(t *testing.T) {
t.Parallel()
var (
- mux = new(event.TypeMux)
- db, _ = ethdb.NewMemDatabase()
+ mux = new(event.TypeMux)
+ db, _ = ethdb.NewMemDatabase()
backend = &testBackend{mux, db}
- api = NewPublicFilterAPI(backend, false)
+ api = NewPublicFilterAPI(backend, false)
firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111")
secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222")
@@ -397,7 +397,7 @@ func TestPendingLogsSubscription(t *testing.T) {
testCases = []struct {
crit FilterCriteria
expected vm.Logs
- c chan []Log
+ c chan []*vm.Log
sub *Subscription
}{
// match all
@@ -423,7 +423,7 @@ func TestPendingLogsSubscription(t *testing.T) {
// on slow machines this could otherwise lead to missing events when the subscription is created after
// (some) events are posted.
for i := range testCases {
- testCases[i].c = make(chan []Log)
+ testCases[i].c = make(chan []*vm.Log)
testCases[i].sub, _ = api.events.SubscribeLogs(testCases[i].crit, testCases[i].c)
}
@@ -431,7 +431,7 @@ func TestPendingLogsSubscription(t *testing.T) {
i := n
tt := test
go func() {
- var fetched []Log
+ var fetched []*vm.Log
fetchLoop:
for {
logs := <-tt.c
@@ -449,7 +449,7 @@ func TestPendingLogsSubscription(t *testing.T) {
if fetched[l].Removed {
t.Errorf("expected log not to be removed for log %d in case %d", l, i)
}
- if !reflect.DeepEqual(fetched[l].Log, tt.expected[l]) {
+ if !reflect.DeepEqual(fetched[l], tt.expected[l]) {
t.Errorf("invalid log on index %d for case %d", l, i)
}
}
diff --git a/eth/handler.go b/eth/handler.go
index 8f05cc5b1..771e69b8d 100644
--- a/eth/handler.go
+++ b/eth/handler.go
@@ -87,6 +87,8 @@ type ProtocolManager struct {
quitSync chan struct{}
noMorePeers chan struct{}
+ lesServer LesServer
+
// wait group is used for graceful shutdowns during downloading
// and processing
wg sync.WaitGroup
@@ -171,7 +173,7 @@ func NewProtocolManager(config *params.ChainConfig, fastSync bool, networkId int
return blockchain.CurrentBlock().NumberU64()
}
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)
}
manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, manager.removePeer)
diff --git a/eth/helper_test.go b/eth/helper_test.go
index f23976785..bd6b2d0da 100644
--- a/eth/helper_test.go
+++ b/eth/helper_test.go
@@ -93,7 +93,7 @@ type testTxPool struct {
// AddBatch appends a batch of transactions to the pool, and notifies any
// 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()
defer p.lock.Unlock()
@@ -101,10 +101,12 @@ func (p *testTxPool) AddBatch(txs []*types.Transaction) {
if p.added != nil {
p.added <- txs
}
+
+ return nil
}
// 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()
defer p.lock.RUnlock()
@@ -116,7 +118,7 @@ func (p *testTxPool) Pending() map[common.Address]types.Transactions {
for _, batch := range batches {
sort.Sort(types.TxByNonce(batch))
}
- return batches
+ return batches, nil
}
// newTestTransaction create a new dummy transaction.
diff --git a/eth/protocol.go b/eth/protocol.go
index 3f65c204b..7d22b33de 100644
--- a/eth/protocol.go
+++ b/eth/protocol.go
@@ -98,11 +98,11 @@ var errorToString = map[int]string{
type txPool interface {
// AddBatch should add the given transactions to the pool.
- AddBatch([]*types.Transaction)
+ AddBatch([]*types.Transaction) error
// Pending should return pending transactions.
// 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.
diff --git a/eth/sync.go b/eth/sync.go
index 6584bb1e2..234534b4f 100644
--- a/eth/sync.go
+++ b/eth/sync.go
@@ -46,7 +46,8 @@ type txsync struct {
// syncTransactions starts sending all currently pending transactions to the given peer.
func (pm *ProtocolManager) syncTransactions(p *peer) {
var txs types.Transactions
- for _, batch := range pm.txpool.Pending() {
+ pending, _ := pm.txpool.Pending()
+ for _, batch := range pending {
txs = append(txs, batch...)
}
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 {
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 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()
+ }
+}
diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go
index 00edd90e1..4daebda92 100644
--- a/ethclient/ethclient.go
+++ b/ethclient/ethclient.go
@@ -81,6 +81,8 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
err := ec.c.CallContext(ctx, &raw, method, args...)
if err != nil {
return nil, err
+ } else if len(raw) == 0 {
+ return nil, ethereum.NotFound
}
// Decode header and transactions.
var head *types.Header
@@ -112,7 +114,7 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
for i := range reqs {
reqs[i] = rpc.BatchElem{
Method: "eth_getUncleByBlockHashAndIndex",
- Args: []interface{}{body.Hash, fmt.Sprintf("%#x", i)},
+ Args: []interface{}{body.Hash, hexutil.EncodeUint64(uint64(i))},
Result: &uncles[i],
}
}
@@ -123,6 +125,9 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
if reqs[i].Error != nil {
return nil, reqs[i].Error
}
+ if uncles[i] == nil {
+ return nil, fmt.Errorf("got null header for uncle %d of block %x", i, body.Hash[:])
+ }
}
}
return types.NewBlockWithHeader(head).WithBody(body.Transactions, uncles), nil
@@ -132,6 +137,9 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
var head *types.Header
err := ec.c.CallContext(ctx, &head, "eth_getBlockByHash", hash, false)
+ if err == nil && head == nil {
+ err = ethereum.NotFound
+ }
return head, err
}
@@ -140,19 +148,31 @@ func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.He
func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
var head *types.Header
err := ec.c.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false)
+ if err == nil && head == nil {
+ err = ethereum.NotFound
+ }
return head, err
}
// TransactionByHash returns the transaction with the given hash.
-func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, error) {
- var tx *types.Transaction
- err := ec.c.CallContext(ctx, &tx, "eth_getTransactionByHash", hash)
- if err == nil {
- if _, r, _ := tx.RawSignatureValues(); r == nil {
- return nil, fmt.Errorf("server returned transaction without signature")
- }
+func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
+ var raw json.RawMessage
+ err = ec.c.CallContext(ctx, &raw, "eth_getTransactionByHash", hash)
+ if err != nil {
+ return nil, false, err
+ } else if len(raw) == 0 {
+ return nil, false, ethereum.NotFound
}
- return tx, err
+ if err := json.Unmarshal(raw, tx); err != nil {
+ return nil, false, err
+ } else if _, r, _ := tx.RawSignatureValues(); r == nil {
+ return nil, false, fmt.Errorf("server returned transaction without signature")
+ }
+ var block struct{ BlockHash *common.Hash }
+ if err := json.Unmarshal(raw, &block); err != nil {
+ return nil, false, err
+ }
+ return tx, block.BlockHash == nil, nil
}
// TransactionCount returns the total number of transactions in the given block.
@@ -167,11 +187,9 @@ func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash,
var tx *types.Transaction
err := ec.c.CallContext(ctx, &tx, "eth_getTransactionByBlockHashAndIndex", blockHash, index)
if err == nil {
- var signer types.Signer = types.HomesteadSigner{}
- if tx.Protected() {
- signer = types.NewEIP155Signer(tx.ChainId())
- }
- if _, r, _ := types.SignatureValues(signer, tx); r == nil {
+ if tx == nil {
+ return nil, ethereum.NotFound
+ } else if _, r, _ := tx.RawSignatureValues(); r == nil {
return nil, fmt.Errorf("server returned transaction without signature")
}
}
@@ -183,8 +201,12 @@ func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash,
func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
var r *types.Receipt
err := ec.c.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash)
- if err == nil && r != nil && len(r.PostState) == 0 {
- return nil, fmt.Errorf("server returned receipt without post state")
+ if err == nil {
+ if r == nil {
+ return nil, ethereum.NotFound
+ } else if len(r.PostState) == 0 {
+ return nil, fmt.Errorf("server returned receipt without post state")
+ }
}
return r, err
}
@@ -193,7 +215,7 @@ func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
}
- return fmt.Sprintf("%#x", number)
+ return hexutil.EncodeBig(number)
}
type rpcProgress struct {
diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go
index 102c0d3b2..178eb2be9 100644
--- a/ethclient/ethclient_test.go
+++ b/ethclient/ethclient_test.go
@@ -21,9 +21,9 @@ import "github.com/ethereum/go-ethereum"
// Verify that Client implements the ethereum interfaces.
var (
_ = ethereum.ChainReader(&Client{})
+ _ = ethereum.TransactionReader(&Client{})
_ = ethereum.ChainStateReader(&Client{})
_ = ethereum.ChainSyncReader(&Client{})
- _ = ethereum.ChainHeadEventer(&Client{})
_ = ethereum.ContractCaller(&Client{})
_ = ethereum.GasEstimator(&Client{})
_ = ethereum.GasPricer(&Client{})
diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go
index a5fa84468..716beef69 100644
--- a/ethstats/ethstats.go
+++ b/ethstats/ethstats.go
@@ -25,6 +25,7 @@ import (
"regexp"
"runtime"
"strconv"
+ "strings"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -41,6 +42,10 @@ import (
"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
// chain statistics up to a monitoring server.
type Service struct {
@@ -53,6 +58,9 @@ type Service struct {
node string // Name of the node to display on the monitoring page
pass string // Password to authorize access to the monitoring page
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.
@@ -65,11 +73,13 @@ func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Servic
}
// Assemble and return the stats service
return &Service{
- eth: ethServ,
- les: lesServ,
- node: parts[1],
- pass: parts[3],
- host: parts[4],
+ eth: ethServ,
+ les: lesServ,
+ node: parts[1],
+ pass: parts[3],
+ host: parts[4],
+ pongCh: make(chan struct{}),
+ histCh: make(chan []uint64, 1),
}, nil
}
@@ -115,7 +125,11 @@ func (s *Service) loop() {
// Loop reporting until termination
for {
// 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 {
glog.V(logger.Warn).Infof("Stats server unreachable: %v", err)
time.Sleep(10 * time.Second)
@@ -130,22 +144,34 @@ func (s *Service) loop() {
time.Sleep(10 * time.Second)
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)
conn.Close()
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
fullReport := time.NewTicker(15 * time.Second)
for err == nil {
select {
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)
}
- case head := <-headSub.Chan():
- if head == nil { // node stopped
+ case list := <-s.histCh:
+ 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()
return
}
@@ -155,8 +181,8 @@ func (s *Service) loop() {
if err = s.reportPending(out); err != nil {
glog.V(logger.Warn).Infof("Post-block transaction stats report failed: %v", err)
}
- case ev := <-txSub.Chan():
- if ev == nil { // node stopped
+ case _, ok := <-txSub.Chan():
+ if !ok { // node stopped
conn.Close()
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
// on the monitoring page.
type nodeInfo struct {
@@ -190,6 +286,7 @@ type nodeInfo struct {
Os string `json:"os"`
OsVer string `json:"os_v"`
Client string `json:"client"`
+ History bool `json:"canUpdateHistory"`
}
// 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,
OsVer: runtime.GOARCH,
Client: "0.1.1",
+ History: true,
},
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.
// This should only be used on reconnects or rarely to avoid overloading the
// server. Use the individual methods for reporting subscribed events.
-func (s *Service) report(in *json.Decoder, out *json.Encoder) error {
- if err := s.reportLatency(in, out); err != nil {
+func (s *Service) report(out *json.Encoder) error {
+ if err := s.reportLatency(out); err != nil {
return err
}
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
// 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
start := time.Now()
@@ -276,9 +374,12 @@ func (s *Service) reportLatency(in *json.Decoder, out *json.Encoder) error {
return err
}
// Wait for the pong request to arrive back
- var pong map[string][]interface{}
- if err := in.Decode(&pong); err != nil || len(pong["emit"]) != 2 || pong["emit"][0].(string) != "node-pong" {
- return errors.New("unexpected ping reply")
+ select {
+ case <-s.pongCh:
+ // 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
latency := map[string][]interface{}{
@@ -297,6 +398,7 @@ func (s *Service) reportLatency(in *json.Decoder, out *json.Encoder) error {
type blockStats struct {
Number *big.Int `json:"number"`
Hash common.Hash `json:"hash"`
+ Timestamp *big.Int `json:"timestamp"`
Miner common.Address `json:"miner"`
GasUsed *big.Int `json:"gasUsed"`
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.
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 (
- head *types.Header
+ header *types.Header
td *big.Int
txs []*types.Transaction
uncles []*types.Header
@@ -342,37 +461,77 @@ func (s *Service) reportBlock(out *json.Encoder, block *types.Block) error {
if block == nil {
block = s.eth.BlockChain().CurrentBlock()
}
- head = block.Header()
- td = s.eth.BlockChain().GetTd(head.Hash(), head.Number.Uint64())
+ header = block.Header()
+ td = s.eth.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
txs = block.Transactions()
uncles = block.Uncles()
} else {
// Light nodes would need on-demand lookups for transactions/uncles, skip
if block != nil {
- head = block.Header()
+ header = block.Header()
+ } else {
+ header = s.les.BlockChain().CurrentHeader()
+ }
+ td = s.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
+ }
+ // Assemble and return the block stats
+ return &blockStats{
+ Number: header.Number,
+ Hash: header.Hash(),
+ Timestamp: header.Time,
+ Miner: header.Coinbase,
+ GasUsed: new(big.Int).Set(header.GasUsed),
+ GasLimit: new(big.Int).Set(header.GasLimit),
+ Diff: header.Difficulty.String(),
+ TotalDiff: td.String(),
+ Txs: txs,
+ 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()
}
- td = s.les.BlockChain().GetTd(head.Hash(), head.Number.Uint64())
+ start := head.Number.Int64() - historyUpdateRange
+ if start < 0 {
+ start = 0
+ }
+ for i := uint64(start); i <= head.Number.Uint64(); i++ {
+ indexes = append(indexes, i)
+ }
}
- // Assemble the block stats report and send it to the server
+ // 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,
- "block": &blockStats{
- Number: head.Number,
- Hash: head.Hash(),
- Miner: head.Coinbase,
- GasUsed: new(big.Int).Set(head.GasUsed),
- GasLimit: new(big.Int).Set(head.GasLimit),
- Diff: head.Difficulty.String(),
- TotalDiff: td.String(),
- Txs: txs,
- Uncles: uncles,
- },
+ "id": s.node,
+ "history": history,
}
report := map[string][]interface{}{
- "emit": []interface{}{"block", stats},
+ "emit": []interface{}{"history", stats},
}
if err := out.Encode(report); err != nil {
return err
diff --git a/interfaces.go b/interfaces.go
index aab0e2029..bbb204ff2 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -18,6 +18,7 @@
package ethereum
import (
+ "errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
@@ -26,6 +27,9 @@ import (
"golang.org/x/net/context"
)
+// NotFound is returned by API methods if the requested item does not exist.
+var NotFound = errors.New("not found")
+
// TODO: move subscription to package event
// Subscription represents an event subscription where events are
@@ -46,6 +50,8 @@ type Subscription interface {
// blockchain fork that was previously downloaded and processed by the node. The block
// number argument can be nil to select the latest canonical block. Reading block headers
// should be preferred over full blocks whenever possible.
+//
+// The returned error is NotFound if the requested item does not exist.
type ChainReader interface {
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
@@ -53,7 +59,30 @@ type ChainReader interface {
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error)
TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error)
- TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, error)
+
+ // This method subscribes to notifications about changes of the head block of
+ // the canonical chain.
+ SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (Subscription, error)
+}
+
+// TransactionReader provides access to past transactions and their receipts.
+// Implementations may impose arbitrary restrictions on the transactions and receipts that
+// can be retrieved. Historic transactions may not be available.
+//
+// Avoid relying on this interface if possible. Contract logs (through the LogFilterer
+// interface) are more reliable and usually safer in the presence of chain
+// reorganisations.
+//
+// The returned error is NotFound if the requested item does not exist.
+type TransactionReader interface {
+ // TransactionByHash checks the pool of pending transactions in addition to the
+ // blockchain. The isPending return value indicates whether the transaction has been
+ // mined yet. Note that the transaction may not be part of the canonical chain even if
+ // it's not pending.
+ TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error)
+ // TransactionReceipt returns the receipt of a mined transaction. Note that the
+ // transaction may not be included in the current canonical chain even if a receipt
+ // exists.
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
}
@@ -83,11 +112,6 @@ type ChainSyncReader interface {
SyncProgress(ctx context.Context) (*SyncProgress, error)
}
-// A ChainHeadEventer returns notifications whenever the canonical head block is updated.
-type ChainHeadEventer interface {
- SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (Subscription, error)
-}
-
// CallMsg contains parameters for contract calls.
type CallMsg struct {
From common.Address // the sender of the 'transaction'
@@ -128,6 +152,9 @@ type FilterQuery struct {
// LogFilterer provides access to contract log events using a one-off query or continuous
// event subscription.
+//
+// Logs received through a streaming query subscription may have Removed set to true,
+// indicating that the log was reverted due to a chain reorganisation.
type LogFilterer interface {
FilterLogs(ctx context.Context, q FilterQuery) ([]vm.Log, error)
SubscribeFilterLogs(ctx context.Context, q FilterQuery, ch chan<- vm.Log) (Subscription, error)
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index a25eff5ed..607df6b0c 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -28,6 +28,7 @@ import (
"github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
"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/types"
"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
-// safely used to calculate a signature from. The hash is calulcated with:
-// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
-func signHash(message string) []byte {
- data := common.FromHex(message)
- // Give context to the signed message. This prevents an adversery to sign a tx.
- // It has no cryptographic purpose.
+// safely used to calculate a signature from.
+//
+// The hash is calulcated as
+// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
+//
+// This gives context to the signed message and prevents signing of transactions.
+func signHash(data []byte) []byte {
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))
}
@@ -306,13 +307,8 @@ func signHash(message string) []byte {
// The key used to calculate the signature is decrypted with the given password.
//
// 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) {
- hash := signHash(message)
- signature, err := s.b.AccountManager().SignWithPassphrase(addr, passwd, hash)
- if err != nil {
- return "0x", err
- }
- return common.ToHex(signature), nil
+func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
+ return s.b.AccountManager().SignWithPassphrase(addr, passwd, signHash(data))
}
// 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)
//
// 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) {
- var (
- hash = signHash(message)
- sig = common.FromHex(signature)
- )
-
+func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
if len(sig) != 65 {
return common.Address{}, fmt.Errorf("signature must be 65 bytes long")
}
-
// see crypto.Ecrecover description
if sig[64] == 27 || sig[64] == 28 {
sig[64] -= 27
}
-
- rpk, err := crypto.Ecrecover(hash, sig)
+ rpk, err := crypto.Ecrecover(signHash(data), sig)
if err != nil {
return common.Address{}, err
}
-
pubKey := crypto.ToECDSAPub(rpk)
recoveredAddr := crypto.PubkeyToAddress(*pubKey)
-
return recoveredAddr, nil
}
@@ -1116,10 +1103,8 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
// The account associated with addr must be unlocked.
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
-func (s *PublicTransactionPoolAPI) Sign(addr common.Address, message string) (string, error) {
- hash := signHash(message)
- signature, err := s.b.AccountManager().SignEthereum(addr, hash)
- return common.ToHex(signature), err
+func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
+ return s.b.AccountManager().SignEthereum(addr, signHash(data))
}
// 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
// the accounts this node manages.
-func (s *PublicTransactionPoolAPI) PendingTransactions() []*RPCTransaction {
- pending := s.b.GetPoolTransactions()
+func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) {
+ pending, err := s.b.GetPoolTransactions()
+ if err != nil {
+ return nil, err
+ }
+
transactions := make([]*RPCTransaction, 0, len(pending))
for _, tx := range pending {
var signer types.Signer = types.HomesteadSigner{}
@@ -1286,13 +1275,17 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() []*RPCTransaction {
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
// 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) {
- pending := s.b.GetPoolTransactions()
+ pending, err := s.b.GetPoolTransactions()
+ if err != nil {
+ return common.Hash{}, err
+ }
+
for _, p := range pending {
var signer types.Signer = types.HomesteadSigner{}
if p.Protected() {
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index fdc4a39dc..36d7e754b 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -51,11 +51,11 @@ type Backend interface {
GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error)
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
GetTd(blockHash common.Hash) *big.Int
- GetVMEnv(ctx context.Context, msg core.Message, state State, header *types.Header) (vm.Environment, func() error, error)
+ GetVMEnv(ctx context.Context, msg core.Message, state State, header *types.Header) (*vm.Environment, func() error, error)
// TxPool API
SendTx(ctx context.Context, signedTx *types.Transaction) error
RemoveTx(txHash common.Hash)
- GetPoolTransactions() types.Transactions
+ GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)
Stats() (pending int, queued int)
diff --git a/internal/ethapi/tracer.go b/internal/ethapi/tracer.go
index 5f69826a3..6d632376b 100644
--- a/internal/ethapi/tracer.go
+++ b/internal/ethapi/tracer.go
@@ -124,7 +124,7 @@ func (sw *stackWrapper) toValue(vm *otto.Otto) otto.Value {
// dbWrapper provides a JS wrapper around vm.Database
type dbWrapper struct {
- db vm.Database
+ db vm.StateDB
}
// getBalance retrieves an account's balance
@@ -278,11 +278,11 @@ func wrapError(context string, err error) error {
}
// CaptureState implements the Tracer interface to trace a single step of VM execution
-func (jst *JavascriptTracer) CaptureState(env vm.Environment, pc uint64, op vm.OpCode, gas, cost *big.Int, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
+func (jst *JavascriptTracer) CaptureState(env *vm.Environment, pc uint64, op vm.OpCode, gas, cost *big.Int, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
if jst.err == nil {
jst.memory.memory = memory
jst.stack.stack = stack
- jst.db.db = env.Db()
+ jst.db.db = env.StateDB
ocw := &opCodeWrapper{op}
diff --git a/internal/ethapi/tracer_test.go b/internal/ethapi/tracer_test.go
index b88178a6d..29814d783 100644
--- a/internal/ethapi/tracer_test.go
+++ b/internal/ethapi/tracer_test.go
@@ -25,62 +25,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
-type Env struct {
- gasLimit *big.Int
- depth int
- evm *vm.EVM
-}
-
-func NewEnv(config *vm.Config) *Env {
- env := &Env{gasLimit: big.NewInt(10000), depth: 0}
- env.evm = vm.New(env, *config)
- return env
-}
-
-func (self *Env) ChainConfig() *params.ChainConfig {
- return params.TestChainConfig
-}
-func (self *Env) Vm() vm.Vm { return self.evm }
-func (self *Env) Origin() common.Address { return common.Address{} }
-func (self *Env) BlockNumber() *big.Int { return big.NewInt(0) }
-
-//func (self *Env) PrevHash() []byte { return self.parent }
-func (self *Env) Coinbase() common.Address { return common.Address{} }
-func (self *Env) SnapshotDatabase() int { return 0 }
-func (self *Env) RevertToSnapshot(int) {}
-func (self *Env) Time() *big.Int { return big.NewInt(time.Now().Unix()) }
-func (self *Env) Difficulty() *big.Int { return big.NewInt(0) }
-func (self *Env) Db() vm.Database { return nil }
-func (self *Env) GasLimit() *big.Int { return self.gasLimit }
-func (self *Env) VmType() vm.Type { return vm.StdVmTy }
-func (self *Env) GetHash(n uint64) common.Hash {
- return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String())))
-}
-func (self *Env) AddLog(log *vm.Log) {
-}
-func (self *Env) Depth() int { return self.depth }
-func (self *Env) SetDepth(i int) { self.depth = i }
-func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool {
- return true
-}
-func (self *Env) Transfer(from, to vm.Account, amount *big.Int) {}
-func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return nil, nil
-}
-func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return nil, nil
-}
-func (self *Env) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
- return nil, common.Address{}, nil
-}
-func (self *Env) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
- return nil, nil
-}
-
type account struct{}
func (account) SubBalance(amount *big.Int) {}
@@ -91,17 +38,17 @@ func (account) SetBalance(*big.Int) {}
func (account) SetNonce(uint64) {}
func (account) Balance() *big.Int { return nil }
func (account) Address() common.Address { return common.Address{} }
-func (account) ReturnGas(*big.Int, *big.Int) {}
+func (account) ReturnGas(*big.Int) {}
func (account) SetCode(common.Hash, []byte) {}
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
func runTrace(tracer *JavascriptTracer) (interface{}, error) {
- env := NewEnv(&vm.Config{Debug: true, Tracer: tracer})
+ env := vm.NewEnvironment(vm.Context{}, nil, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
- contract := vm.NewContract(account{}, account{}, big.NewInt(0), env.GasLimit(), big.NewInt(1))
+ contract := vm.NewContract(account{}, account{}, big.NewInt(0), big.NewInt(10000))
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
- _, err := env.Vm().Run(contract, []byte{})
+ _, err := env.EVM().Run(contract, []byte{})
if err != nil {
return nil, err
}
@@ -186,8 +133,8 @@ func TestHaltBetweenSteps(t *testing.T) {
t.Fatal(err)
}
- env := NewEnv(&vm.Config{Debug: true, Tracer: tracer})
- contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), big.NewInt(0), big.NewInt(0))
+ env := vm.NewEnvironment(vm.Context{}, nil, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
+ contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), big.NewInt(0))
tracer.CaptureState(env, 0, 0, big.NewInt(0), big.NewInt(0), nil, nil, contract, 0, nil)
timeout := errors.New("stahp")
diff --git a/les/api_backend.go b/les/api_backend.go
index b77767ed7..7dc548ec3 100644
--- a/les/api_backend.go
+++ b/les/api_backend.go
@@ -88,7 +88,7 @@ func (b *LesApiBackend) GetTd(blockHash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash)
}
-func (b *LesApiBackend) GetVMEnv(ctx context.Context, msg core.Message, state ethapi.State, header *types.Header) (vm.Environment, func() error, error) {
+func (b *LesApiBackend) GetVMEnv(ctx context.Context, msg core.Message, state ethapi.State, header *types.Header) (*vm.Environment, func() error, error) {
stateDb := state.(*light.LightState).Copy()
addr := msg.From()
from, err := stateDb.GetOrNewStateObject(ctx, addr)
@@ -96,8 +96,10 @@ func (b *LesApiBackend) GetVMEnv(ctx context.Context, msg core.Message, state et
return nil, nil, err
}
from.SetBalance(common.MaxBig)
- env := light.NewEnv(ctx, stateDb, b.eth.chainConfig, b.eth.blockchain, msg, header, vm.Config{})
- return env, env.Error, nil
+
+ vmstate := light.NewVMState(ctx, stateDb)
+ context := core.NewEVMContext(msg, header, b.eth.blockchain)
+ return vm.NewEnvironment(context, vmstate, b.eth.chainConfig, vm.Config{}), vmstate.Error, nil
}
func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
@@ -108,7 +110,7 @@ func (b *LesApiBackend) RemoveTx(txHash common.Hash) {
b.eth.txPool.RemoveTx(txHash)
}
-func (b *LesApiBackend) GetPoolTransactions() types.Transactions {
+func (b *LesApiBackend) GetPoolTransactions() (types.Transactions, error) {
return b.eth.txPool.GetTransactions()
}
diff --git a/les/fetcher.go b/les/fetcher.go
index ae9bf8474..c23af8da3 100644
--- a/les/fetcher.go
+++ b/les/fetcher.go
@@ -23,172 +23,426 @@ import (
"time"
"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/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 {
pm *ProtocolManager
odr *LesOdr
- chain BlockChain
-
- headAnnouncedMu sync.Mutex
- headAnnouncedBy map[common.Hash][]*peer
- currentTd *big.Int
- deliverChn chan fetchResponse
- reqMu sync.RWMutex
- requested map[uint64]fetchRequest
- timeoutChn chan uint64
- notifyChn chan bool // true if initiated from outside
- syncing bool
- syncDone chan struct{}
+ chain *light.LightChain
+
+ maxConfirmedTd *big.Int
+ peers map[*peer]*fetcherPeerInfo
+ lastUpdateStats *updateStatsEntry
+
+ lock sync.Mutex // qwerqwerqwe
+ deliverChn chan fetchResponse
+ reqMu sync.RWMutex
+ requested map[uint64]fetchRequest
+ timeoutChn chan uint64
+ requestChn chan bool // true if initiated from outside
+ syncing bool
+ 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 {
- hash common.Hash
- amount uint64
- peer *peer
+ hash common.Hash
+ amount uint64
+ peer *peer
+ sent mclock.AbsTime
+ timeout bool
}
+// fetchResponse represents a header download response
type fetchResponse struct {
reqID uint64
headers []*types.Header
+ peer *peer
}
+// newLightFetcher creates a new light fetcher
func newLightFetcher(pm *ProtocolManager) *lightFetcher {
f := &lightFetcher{
- pm: pm,
- chain: pm.blockchain,
- odr: pm.odr,
- headAnnouncedBy: make(map[common.Hash][]*peer),
- deliverChn: make(chan fetchResponse, 100),
- requested: make(map[uint64]fetchRequest),
- timeoutChn: make(chan uint64),
- notifyChn: make(chan bool, 100),
- syncDone: make(chan struct{}),
- currentTd: big.NewInt(0),
+ pm: pm,
+ chain: pm.blockchain.(*light.LightChain),
+ odr: pm.odr,
+ peers: make(map[*peer]*fetcherPeerInfo),
+ deliverChn: make(chan fetchResponse, 100),
+ requested: make(map[uint64]fetchRequest),
+ timeoutChn: make(chan uint64),
+ requestChn: make(chan bool, 100),
+ syncDone: make(chan *peer),
+ maxConfirmedTd: big.NewInt(0),
}
go f.syncLoop()
return f
}
-func (f *lightFetcher) notify(p *peer, head *announceData) {
- var headHash common.Hash
- if head == nil {
- // initial notify
- headHash = p.Head()
- } else {
- if core.GetTd(f.pm.chainDb, head.Hash, head.Number) != nil {
- head.haveHeaders = head.Number
- }
- //fmt.Println("notify", p.id, head.Number, head.ReorgDepth, head.haveHeaders)
- if !p.addNotify(head) {
- //fmt.Println("addNotify fail")
- f.pm.removePeer(p.id)
+// syncLoop is the main event loop of the light fetcher
+func (f *lightFetcher) syncLoop() {
+ f.pm.wg.Add(1)
+ defer f.pm.wg.Done()
+
+ requestStarted := false
+ for {
+ 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
+ }
+ f.reqMu.Unlock()
+ // keep starting new requests while possible
+ f.requestChn <- false
+ }()
+ }
+ }
+ }
+ 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) {
- f.headAnnouncedMu.Lock()
- defer f.headAnnouncedMu.Unlock()
+// addPeer adds a new peer to the fetcher's peer set
+func (f *lightFetcher) addPeer(p *peer) {
+ p.lock.Lock()
+ p.hasBlock = func(hash common.Hash, number uint64) bool {
+ return f.peerHasBlock(p, hash, number)
+ }
+ p.lock.Unlock()
+
+ f.lock.Lock()
+ defer f.lock.Unlock()
+
+ 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
+ }
- hash := header.Hash()
- peerList := f.headAnnouncedBy[hash]
- if peerList == nil {
+ if fp.lastAnnounced != nil && head.Td.Cmp(fp.lastAnnounced.td) <= 0 {
+ // announced tds should be strictly monotonic
+ glog.V(logger.Debug).Infof("non-monotonic Td from peer %v", p.id)
+ go f.pm.removePeer(p.id)
return
}
- number := header.Number.Uint64()
- td := core.GetTd(f.pm.chainDb, hash, number)
- for _, peer := range peerList {
- peer.lock.Lock()
- ok := peer.gotHeader(hash, number, td)
- peer.lock.Unlock()
- 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
}
- delete(f.headAnnouncedBy, hash)
-}
+ 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
+ }
+ }
+ 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
+ }
-func (f *lightFetcher) nextRequest() (*peer, *announceData) {
- var bestPeer *peer
- bestTd := f.currentTd
- for _, peer := range f.pm.peers.AllPeers() {
- peer.lock.RLock()
- if !peer.headInfo.requested && (peer.headInfo.Td.Cmp(bestTd) > 0 ||
- (bestPeer != nil && peer.headInfo.Td.Cmp(bestTd) == 0 && peer.headInfo.haveHeaders > bestPeer.headInfo.haveHeaders)) {
- bestPeer = peer
- bestTd = peer.headInfo.Td
+ if n == nil {
+ break
+ }
}
- peer.lock.RUnlock()
- }
- if bestPeer == nil {
- return nil, nil
- }
- bestPeer.lock.Lock()
- res := bestPeer.headInfo
- 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
+ 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++
}
- peer.lock.Unlock()
+ n.hash = head.Hash
+ n.td = head.Td
+ fp.nodeByHash[n.hash] = n
}
}
- return bestPeer, res
-}
+ 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
+ }
-func (f *lightFetcher) deliverHeaders(reqID uint64, headers []*types.Header) {
- f.deliverChn <- fetchResponse{reqID: reqID, headers: headers}
+ 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) requestedID(reqID uint64) bool {
- f.reqMu.RLock()
- _, ok := f.requested[reqID]
- f.reqMu.RUnlock()
- return ok
+// peerHasBlock returns true if we can assume the peer knows the given block
+// based on its announcements
+func (f *lightFetcher) peerHasBlock(p *peer, hash common.Hash, number uint64) bool {
+ f.lock.Lock()
+ defer f.lock.Unlock()
+
+ fp := f.peers[p]
+ if fp == nil || fp.root == nil {
+ return false
+ }
+
+ 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
+ }
+ f.chain.LockChain()
+ defer f.chain.UnlockChain()
+ // if it's older than the peer's block tree root but it's in the same canonical chain
+ // than the root, we can still be sure the peer knows it
+ return core.GetCanonicalHash(f.pm.chainDb, fp.root.number) == fp.root.hash && core.GetCanonicalHash(f.pm.chainDb, number) == hash
}
-func (f *lightFetcher) request(p *peer, block *announceData) {
- //fmt.Println("request", p.id, block.Number, block.haveHeaders)
- amount := block.Number - block.haveHeaders
- if amount == 0 {
- return
+// request initiates a header download request from a certain peer
+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 amount > 100 {
+ if fp.bestConfirmed == nil || fp.root == nil || !f.checkKnownNode(p, fp.root) {
f.syncing = true
go func() {
- //fmt.Println("f.pm.synchronise(p)")
+ glog.V(logger.Debug).Infof("synchronising with peer %v", p.id)
f.pm.synchronise(p)
- //fmt.Println("sync done")
- f.syncDone <- struct{}{}
+ f.syncDone <- p
}()
- return
+ return 0, false
}
- reqID := f.odr.getNextReqID()
- f.reqMu.Lock()
- f.requested[reqID] = fetchRequest{hash: block.Hash, amount: amount, peer: p}
- f.reqMu.Unlock()
+ reqID := getNextReqID()
+ n.requested = true
cost := p.GetRequestCost(GetBlockHeadersMsg, int(amount))
p.fcServer.SendRequest(reqID, cost)
- go p.RequestHeadersByHash(reqID, cost, block.Hash, int(amount), 0, true)
+ 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 {
+ f.reqMu.RLock()
+ _, ok := f.requested[reqID]
+ f.reqMu.RUnlock()
+ return ok
+}
+
+// nextRequest selects the peer and announced head to be requested next, amount
+// to be downloaded starting from the head backwards is also returned
+func (f *lightFetcher) nextRequest() (*peer, *fetcherTreeNode, uint64) {
+ var (
+ 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 bestTd == f.maxConfirmedTd {
+ return nil, nil, 0
+ }
+
+ peer := f.pm.serverPool.selectPeer(func(p *peer) (bool, uint64) {
+ fp := f.peers[p]
+ if fp == nil || fp.nodeByHash[bestHash] == nil {
+ return false, 0
+ }
+ return true, p.fcServer.CanSend(p.GetRequestCost(GetBlockHeadersMsg, int(bestAmount)))
+ })
+ var node *fetcherTreeNode
+ if peer != nil {
+ 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 {
if uint64(len(resp.headers)) != req.amount || resp.headers[0].Hash() != req.hash {
return false
@@ -200,96 +454,248 @@ func (f *lightFetcher) processResponse(req fetchRequest, resp fetchResponse) boo
if _, err := f.chain.InsertHeaderChain(headers, 1); err != nil {
return false
}
- for _, header := range headers {
- td := core.GetTd(f.pm.chainDb, header.Hash(), header.Number.Uint64())
+ tds := make([]*big.Int, len(headers))
+ for i, header := range headers {
+ td := f.chain.GetTd(header.Hash(), header.Number.Uint64())
if td == nil {
return false
}
- if td.Cmp(f.currentTd) > 0 {
- f.currentTd = td
- }
- f.gotHeader(header)
+ tds[i] = td
}
+ f.newHeaders(headers, tds)
return true
}
-func (f *lightFetcher) checkSyncedHeaders() {
- //fmt.Println("checkSyncedHeaders()")
- for _, peer := range f.pm.peers.AllPeers() {
- peer.lock.Lock()
- h := peer.firstHeadInfo
- remove := false
- loop:
- for h != nil {
- 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 {
- f.currentTd = td
- }
- }
- h = h.next
+// newHeaders updates the block trees of all active peers according to a newly
+// downloaded and validated batch or headers
+func (f *lightFetcher) newHeaders(headers []*types.Header, tds []*big.Int) {
+ var maxTd *big.Int
+ for p, fp := range f.peers {
+ if !f.checkAnnouncedHeaders(fp, headers, tds) {
+ glog.V(logger.Debug).Infof("announce inconsistency by peer %v", p.id)
+ go f.pm.removePeer(p.id)
}
- peer.lock.Unlock()
- if remove {
- //fmt.Println("checkSync fail")
- f.pm.removePeer(peer.id)
+ if fp.confirmedTd != nil && (maxTd == nil || maxTd.Cmp(fp.confirmedTd) > 0) {
+ maxTd = fp.confirmedTd
}
}
+ if maxTd != nil {
+ f.updateMaxConfirmedTd(maxTd)
+ }
}
-func (f *lightFetcher) syncLoop() {
- f.pm.wg.Add(1)
- defer f.pm.wg.Done()
+// 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
+ )
- srtoNotify := false
- for {
- select {
- case <-f.pm.quitSync:
- return
- case ext := <-f.notifyChn:
- //fmt.Println("<-f.notifyChn", f.syncing, ext, srtoNotify)
- s := srtoNotify
- srtoNotify = false
- if !f.syncing && !(ext && s) {
- if p, r := f.nextRequest(); r != nil {
- srtoNotify = true
- go func() {
- time.Sleep(softRequestTimeout)
- f.notifyChn <- false
- }()
- f.request(p, r)
+ 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
}
}
- case reqID := <-f.timeoutChn:
- f.reqMu.Lock()
- req, ok := f.requested[reqID]
- if ok {
- delete(f.requested, reqID)
+ // 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
}
- f.reqMu.Unlock()
- if ok {
- //fmt.Println("hard timeout")
- f.pm.removePeer(req.peer.id)
+ if n.known {
+ // we reached a known node that matched our expectations, return with success
+ return true
}
- case resp := <-f.deliverChn:
- //fmt.Println("<-f.deliverChn", f.syncing)
- f.reqMu.Lock()
- req, ok := f.requested[resp.reqID]
- delete(f.requested, resp.reqID)
- f.reqMu.Unlock()
- if !ok || !(f.syncing || f.processResponse(req, resp)) {
- //fmt.Println("processResponse fail")
- f.pm.removePeer(req.peer.id)
+ n.known = true
+ if fp.confirmedTd == nil || td.Cmp(fp.confirmedTd) > 0 {
+ fp.confirmedTd = td
+ fp.bestConfirmed = n
}
- case <-f.syncDone:
- //fmt.Println("<-f.syncDone", f.syncing)
- f.checkSyncedHeaders()
- f.syncing = false
+ n = n.parent
+ if n == nil {
+ return true
+ }
+ }
+ }
+}
+
+// checkSyncedHeaders updates peer's block tree after synchronisation by marking
+// downloaded headers as known. If none of the announced headers are found after
+// syncing, the peer is dropped.
+func (f *lightFetcher) checkSyncedHeaders(p *peer) {
+ fp := f.peers[p]
+ if fp == nil {
+ glog.V(logger.Debug).Infof("checkSyncedHeaders: unknown peer")
+ return
+ }
+ n := fp.lastAnnounced
+ var td *big.Int
+ for n != nil {
+ if td = f.chain.GetTd(n.hash, n.number); td != nil {
+ break
+ }
+ n = n.parent
+ }
+ // now n is the latest downloaded header after syncing
+ if n == nil {
+ 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
+ }
+ }
+ }
+ for {
+ if n.td != nil {
+ delete(fp.nodeByHash, n.hash)
+ }
+ fp.nodeCnt--
+ if len(n.children) == 0 {
+ return
+ }
+ for i, nn := range n.children {
+ if i == 0 {
+ n = nn
+ } else {
+ fp.deleteNode(nn)
+ }
+ }
+ }
+}
+
+// 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
}
}
}
diff --git a/les/handler.go b/les/handler.go
index 83d73666f..b024841f2 100644
--- a/les/handler.go
+++ b/les/handler.go
@@ -22,8 +22,8 @@ import (
"errors"
"fmt"
"math/big"
+ "net"
"sync"
- "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
@@ -58,7 +58,7 @@ const (
MaxHeaderProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval 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
@@ -88,7 +88,7 @@ type BlockChain interface {
type txPool interface {
// AddTransactions should add the given transactions to the pool.
- AddBatch([]*types.Transaction)
+ AddBatch([]*types.Transaction) error
}
type ProtocolManager struct {
@@ -101,10 +101,7 @@ type ProtocolManager struct {
chainDb ethdb.Database
odr *LesOdr
server *LesServer
-
- topicDisc *discv5.Network
- lesTopic discv5.Topic
- p2pServer *p2p.Server
+ serverPool *serverPool
downloader *downloader.Downloader
fetcher *lightFetcher
@@ -157,13 +154,29 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, network
Version: version,
Length: ProtocolLengths[i],
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
+ var entry *poolEntry
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 {
case manager.newPeerCh <- peer:
manager.wg.Add(1)
defer manager.wg.Done()
- return manager.handle(peer)
+ err := manager.handle(peer)
+ if entry != nil {
+ manager.serverPool.disconnect(entry)
+ }
+ return err
case <-manager.quitSync:
+ if entry != nil {
+ manager.serverPool.disconnect(entry)
+ }
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,
nil, blockchain.CurrentHeader, nil, nil, nil, blockchain.GetTdByHash,
blockchain.InsertHeaderChain, nil, nil, blockchain.Rollback, removePeer)
- manager.fetcher = newLightFetcher(manager)
}
if odr != nil {
@@ -222,10 +234,12 @@ func (pm *ProtocolManager) removePeer(id string) {
glog.V(logger.Debug).Infof("LES: unregister peer %v", id)
if pm.lightSync {
pm.downloader.UnregisterPeer(id)
- pm.odr.UnregisterPeer(peer)
if pm.txrelay != nil {
pm.txrelay.removePeer(id)
}
+ if pm.fetcher != nil {
+ pm.fetcher.removePeer(peer)
+ }
}
if err := pm.peers.Unregister(id); err != nil {
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) {
- pm.p2pServer = srvr
+ var topicDisc *discv5.Network
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 {
// 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()
} else {
- if pm.topicDisc != nil {
+ if topicDisc != nil {
go func() {
- glog.V(logger.Debug).Infoln("Starting registering topic", string(pm.lesTopic))
- pm.topicDisc.RegisterTopic(pm.lesTopic, pm.quitSync)
- glog.V(logger.Debug).Infoln("Stopped registering topic", string(pm.lesTopic))
+ glog.V(logger.Info).Infoln("Starting registering topic", string(lesTopic))
+ topicDisc.RegisterTopic(lesTopic, pm.quitSync)
+ glog.V(logger.Info).Infoln("Stopped registering topic", string(lesTopic))
}()
}
go func() {
@@ -352,13 +338,13 @@ func (pm *ProtocolManager) handle(p *peer) error {
glog.V(logger.Debug).Infof("LES: register peer %v", p.id)
if pm.lightSync {
requestHeadersByHash := func(origin common.Hash, amount int, skip int, reverse bool) error {
- reqID := pm.odr.getNextReqID()
+ reqID := getNextReqID()
cost := p.GetRequestCost(GetBlockHeadersMsg, amount)
p.fcServer.SendRequest(reqID, cost)
return p.RequestHeadersByHash(reqID, cost, origin, amount, skip, reverse)
}
requestHeadersByNumber := func(origin uint64, amount int, skip int, reverse bool) error {
- reqID := pm.odr.getNextReqID()
+ reqID := getNextReqID()
cost := p.GetRequestCost(GetBlockHeadersMsg, amount)
p.fcServer.SendRequest(reqID, cost)
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 {
return err
}
- pm.odr.RegisterPeer(p)
if pm.txrelay != nil {
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{})
@@ -454,7 +449,9 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
return errResp(ErrDecode, "%v: %v", msg, err)
}
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:
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)
}
p.fcServer.GotReply(resp.ReqID, resp.BV)
- if pm.fetcher.requestedID(resp.ReqID) {
- pm.fetcher.deliverHeaders(resp.ReqID, resp.Headers)
+ if pm.fetcher != nil && pm.fetcher.requestedID(resp.ReqID) {
+ pm.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers)
} else {
err := pm.downloader.DeliverHeaders(p.id, resp.Headers)
if err != nil {
@@ -879,7 +876,11 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
if reqCnt > maxReqs || reqCnt > MaxTxSend {
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)
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
diff --git a/les/helper_test.go b/les/helper_test.go
index a5428e954..2df3faab2 100644
--- a/les/helper_test.go
+++ b/les/helper_test.go
@@ -25,6 +25,7 @@ import (
"math/big"
"sync"
"testing"
+ "time"
"github.com/ethereum/go-ethereum/common"
"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() {
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) {
+
+}
diff --git a/les/odr.go b/les/odr.go
index 444b1da2a..8878508c4 100644
--- a/les/odr.go
+++ b/les/odr.go
@@ -17,6 +17,8 @@
package les
import (
+ "crypto/rand"
+ "encoding/binary"
"sync"
"time"
@@ -37,6 +39,11 @@ var (
// peerDropFn is a callback type for dropping a peer detected as malicious.
type peerDropFn func(id string)
+type odrPeerSelector interface {
+ selectPeer(func(*peer) (bool, uint64)) *peer
+ adjustResponseTime(*poolEntry, time.Duration, bool)
+}
+
type LesOdr struct {
light.OdrBackend
db ethdb.Database
@@ -44,15 +51,13 @@ type LesOdr struct {
removePeer peerDropFn
mlock, clock sync.Mutex
sentReqs map[uint64]*sentReq
- peers *odrPeerSet
- lastReqID uint64
+ serverPool odrPeerSelector
}
func NewLesOdr(db ethdb.Database) *LesOdr {
return &LesOdr{
db: db,
stop: make(chan struct{}),
- peers: newOdrPeerSet(),
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
}
-// 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 (
MsgBlockBodies = iota
MsgCode
@@ -142,29 +137,26 @@ func (self *LesOdr) requestPeer(req *sentReq, peer *peer, delivered, timeout cha
select {
case <-delivered:
- servTime := uint64(mclock.Now() - stime)
- self.peers.updateTimeout(peer, false)
- self.peers.updateServTime(peer, servTime)
+ if self.serverPool != nil {
+ self.serverPool.adjustResponseTime(peer.poolEntry, time.Duration(mclock.Now()-stime), false)
+ }
return
case <-time.After(softRequestTimeout):
close(timeout)
- if self.peers.updateTimeout(peer, true) {
- self.removePeer(peer.id)
- }
case <-self.stop:
return
}
select {
case <-delivered:
- servTime := uint64(mclock.Now() - stime)
- self.peers.updateServTime(peer, servTime)
- return
case <-time.After(hardRequestTimeout):
- self.removePeer(peer.id)
+ go self.removePeer(peer.id)
case <-self.stop:
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
@@ -176,7 +168,7 @@ func (self *LesOdr) networkRequest(ctx context.Context, lreq LesOdrRequest) erro
sentTo: make(map[*peer]chan struct{}),
answered: answered, // reply delivered by any peer
}
- reqID := self.getNextReqID()
+ reqID := getNextReqID()
self.mlock.Lock()
self.sentReqs[reqID] = req
self.mlock.Unlock()
@@ -193,7 +185,16 @@ func (self *LesOdr) networkRequest(ctx context.Context, lreq LesOdrRequest) erro
exclude := make(map[*peer]struct{})
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 {
case <-ctx.Done():
return ctx.Err()
@@ -202,17 +203,17 @@ func (self *LesOdr) networkRequest(ctx context.Context, lreq LesOdrRequest) erro
case <-time.After(retryPeers):
}
} else {
- exclude[peer] = struct{}{}
+ exclude[p] = struct{}{}
delivered := make(chan struct{})
timeout := make(chan struct{})
req.lock.Lock()
- req.sentTo[peer] = delivered
+ req.sentTo[p] = delivered
req.lock.Unlock()
reqWg.Add(1)
- cost := lreq.GetCost(peer)
- peer.fcServer.SendRequest(reqID, cost)
- go self.requestPeer(req, peer, delivered, timeout, reqWg)
- lreq.Request(reqID, peer)
+ cost := lreq.GetCost(p)
+ p.fcServer.SendRequest(reqID, cost)
+ go self.requestPeer(req, p, delivered, timeout, reqWg)
+ lreq.Request(reqID, p)
select {
case <-ctx.Done():
@@ -239,10 +240,8 @@ func (self *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err err
return
}
-func (self *LesOdr) getNextReqID() uint64 {
- self.clock.Lock()
- defer self.clock.Unlock()
-
- self.lastReqID++
- return self.lastReqID
+func getNextReqID() uint64 {
+ var rnd [8]byte
+ rand.Read(rnd[:])
+ return binary.BigEndian.Uint64(rnd[:])
}
diff --git a/les/odr_peerset.go b/les/odr_peerset.go
deleted file mode 100644
index e9b7eec7f..000000000
--- a/les/odr_peerset.go
+++ /dev/null
@@ -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++
- }
-}
diff --git a/les/odr_requests.go b/les/odr_requests.go
index f4bd51888..a4fbd79f6 100644
--- a/les/odr_requests.go
+++ b/les/odr_requests.go
@@ -36,6 +36,7 @@ import (
type LesOdrRequest interface {
GetCost(*peer) uint64
+ CanSend(*peer) bool
Request(uint64, *peer) error
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)
}
+// 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)
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)
@@ -121,6 +127,11 @@ func (self *ReceiptsRequest) GetCost(peer *peer) uint64 {
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)
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)
@@ -171,6 +182,11 @@ func (self *TrieRequest) GetCost(peer *peer) uint64 {
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)
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)
@@ -221,6 +237,11 @@ func (self *CodeRequest) GetCost(peer *peer) uint64 {
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)
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)
@@ -274,6 +295,14 @@ func (self *ChtRequest) GetCost(peer *peer) uint64 {
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)
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)
diff --git a/les/odr_test.go b/les/odr_test.go
index 80f7b8208..685435996 100644
--- a/les/odr_test.go
+++ b/les/odr_test.go
@@ -115,12 +115,17 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
if bc != nil {
header := bc.GetHeaderByHash(bhash)
statedb, err := state.New(header.Root, db)
+
if err == nil {
from := statedb.GetOrNewStateObject(testBankAddress)
from.SetBalance(common.MaxBig)
msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), big.NewInt(100000), new(big.Int), data, false)}
- vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
+
+ context := core.NewEVMContext(msg, header, bc)
+ vmenv := vm.NewEnvironment(context, statedb, config, vm.Config{})
+
+ //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
gp := new(core.GasPool).AddGas(common.MaxBig)
ret, _, _ := core.ApplyMessage(vmenv, msg, gp)
res = append(res, ret...)
@@ -128,16 +133,20 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
} else {
header := lc.GetHeaderByHash(bhash)
state := light.NewLightState(light.StateTrieID(header), lc.Odr())
+ vmstate := light.NewVMState(ctx, state)
from, err := state.GetOrNewStateObject(ctx, testBankAddress)
if err == nil {
from.SetBalance(common.MaxBig)
msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), big.NewInt(100000), new(big.Int), data, false)}
- vmenv := light.NewEnv(ctx, state, config, lc, msg, header, vm.Config{})
+ context := core.NewEVMContext(msg, header, lc)
+ vmenv := vm.NewEnvironment(context, vmstate, config, vm.Config{})
+
+ //vmenv := light.NewEnv(ctx, state, config, lc, msg, header, vm.Config{})
gp := new(core.GasPool).AddGas(common.MaxBig)
ret, _, _ := core.ApplyMessage(vmenv, msg, gp)
- if vmenv.Error() == nil {
+ if vmstate.Error() == nil {
res = append(res, ret...)
}
}
@@ -151,6 +160,8 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
pm, db, odr := newTestProtocolManagerMust(t, false, 4, testChainGen)
lpm, ldb, odr := newTestProtocolManagerMust(t, true, 0, nil)
_, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm)
+ pool := (*testServerPool)(lpeer)
+ odr.serverPool = pool
select {
case <-time.After(time.Millisecond * 100):
case err := <-err1:
@@ -179,13 +190,13 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
}
// temporarily remove peer to test odr fails
- odr.UnregisterPeer(lpeer)
+ odr.serverPool = nil
// expect retrievals to fail (except genesis block) without a les peer
test(expFail)
- odr.RegisterPeer(lpeer)
+ odr.serverPool = pool
// expect all retrievals to pass
test(5)
- odr.UnregisterPeer(lpeer)
+ odr.serverPool = nil
// still expect all retrievals to pass, now data should be cached locally
test(5)
}
diff --git a/les/peer.go b/les/peer.go
index 5d566d899..0a8db4975 100644
--- a/les/peer.go
+++ b/les/peer.go
@@ -51,12 +51,14 @@ type peer struct {
id string
- firstHeadInfo, headInfo *announceData
- headInfoLen int
- lock sync.RWMutex
+ headInfo *announceData
+ lock sync.RWMutex
announceChn chan announceData
+ poolEntry *poolEntry
+ hasBlock func(common.Hash, uint64) bool
+
fcClient *flowcontrol.ClientNode // nil if the peer is server only
fcServer *flowcontrol.ServerNode // nil if the peer is client only
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}
}
-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.
func (p *peer) Td() *big.Int {
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 {
+ p.lock.RLock()
+ defer p.lock.RUnlock()
+
cost := p.fcCosts[msgcode].baseCost + p.fcCosts[msgcode].reqCost*uint64(amount)
if cost > p.fcServerParams.BufLimit {
cost = p.fcServerParams.BufLimit
@@ -202,6 +146,14 @@ func (p *peer) GetRequestCost(msgcode uint64, amount int) uint64 {
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
// a hash notification.
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.firstHeadInfo = &announceData{Td: rTd, Hash: rHash, Number: rNum}
- p.headInfo = p.firstHeadInfo
- p.headInfoLen = 1
+ p.headInfo = &announceData{Td: rTd, Hash: rHash, Number: rNum}
return nil
}
diff --git a/les/randselect.go b/les/randselect.go
new file mode 100644
index 000000000..1a9d0695b
--- /dev/null
+++ b/les/randselect.go
@@ -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)
+}
diff --git a/les/randselect_test.go b/les/randselect_test.go
new file mode 100644
index 000000000..f3c34305e
--- /dev/null
+++ b/les/randselect_test.go
@@ -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)
+}
diff --git a/les/request_test.go b/les/request_test.go
index a6fbb06ce..03b946771 100644
--- a/les/request_test.go
+++ b/les/request_test.go
@@ -71,6 +71,8 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) {
pm, db, _ := newTestProtocolManagerMust(t, false, 4, testChainGen)
lpm, ldb, odr := newTestProtocolManagerMust(t, true, 0, nil)
_, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm)
+ pool := (*testServerPool)(lpeer)
+ odr.serverPool = pool
select {
case <-time.After(time.Millisecond * 100):
case err := <-err1:
@@ -100,11 +102,10 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) {
}
// temporarily remove peer to test odr fails
- odr.UnregisterPeer(lpeer)
+ odr.serverPool = nil
// expect retrievals to fail (except genesis block) without a les peer
test(0)
- odr.RegisterPeer(lpeer)
+ odr.serverPool = pool
// expect all retrievals to pass
test(5)
- odr.UnregisterPeer(lpeer)
}
diff --git a/les/server.go b/les/server.go
index c763e8c63..e55616a44 100644
--- a/les/server.go
+++ b/les/server.go
@@ -42,6 +42,9 @@ type LesServer struct {
fcManager *flowcontrol.ClientManager // nil if our node is client only
fcCostStats *requestCostStats
defParams *flowcontrol.ServerParams
+ srvr *p2p.Server
+ synced, stopped bool
+ lock sync.Mutex
}
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
@@ -67,12 +70,35 @@ func (s *LesServer) Protocols() []p2p.Protocol {
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) {
- 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() {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.stopped = true
s.fcCostStats.store()
s.fcManager.Stop()
go func() {
@@ -323,9 +349,8 @@ func (pm *ProtocolManager) blockLoop() {
}
var (
- lastChtKey = []byte("LastChtNumber") // chtNum (uint64 big endian)
- chtPrefix = []byte("cht") // chtPrefix + chtNum (uint64 big endian) -> trie root hash
- chtConfirmations = light.ChtFrequency / 2
+ lastChtKey = []byte("LastChtNumber") // chtNum (uint64 big endian)
+ chtPrefix = []byte("cht") // chtPrefix + chtNum (uint64 big endian) -> trie root 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)
var newChtNum uint64
- if headNum > chtConfirmations {
- newChtNum = (headNum - chtConfirmations) / light.ChtFrequency
+ if headNum > light.ChtConfirmations {
+ newChtNum = (headNum - light.ChtConfirmations) / light.ChtFrequency
}
var lastChtNum uint64
diff --git a/les/serverpool.go b/les/serverpool.go
new file mode 100644
index 000000000..f5e880460
--- /dev/null
+++ b/les/serverpool.go
@@ -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++
+}
diff --git a/light/lightchain.go b/light/lightchain.go
index 1cea7a892..d397f5006 100644
--- a/light/lightchain.go
+++ b/light/lightchain.go
@@ -505,3 +505,14 @@ func (self *LightChain) SyncCht(ctx context.Context) bool {
}
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()
+}
diff --git a/light/odr.go b/light/odr.go
index 679569bf9..4f6ef6b9e 100644
--- a/light/odr.go
+++ b/light/odr.go
@@ -48,6 +48,7 @@ type OdrRequest interface {
// TrieID identifies a state or account storage trie
type TrieID struct {
BlockHash, Root common.Hash
+ BlockNumber uint64
AccKey []byte
}
@@ -55,9 +56,10 @@ type TrieID struct {
// header.
func StateTrieID(header *types.Header) *TrieID {
return &TrieID{
- BlockHash: header.Hash(),
- AccKey: nil,
- Root: header.Root,
+ BlockHash: header.Hash(),
+ BlockNumber: header.Number.Uint64(),
+ AccKey: nil,
+ Root: header.Root,
}
}
@@ -66,9 +68,10 @@ func StateTrieID(header *types.Header) *TrieID {
// checking Merkle proofs.
func StorageTrieID(state *TrieID, addr common.Address, root common.Hash) *TrieID {
return &TrieID{
- BlockHash: state.BlockHash,
- AccKey: crypto.Keccak256(addr[:]),
- Root: root,
+ BlockHash: state.BlockHash,
+ BlockNumber: state.BlockNumber,
+ AccKey: crypto.Keccak256(addr[:]),
+ Root: root,
}
}
diff --git a/light/odr_test.go b/light/odr_test.go
index 50255a7f3..2f60f32fd 100644
--- a/light/odr_test.go
+++ b/light/odr_test.go
@@ -157,6 +157,8 @@ func (callmsg) CheckNonce() bool { return false }
func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) []byte {
data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000")
+ config := params.TestChainConfig
+
var res []byte
for i := 0; i < 3; i++ {
data[35] = byte(i)
@@ -168,7 +170,10 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
from.SetBalance(common.MaxBig)
msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), big.NewInt(1000000), new(big.Int), data, false)}
- vmenv := core.NewEnv(statedb, testChainConfig(), bc, msg, header, vm.Config{})
+
+ context := core.NewEVMContext(msg, header, bc)
+ vmenv := vm.NewEnvironment(context, statedb, config, vm.Config{})
+
gp := new(core.GasPool).AddGas(common.MaxBig)
ret, _, _ := core.ApplyMessage(vmenv, msg, gp)
res = append(res, ret...)
@@ -176,15 +181,17 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
} else {
header := lc.GetHeaderByHash(bhash)
state := NewLightState(StateTrieID(header), lc.Odr())
+ vmstate := NewVMState(ctx, state)
from, err := state.GetOrNewStateObject(ctx, testBankAddress)
if err == nil {
from.SetBalance(common.MaxBig)
msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), big.NewInt(1000000), new(big.Int), data, false)}
- vmenv := NewEnv(ctx, state, testChainConfig(), lc, msg, header, vm.Config{})
+ context := core.NewEVMContext(msg, header, lc)
+ vmenv := vm.NewEnvironment(context, vmstate, config, vm.Config{})
gp := new(core.GasPool).AddGas(common.MaxBig)
ret, _, _ := core.ApplyMessage(vmenv, msg, gp)
- if vmenv.Error() == nil {
+ if vmstate.Error() == nil {
res = append(res, ret...)
}
}
diff --git a/light/odr_util.go b/light/odr_util.go
index 5c72f90e9..761711621 100644
--- a/light/odr_util.go
+++ b/light/odr_util.go
@@ -38,8 +38,9 @@ var (
ErrNoTrustedCht = errors.New("No trusted canonical hash trie")
ErrNoHeader = errors.New("Header not found")
- ChtFrequency = uint64(4096)
- trustedChtKey = []byte("TrustedCHT")
+ ChtFrequency = uint64(4096)
+ ChtConfirmations = uint64(2048)
+ trustedChtKey = []byte("TrustedCHT")
)
type ChtNode struct {
diff --git a/light/state.go b/light/state.go
index 9f2376809..f8b75c588 100644
--- a/light/state.go
+++ b/light/state.go
@@ -141,6 +141,15 @@ func (self *LightState) AddBalance(ctx context.Context, addr common.Address, amo
return err
}
+// SubBalance adds the given amount to the balance of the specified account
+func (self *LightState) SubBalance(ctx context.Context, addr common.Address, amount *big.Int) error {
+ stateObject, err := self.GetOrNewStateObject(ctx, addr)
+ if err == nil && stateObject != nil {
+ stateObject.SubBalance(amount)
+ }
+ return err
+}
+
// SetNonce sets the nonce of the specified account
func (self *LightState) SetNonce(ctx context.Context, addr common.Address, nonce uint64) error {
stateObject, err := self.GetOrNewStateObject(ctx, addr)
diff --git a/light/state_object.go b/light/state_object.go
index 6161d2dfb..56f607bff 100644
--- a/light/state_object.go
+++ b/light/state_object.go
@@ -179,7 +179,7 @@ func (c *StateObject) SetBalance(amount *big.Int) {
}
// ReturnGas returns the gas back to the origin. Used by the Virtual machine or Closures
-func (c *StateObject) ReturnGas(gas, price *big.Int) {}
+func (c *StateObject) ReturnGas(gas *big.Int) {}
// Copy creates a copy of the state object
func (self *StateObject) Copy() *StateObject {
diff --git a/light/txpool.go b/light/txpool.go
index 309bc3a32..d0781593b 100644
--- a/light/txpool.go
+++ b/light/txpool.go
@@ -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 {
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
+ currentState := pool.currentState()
if n, err := currentState.GetNonce(ctx, from); err == nil {
if n > tx.Nonce() {
return core.ErrNonce
@@ -500,7 +489,7 @@ func (tp *TxPool) GetTransaction(hash common.Hash) *types.Transaction {
// GetTransactions returns all currently processable transactions.
// 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()
defer self.mu.RUnlock()
@@ -510,7 +499,7 @@ func (self *TxPool) GetTransactions() (txs types.Transactions) {
txs[i] = tx
i++
}
- return txs
+ return txs, nil
}
// Content retrieves the data content of the transaction pool, returning all the
diff --git a/light/vm_env.go b/light/vm_env.go
index d4d7bcce7..cc0c568c9 100644
--- a/light/vm_env.go
+++ b/light/vm_env.go
@@ -20,123 +20,38 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/params"
"golang.org/x/net/context"
)
-// VMEnv is the light client version of the vm execution environment.
-// Unlike other structures, VMEnv holds a context that is applied by state
-// retrieval requests through the entire execution. If any state operation
-// returns an error, the execution fails.
-type VMEnv struct {
- vm.Environment
- ctx context.Context
- chainConfig *params.ChainConfig
- evm *vm.EVM
- state *VMState
- header *types.Header
- msg core.Message
- depth int
- chain *LightChain
- err error
-}
-
-// NewEnv creates a new execution environment based on an ODR capable light state
-func NewEnv(ctx context.Context, state *LightState, chainConfig *params.ChainConfig, chain *LightChain, msg core.Message, header *types.Header, cfg vm.Config) *VMEnv {
- env := &VMEnv{
- chainConfig: chainConfig,
- chain: chain,
- header: header,
- msg: msg,
- }
- env.state = &VMState{ctx: ctx, state: state, env: env}
-
- env.evm = vm.New(env, cfg)
- return env
-}
-
-func (self *VMEnv) ChainConfig() *params.ChainConfig { return self.chainConfig }
-func (self *VMEnv) Vm() vm.Vm { return self.evm }
-func (self *VMEnv) Origin() common.Address { return self.msg.From() }
-func (self *VMEnv) BlockNumber() *big.Int { return self.header.Number }
-func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase }
-func (self *VMEnv) Time() *big.Int { return self.header.Time }
-func (self *VMEnv) Difficulty() *big.Int { return self.header.Difficulty }
-func (self *VMEnv) GasLimit() *big.Int { return self.header.GasLimit }
-func (self *VMEnv) Db() vm.Database { return self.state }
-func (self *VMEnv) Depth() int { return self.depth }
-func (self *VMEnv) SetDepth(i int) { self.depth = i }
-func (self *VMEnv) GetHash(n uint64) common.Hash {
- for header := self.chain.GetHeader(self.header.ParentHash, self.header.Number.Uint64()-1); header != nil; header = self.chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {
- if header.Number.Uint64() == n {
- return header.Hash()
- }
- }
-
- return common.Hash{}
-}
-
-func (self *VMEnv) AddLog(log *vm.Log) {
- //self.state.AddLog(log)
-}
-func (self *VMEnv) CanTransfer(from common.Address, balance *big.Int) bool {
- return self.state.GetBalance(from).Cmp(balance) >= 0
-}
-
-func (self *VMEnv) SnapshotDatabase() int {
- return self.state.SnapshotDatabase()
-}
-
-func (self *VMEnv) RevertToSnapshot(idx int) {
- self.state.RevertToSnapshot(idx)
-}
-
-func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) {
- core.Transfer(from, to, amount)
-}
-
-func (self *VMEnv) Call(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return core.Call(self, me, addr, data, gas, price, value)
-}
-func (self *VMEnv) CallCode(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- return core.CallCode(self, me, addr, data, gas, price, value)
-}
-
-func (self *VMEnv) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
- return core.DelegateCall(self, me, addr, data, gas, price)
-}
-
-func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
- return core.Create(self, me, data, gas, price, value)
-}
-
-// Error returns the error (if any) that happened during execution.
-func (self *VMEnv) Error() error {
- return self.err
-}
-
// VMState is a wrapper for the light state that holds the actual context and
// passes it to any state operation that requires it.
type VMState struct {
- vm.Database
ctx context.Context
state *LightState
snapshots []*LightState
- env *VMEnv
+ err error
+}
+
+func NewVMState(ctx context.Context, state *LightState) *VMState {
+ return &VMState{ctx: ctx, state: state}
+}
+
+func (s *VMState) Error() error {
+ return s.err
}
+func (s *VMState) AddLog(log *vm.Log) {}
+
// errHandler handles and stores any state error that happens during execution.
func (s *VMState) errHandler(err error) {
- if err != nil && s.env.err == nil {
- s.env.err = err
+ if err != nil && s.err == nil {
+ s.err = err
}
}
-func (self *VMState) SnapshotDatabase() int {
+func (self *VMState) Snapshot() int {
self.snapshots = append(self.snapshots, self.state.Copy())
return len(self.snapshots) - 1
}
@@ -175,6 +90,12 @@ func (s *VMState) AddBalance(addr common.Address, amount *big.Int) {
s.errHandler(err)
}
+// SubBalance adds the given amount to the balance of the specified account
+func (s *VMState) SubBalance(addr common.Address, amount *big.Int) {
+ err := s.state.SubBalance(s.ctx, addr, amount)
+ s.errHandler(err)
+}
+
// GetBalance retrieves the balance from the given address or 0 if the account does
// not exist
func (s *VMState) GetBalance(addr common.Address) *big.Int {
diff --git a/miner/miner.go b/miner/miner.go
index 87568ac18..61cd3e049 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -119,15 +119,14 @@ func (m *Miner) SetGasPrice(price *big.Int) {
func (self *Miner) Start(coinbase common.Address, threads int) {
atomic.StoreInt32(&self.shouldStart, 1)
- self.threads = threads
- self.worker.coinbase = coinbase
+ self.worker.setEtherbase(coinbase)
self.coinbase = coinbase
+ self.threads = threads
if atomic.LoadInt32(&self.canStart) == 0 {
glog.V(logger.Info).Infoln("Can not start mining operation due to network sync (starts when finished)")
return
}
-
atomic.StoreInt32(&self.mining, 1)
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))
-
self.worker.start()
-
self.worker.commitNewWork()
}
@@ -177,8 +174,7 @@ func (self *Miner) SetExtra(extra []byte) error {
if uint64(len(extra)) > params.MaximumExtraDataSize.Uint64() {
return fmt.Errorf("Extra exceeds max length. %d > %v", len(extra), params.MaximumExtraDataSize)
}
-
- self.worker.extra = extra
+ self.worker.setExtra(extra)
return nil
}
@@ -188,9 +184,9 @@ func (self *Miner) Pending() (*types.Block, *state.StateDB) {
}
// PendingBlock returns the currently pending block.
-//
-// Note, to access both the pending block and the pending state
-// simultaneously, please use Pending(), as the pending state can
+//
+// Note, to access both the pending block and the pending state
+// simultaneously, please use Pending(), as the pending state can
// change between multiple method calls
func (self *Miner) PendingBlock() *types.Block {
return self.worker.pendingBlock()
diff --git a/miner/remote_agent.go b/miner/remote_agent.go
index 00b5f7e08..1a27a1312 100644
--- a/miner/remote_agent.go
+++ b/miner/remote_agent.go
@@ -37,7 +37,7 @@ type hashrate struct {
type RemoteAgent struct {
mu sync.Mutex
- quit chan struct{}
+ quitCh chan struct{}
workCh chan *Work
returnCh chan<- *Result
@@ -76,18 +76,16 @@ func (a *RemoteAgent) Start() {
if !atomic.CompareAndSwapInt32(&a.running, 0, 1) {
return
}
-
- a.quit = make(chan struct{})
+ a.quitCh = make(chan struct{})
a.workCh = make(chan *Work, 1)
- go a.maintainLoop()
+ go a.loop(a.workCh, a.quitCh)
}
func (a *RemoteAgent) Stop() {
if !atomic.CompareAndSwapInt32(&a.running, 1, 0) {
return
}
-
- close(a.quit)
+ close(a.quitCh)
close(a.workCh)
}
@@ -148,15 +146,20 @@ func (a *RemoteAgent) SubmitWork(nonce uint64, mixDigest, hash common.Hash) bool
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)
-out:
for {
select {
- case <-a.quit:
- break out
- case work := <-a.workCh:
+ case <-quitCh:
+ return
+ case work := <-workCh:
a.mu.Lock()
a.currentWork = work
a.mu.Unlock()
diff --git a/miner/unconfirmed.go b/miner/unconfirmed.go
new file mode 100644
index 000000000..86a30de35
--- /dev/null
+++ b/miner/unconfirmed.go
@@ -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)
+ }
+ }
+}
diff --git a/miner/unconfirmed_test.go b/miner/unconfirmed_test.go
new file mode 100644
index 000000000..456af1764
--- /dev/null
+++ b/miner/unconfirmed_test.go
@@ -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)
+ }
+}
diff --git a/miner/worker.go b/miner/worker.go
index edbd502c1..f29566c0a 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -55,26 +55,20 @@ type Agent interface {
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
// all of the current state information
type Work struct {
config *params.ChainConfig
signer types.Signer
- state *state.StateDB // apply state changes here
- ancestors *set.Set // ancestor set (used for checking uncle parent validity)
- family *set.Set // family set (used for checking uncle invalidity)
- uncles *set.Set // uncle set
- tcount int // tx count in cycle
- ownedAccounts *set.Set
- lowGasTxs types.Transactions
- failedTxs types.Transactions
- localMinedBlocks *uint64RingBuffer // the most recent block numbers that were mined locally (used to check block inclusion)
+ state *state.StateDB // apply state changes here
+ ancestors *set.Set // ancestor set (used for checking uncle parent validity)
+ family *set.Set // family set (used for checking uncle invalidity)
+ uncles *set.Set // uncle set
+ tcount int // tx count in cycle
+ ownedAccounts *set.Set
+ lowGasTxs types.Transactions
+ failedTxs types.Transactions
Block *types.Block // the new block
@@ -123,6 +117,8 @@ type worker struct {
txQueueMu sync.Mutex
txQueue map[common.Hash]*types.Transaction
+ unconfirmed *unconfirmedBlocks // set of locally mined blocks pending canonicalness confirmations
+
// atomic status counters
mining int32
atWork int32
@@ -144,6 +140,7 @@ func newWorker(config *params.ChainConfig, coinbase common.Address, eth Backend,
coinbase: coinbase,
txQueue: make(map[common.Hash]*types.Transaction),
agents: make(map[Agent]struct{}),
+ unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), 5),
fullValidation: false,
}
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
}
+func (self *worker) setExtra(extra []byte) {
+ self.mu.Lock()
+ defer self.mu.Unlock()
+ self.extra = extra
+}
+
func (self *worker) pending() (*types.Block, *state.StateDB) {
self.currentMu.Lock()
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() {
for {
mustCommitNewWork := true
@@ -349,17 +340,8 @@ func (self *worker) wait() {
}
}(block, work.state.Logs(), work.receipts)
}
-
- // check staleness and display confirmation
- 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)
+ // Insert the block into the set of pending ones to wait for confirmations
+ self.unconfirmed.Insert(block.NumberU64(), block.Hash())
if mustCommitNewWork {
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
work.tcount = 0
work.ownedAccounts = accountAddressesSet(accounts)
- if self.current != nil {
- work.localMinedBlocks = self.current.localMinedBlocks
- }
self.current = work
return nil
}
@@ -429,38 +408,6 @@ func (w *worker) setGasPrice(p *big.Int) {
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() {
self.mu.Lock()
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.
err := self.makeCurrent(parent, header)
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 {
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)
self.eth.TxPool().RemoveBatch(work.lowGasTxs)
@@ -561,7 +514,7 @@ func (self *worker) commitNewWork() {
// We only care about logging if we're actually mining.
if atomic.LoadInt32(&self.mining) == 1 {
glog.V(logger.Info).Infof("commit new work on block %v with %d txs & %d uncles. 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)
}
diff --git a/mobile/accounts.go b/mobile/accounts.go
index 41498b6f0..9a2937b6d 100644
--- a/mobile/accounts.go
+++ b/mobile/accounts.go
@@ -56,7 +56,7 @@ func (a *Accounts) Size() int {
}
// Get returns the account at the given index from the slice.
-func (a *Accounts) Get(index int) (*Account, error) {
+func (a *Accounts) Get(index int) (account *Account, _ error) {
if index < 0 || index >= len(a.accounts) {
return nil, errors.New("index out of bounds")
}
@@ -91,8 +91,8 @@ func NewAccountManager(keydir string, scryptN, scryptP int) *AccountManager {
}
// HasAddress reports whether a key with the given address is present.
-func (am *AccountManager) HasAddress(addr *Address) bool {
- return am.manager.HasAddress(addr.address)
+func (am *AccountManager) HasAddress(address *Address) bool {
+ return am.manager.HasAddress(address.address)
}
// GetAccounts returns all key files present in the directory.
@@ -102,32 +102,32 @@ func (am *AccountManager) GetAccounts() *Accounts {
// DeleteAccount deletes the key matched by account if the passphrase is correct.
// If a contains no filename, the address must match a unique key.
-func (am *AccountManager) DeleteAccount(a *Account, passphrase string) error {
+func (am *AccountManager) DeleteAccount(account *Account, passphrase string) error {
return am.manager.DeleteAccount(accounts.Account{
- Address: a.account.Address,
- File: a.account.File,
+ Address: account.account.Address,
+ File: account.account.File,
}, passphrase)
}
// Sign signs hash with an unlocked private key matching the given address.
-func (am *AccountManager) Sign(addr *Address, hash []byte) ([]byte, error) {
- return am.manager.Sign(addr.address, hash)
+func (am *AccountManager) Sign(address *Address, hash []byte) (signature []byte, _ error) {
+ return am.manager.Sign(address.address, hash)
}
// SignWithPassphrase signs hash if the private key matching the given address can be
// decrypted with the given passphrase.
-func (am *AccountManager) SignWithPassphrase(addr *Address, passphrase string, hash []byte) ([]byte, error) {
- return am.manager.SignWithPassphrase(addr.address, passphrase, hash)
+func (am *AccountManager) SignWithPassphrase(address *Address, passphrase string, hash []byte) (signature []byte, _ error) {
+ return am.manager.SignWithPassphrase(address.address, passphrase, hash)
}
// Unlock unlocks the given account indefinitely.
-func (am *AccountManager) Unlock(a *Account, passphrase string) error {
- return am.manager.TimedUnlock(a.account, passphrase, 0)
+func (am *AccountManager) Unlock(account *Account, passphrase string) error {
+ return am.manager.TimedUnlock(account.account, passphrase, 0)
}
// Lock removes the private key with the given address from memory.
-func (am *AccountManager) Lock(addr *Address) error {
- return am.manager.Lock(addr.address)
+func (am *AccountManager) Lock(address *Address) error {
+ return am.manager.Lock(address.address)
}
// TimedUnlock unlocks the given account with the passphrase. The account
@@ -152,27 +152,27 @@ func (am *AccountManager) NewAccount(passphrase string) (*Account, error) {
}
// ExportKey exports as a JSON key, encrypted with newPassphrase.
-func (am *AccountManager) ExportKey(a *Account, passphrase, newPassphrase string) ([]byte, error) {
- return am.manager.Export(a.account, passphrase, newPassphrase)
+func (am *AccountManager) ExportKey(account *Account, passphrase, newPassphrase string) (key []byte, _ error) {
+ return am.manager.Export(account.account, passphrase, newPassphrase)
}
// ImportKey stores the given encrypted JSON key into the key directory.
-func (am *AccountManager) ImportKey(keyJSON []byte, passphrase, newPassphrase string) (*Account, error) {
- account, err := am.manager.Import(keyJSON, passphrase, newPassphrase)
+func (am *AccountManager) ImportKey(keyJSON []byte, passphrase, newPassphrase string) (account *Account, _ error) {
+ acc, err := am.manager.Import(keyJSON, passphrase, newPassphrase)
if err != nil {
return nil, err
}
- return &Account{account}, nil
+ return &Account{acc}, nil
}
// Update changes the passphrase of an existing account.
-func (am *AccountManager) Update(a *Account, passphrase, newPassphrase string) error {
- return am.manager.Update(a.account, passphrase, newPassphrase)
+func (am *AccountManager) Update(account *Account, passphrase, newPassphrase string) error {
+ return am.manager.Update(account.account, passphrase, newPassphrase)
}
// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
// a key file in the key directory. The key file is encrypted with the same passphrase.
-func (am *AccountManager) ImportPreSaleKey(keyJSON []byte, passphrase string) (*Account, error) {
+func (am *AccountManager) ImportPreSaleKey(keyJSON []byte, passphrase string) (ccount *Account, _ error) {
account, err := am.manager.ImportPreSaleKey(keyJSON, passphrase)
if err != nil {
return nil, err
diff --git a/mobile/big.go b/mobile/big.go
index 6a358ba28..9a55836c1 100644
--- a/mobile/big.go
+++ b/mobile/big.go
@@ -78,7 +78,7 @@ func (bi *BigInts) Size() int {
}
// Get returns the bigint at the given index from the slice.
-func (bi *BigInts) Get(index int) (*BigInt, error) {
+func (bi *BigInts) Get(index int) (bigint *BigInt, _ error) {
if index < 0 || index >= len(bi.bigints) {
return nil, errors.New("index out of bounds")
}
diff --git a/mobile/bind.go b/mobile/bind.go
index 50adc6b0f..a25c37aca 100644
--- a/mobile/bind.go
+++ b/mobile/bind.go
@@ -31,15 +31,15 @@ import (
// Signer is an interaface defining the callback when a contract requires a
// method to sign the transaction before submission.
type Signer interface {
- Sign(*Address, *Transaction) (*Transaction, error)
+ Sign(*Address, *Transaction) (tx *Transaction, _ error)
}
type signer struct {
sign bind.SignerFn
}
-func (s *signer) Sign(addr *Address, tx *Transaction) (*Transaction, error) {
- sig, err := s.sign(types.HomesteadSigner{}, addr.address, tx.tx)
+func (s *signer) Sign(addr *Address, unsignedTx *Transaction) (signedTx *Transaction, _ error) {
+ sig, err := s.sign(types.HomesteadSigner{}, addr.address, unsignedTx.tx)
if err != nil {
return nil, err
}
@@ -113,7 +113,7 @@ type BoundContract struct {
// DeployContract deploys a contract onto the Ethereum blockchain and binds the
// deployment address with a wrapper.
-func DeployContract(opts *TransactOpts, abiJSON string, bytecode []byte, client *EthereumClient, args *Interfaces) (*BoundContract, error) {
+func DeployContract(opts *TransactOpts, abiJSON string, bytecode []byte, client *EthereumClient, args *Interfaces) (contract *BoundContract, _ error) {
// Convert all the deployment parameters to Go types
params := make([]interface{}, len(args.objects))
for i, obj := range args.objects {
@@ -137,7 +137,7 @@ func DeployContract(opts *TransactOpts, abiJSON string, bytecode []byte, client
// BindContract creates a low level contract interface through which calls and
// transactions may be made through.
-func BindContract(address *Address, abiJSON string, client *EthereumClient) (*BoundContract, error) {
+func BindContract(address *Address, abiJSON string, client *EthereumClient) (contract *BoundContract, _ error) {
parsed, err := abi.JSON(strings.NewReader(abiJSON))
if err != nil {
return nil, err
@@ -179,24 +179,24 @@ func (c *BoundContract) Call(opts *CallOpts, out *Interfaces, method string, arg
}
// Transact invokes the (paid) contract method with params as input values.
-func (c *BoundContract) Transact(opts *TransactOpts, method string, args *Interfaces) (*Transaction, error) {
+func (c *BoundContract) Transact(opts *TransactOpts, method string, args *Interfaces) (tx *Transaction, _ error) {
params := make([]interface{}, len(args.objects))
for i, obj := range args.objects {
params[i] = obj
}
- tx, err := c.contract.Transact(&opts.opts, method, params)
+ rawTx, err := c.contract.Transact(&opts.opts, method, params)
if err != nil {
return nil, err
}
- return &Transaction{tx}, nil
+ return &Transaction{rawTx}, nil
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
-func (c *BoundContract) Transfer(opts *TransactOpts) (*Transaction, error) {
- tx, err := c.contract.Transfer(&opts.opts)
+func (c *BoundContract) Transfer(opts *TransactOpts) (tx *Transaction, _ error) {
+ rawTx, err := c.contract.Transfer(&opts.opts)
if err != nil {
return nil, err
}
- return &Transaction{tx}, nil
+ return &Transaction{rawTx}, nil
}
diff --git a/mobile/common.go b/mobile/common.go
index ab1810bf1..779f22b4e 100644
--- a/mobile/common.go
+++ b/mobile/common.go
@@ -33,18 +33,18 @@ type Hash struct {
}
// NewHashFromBytes converts a slice of bytes to a hash value.
-func NewHashFromBytes(hash []byte) (*Hash, error) {
+func NewHashFromBytes(binary []byte) (hash *Hash, _ error) {
h := new(Hash)
- if err := h.SetBytes(hash); err != nil {
+ if err := h.SetBytes(binary); err != nil {
return nil, err
}
return h, nil
}
// NewHashFromHex converts a hex string to a hash value.
-func NewHashFromHex(hash string) (*Hash, error) {
+func NewHashFromHex(hex string) (hash *Hash, _ error) {
h := new(Hash)
- if err := h.SetHex(hash); err != nil {
+ if err := h.SetHex(hex); err != nil {
return nil, err
}
return h, nil
@@ -95,7 +95,7 @@ func (h *Hashes) Size() int {
}
// Get returns the hash at the given index from the slice.
-func (h *Hashes) Get(index int) (*Hash, error) {
+func (h *Hashes) Get(index int) (hash *Hash, _ error) {
if index < 0 || index >= len(h.hashes) {
return nil, errors.New("index out of bounds")
}
@@ -108,18 +108,18 @@ type Address struct {
}
// NewAddressFromBytes converts a slice of bytes to a hash value.
-func NewAddressFromBytes(address []byte) (*Address, error) {
+func NewAddressFromBytes(binary []byte) (address *Address, _ error) {
a := new(Address)
- if err := a.SetBytes(address); err != nil {
+ if err := a.SetBytes(binary); err != nil {
return nil, err
}
return a, nil
}
// NewAddressFromHex converts a hex string to a address value.
-func NewAddressFromHex(address string) (*Address, error) {
+func NewAddressFromHex(hex string) (address *Address, _ error) {
a := new(Address)
- if err := a.SetHex(address); err != nil {
+ if err := a.SetHex(hex); err != nil {
return nil, err
}
return a, nil
@@ -170,7 +170,7 @@ func (a *Addresses) Size() int {
}
// Get returns the address at the given index from the slice.
-func (a *Addresses) Get(index int) (*Address, error) {
+func (a *Addresses) Get(index int) (address *Address, _ error) {
if index < 0 || index >= len(a.addresses) {
return nil, errors.New("index out of bounds")
}
diff --git a/mobile/discover.go b/mobile/discover.go
index 9df2d04c3..9b3c93ccd 100644
--- a/mobile/discover.go
+++ b/mobile/discover.go
@@ -53,7 +53,7 @@ type Enode struct {
// and UDP discovery port 30301.
//
// enode://<hex node id>@10.3.58.6:30303?discport=30301
-func NewEnode(rawurl string) (*Enode, error) {
+func NewEnode(rawurl string) (enode *Enode, _ error) {
node, err := discv5.ParseNode(rawurl)
if err != nil {
return nil, err
@@ -82,7 +82,7 @@ func (e *Enodes) Size() int {
}
// Get returns the enode at the given index from the slice.
-func (e *Enodes) Get(index int) (*Enode, error) {
+func (e *Enodes) Get(index int) (enode *Enode, _ error) {
if index < 0 || index >= len(e.nodes) {
return nil, errors.New("index out of bounds")
}
diff --git a/mobile/doc.go b/mobile/doc.go
index 50cc7f4f8..89be470cc 100644
--- a/mobile/doc.go
+++ b/mobile/doc.go
@@ -51,6 +51,10 @@
// should not be provided to limit the remote code complexity. Arrays should be
// avoided as much as possible since they complicate bounds checking.
//
+// If a method has multiple return values (e.g. some return + an error), those
+// are generated as output arguments in ObjC. To avoid weird generated names like
+// ret_0 for them, please always assign names to output variables if tuples.
+//
// Note, a panic *cannot* cross over language boundaries, instead will result in
// an undebuggable SEGFAULT in the process. For error handling only ever use error
// returns, which may be the only or the second return.
diff --git a/mobile/ethclient.go b/mobile/ethclient.go
index 668d65e32..36a15aa47 100644
--- a/mobile/ethclient.go
+++ b/mobile/ethclient.go
@@ -32,79 +32,80 @@ type EthereumClient struct {
}
// NewEthereumClient connects a client to the given URL.
-func NewEthereumClient(rawurl string) (*EthereumClient, error) {
- client, err := ethclient.Dial(rawurl)
- return &EthereumClient{client}, err
+func NewEthereumClient(rawurl string) (client *EthereumClient, _ error) {
+ rawClient, err := ethclient.Dial(rawurl)
+ return &EthereumClient{rawClient}, err
}
// GetBlockByHash returns the given full block.
-func (ec *EthereumClient) GetBlockByHash(ctx *Context, hash *Hash) (*Block, error) {
- block, err := ec.client.BlockByHash(ctx.context, hash.hash)
- return &Block{block}, err
+func (ec *EthereumClient) GetBlockByHash(ctx *Context, hash *Hash) (block *Block, _ error) {
+ rawBlock, err := ec.client.BlockByHash(ctx.context, hash.hash)
+ return &Block{rawBlock}, err
}
// GetBlockByNumber returns a block from the current canonical chain. If number is <0, the
// latest known block is returned.
-func (ec *EthereumClient) GetBlockByNumber(ctx *Context, number int64) (*Block, error) {
+func (ec *EthereumClient) GetBlockByNumber(ctx *Context, number int64) (block *Block, _ error) {
if number < 0 {
- block, err := ec.client.BlockByNumber(ctx.context, nil)
- return &Block{block}, err
+ rawBlock, err := ec.client.BlockByNumber(ctx.context, nil)
+ return &Block{rawBlock}, err
}
- block, err := ec.client.BlockByNumber(ctx.context, big.NewInt(number))
- return &Block{block}, err
+ rawBlock, err := ec.client.BlockByNumber(ctx.context, big.NewInt(number))
+ return &Block{rawBlock}, err
}
// GetHeaderByHash returns the block header with the given hash.
-func (ec *EthereumClient) GetHeaderByHash(ctx *Context, hash *Hash) (*Header, error) {
- header, err := ec.client.HeaderByHash(ctx.context, hash.hash)
- return &Header{header}, err
+func (ec *EthereumClient) GetHeaderByHash(ctx *Context, hash *Hash) (header *Header, _ error) {
+ rawHeader, err := ec.client.HeaderByHash(ctx.context, hash.hash)
+ return &Header{rawHeader}, err
}
// GetHeaderByNumber returns a block header from the current canonical chain. If number is <0,
// the latest known header is returned.
-func (ec *EthereumClient) GetHeaderByNumber(ctx *Context, number int64) (*Header, error) {
+func (ec *EthereumClient) GetHeaderByNumber(ctx *Context, number int64) (header *Header, _ error) {
if number < 0 {
- header, err := ec.client.HeaderByNumber(ctx.context, nil)
- return &Header{header}, err
+ rawHeader, err := ec.client.HeaderByNumber(ctx.context, nil)
+ return &Header{rawHeader}, err
}
- header, err := ec.client.HeaderByNumber(ctx.context, big.NewInt(number))
- return &Header{header}, err
+ rawHeader, err := ec.client.HeaderByNumber(ctx.context, big.NewInt(number))
+ return &Header{rawHeader}, err
}
// GetTransactionByHash returns the transaction with the given hash.
-func (ec *EthereumClient) GetTransactionByHash(ctx *Context, hash *Hash) (*Transaction, error) {
- tx, err := ec.client.TransactionByHash(ctx.context, hash.hash)
- return &Transaction{tx}, err
+func (ec *EthereumClient) GetTransactionByHash(ctx *Context, hash *Hash) (tx *Transaction, _ error) {
+ // TODO(karalabe): handle isPending
+ rawTx, _, err := ec.client.TransactionByHash(ctx.context, hash.hash)
+ return &Transaction{rawTx}, err
}
// GetTransactionCount returns the total number of transactions in the given block.
-func (ec *EthereumClient) GetTransactionCount(ctx *Context, hash *Hash) (int, error) {
- count, err := ec.client.TransactionCount(ctx.context, hash.hash)
- return int(count), err
+func (ec *EthereumClient) GetTransactionCount(ctx *Context, hash *Hash) (count int, _ error) {
+ rawCount, err := ec.client.TransactionCount(ctx.context, hash.hash)
+ return int(rawCount), err
}
// GetTransactionInBlock returns a single transaction at index in the given block.
-func (ec *EthereumClient) GetTransactionInBlock(ctx *Context, hash *Hash, index int) (*Transaction, error) {
- tx, err := ec.client.TransactionInBlock(ctx.context, hash.hash, uint(index))
- return &Transaction{tx}, err
+func (ec *EthereumClient) GetTransactionInBlock(ctx *Context, hash *Hash, index int) (tx *Transaction, _ error) {
+ rawTx, err := ec.client.TransactionInBlock(ctx.context, hash.hash, uint(index))
+ return &Transaction{rawTx}, err
}
// GetTransactionReceipt returns the receipt of a transaction by transaction hash.
// Note that the receipt is not available for pending transactions.
-func (ec *EthereumClient) GetTransactionReceipt(ctx *Context, hash *Hash) (*Receipt, error) {
- receipt, err := ec.client.TransactionReceipt(ctx.context, hash.hash)
- return &Receipt{receipt}, err
+func (ec *EthereumClient) GetTransactionReceipt(ctx *Context, hash *Hash) (receipt *Receipt, _ error) {
+ rawReceipt, err := ec.client.TransactionReceipt(ctx.context, hash.hash)
+ return &Receipt{rawReceipt}, err
}
// SyncProgress retrieves the current progress of the sync algorithm. If there's
// no sync currently running, it returns nil.
-func (ec *EthereumClient) SyncProgress(ctx *Context) (*SyncProgress, error) {
- progress, err := ec.client.SyncProgress(ctx.context)
- if progress == nil {
+func (ec *EthereumClient) SyncProgress(ctx *Context) (progress *SyncProgress, _ error) {
+ rawProgress, err := ec.client.SyncProgress(ctx.context)
+ if rawProgress == nil {
return nil, err
}
- return &SyncProgress{*progress}, err
+ return &SyncProgress{*rawProgress}, err
}
// NewHeadHandler is a client-side subscription callback to invoke on events and
@@ -116,10 +117,10 @@ type NewHeadHandler interface {
// SubscribeNewHead subscribes to notifications about the current blockchain head
// on the given channel.
-func (ec *EthereumClient) SubscribeNewHead(ctx *Context, handler NewHeadHandler, buffer int) (*Subscription, error) {
+func (ec *EthereumClient) SubscribeNewHead(ctx *Context, handler NewHeadHandler, buffer int) (sub *Subscription, _ error) {
// Subscribe to the event internally
ch := make(chan *types.Header, buffer)
- sub, err := ec.client.SubscribeNewHead(ctx.context, ch)
+ rawSub, err := ec.client.SubscribeNewHead(ctx.context, ch)
if err != nil {
return nil, err
}
@@ -130,31 +131,31 @@ func (ec *EthereumClient) SubscribeNewHead(ctx *Context, handler NewHeadHandler,
case header := <-ch:
handler.OnNewHead(&Header{header})
- case err := <-sub.Err():
+ case err := <-rawSub.Err():
handler.OnError(err.Error())
return
}
}
}()
- return &Subscription{sub}, nil
+ return &Subscription{rawSub}, nil
}
// State Access
// GetBalanceAt returns the wei balance of the given account.
// The block number can be <0, in which case the balance is taken from the latest known block.
-func (ec *EthereumClient) GetBalanceAt(ctx *Context, account *Address, number int64) (*BigInt, error) {
+func (ec *EthereumClient) GetBalanceAt(ctx *Context, account *Address, number int64) (balance *BigInt, _ error) {
if number < 0 {
- balance, err := ec.client.BalanceAt(ctx.context, account.address, nil)
- return &BigInt{balance}, err
+ rawBalance, err := ec.client.BalanceAt(ctx.context, account.address, nil)
+ return &BigInt{rawBalance}, err
}
- balance, err := ec.client.BalanceAt(ctx.context, account.address, big.NewInt(number))
- return &BigInt{balance}, err
+ rawBalance, err := ec.client.BalanceAt(ctx.context, account.address, big.NewInt(number))
+ return &BigInt{rawBalance}, err
}
// GetStorageAt returns the value of key in the contract storage of the given account.
// The block number can be <0, in which case the value is taken from the latest known block.
-func (ec *EthereumClient) GetStorageAt(ctx *Context, account *Address, key *Hash, number int64) ([]byte, error) {
+func (ec *EthereumClient) GetStorageAt(ctx *Context, account *Address, key *Hash, number int64) (storage []byte, _ error) {
if number < 0 {
return ec.client.StorageAt(ctx.context, account.address, key.hash, nil)
}
@@ -163,7 +164,7 @@ func (ec *EthereumClient) GetStorageAt(ctx *Context, account *Address, key *Hash
// GetCodeAt returns the contract code of the given account.
// The block number can be <0, in which case the code is taken from the latest known block.
-func (ec *EthereumClient) GetCodeAt(ctx *Context, account *Address, number int64) ([]byte, error) {
+func (ec *EthereumClient) GetCodeAt(ctx *Context, account *Address, number int64) (code []byte, _ error) {
if number < 0 {
return ec.client.CodeAt(ctx.context, account.address, nil)
}
@@ -172,26 +173,26 @@ func (ec *EthereumClient) GetCodeAt(ctx *Context, account *Address, number int64
// GetNonceAt returns the account nonce of the given account.
// The block number can be <0, in which case the nonce is taken from the latest known block.
-func (ec *EthereumClient) GetNonceAt(ctx *Context, account *Address, number int64) (int64, error) {
+func (ec *EthereumClient) GetNonceAt(ctx *Context, account *Address, number int64) (nonce int64, _ error) {
if number < 0 {
- nonce, err := ec.client.NonceAt(ctx.context, account.address, nil)
- return int64(nonce), err
+ rawNonce, err := ec.client.NonceAt(ctx.context, account.address, nil)
+ return int64(rawNonce), err
}
- nonce, err := ec.client.NonceAt(ctx.context, account.address, big.NewInt(number))
- return int64(nonce), err
+ rawNonce, err := ec.client.NonceAt(ctx.context, account.address, big.NewInt(number))
+ return int64(rawNonce), err
}
// Filters
// FilterLogs executes a filter query.
-func (ec *EthereumClient) FilterLogs(ctx *Context, query *FilterQuery) (*Logs, error) {
- logs, err := ec.client.FilterLogs(ctx.context, query.query)
+func (ec *EthereumClient) FilterLogs(ctx *Context, query *FilterQuery) (logs *Logs, _ error) {
+ rawLogs, err := ec.client.FilterLogs(ctx.context, query.query)
if err != nil {
return nil, err
}
// Temp hack due to vm.Logs being []*vm.Log
- res := make(vm.Logs, len(logs))
- for i, log := range logs {
+ res := make(vm.Logs, len(rawLogs))
+ for i, log := range rawLogs {
res[i] = &log
}
return &Logs{res}, nil
@@ -205,10 +206,10 @@ type FilterLogsHandler interface {
}
// SubscribeFilterLogs subscribes to the results of a streaming filter query.
-func (ec *EthereumClient) SubscribeFilterLogs(ctx *Context, query *FilterQuery, handler FilterLogsHandler, buffer int) (*Subscription, error) {
+func (ec *EthereumClient) SubscribeFilterLogs(ctx *Context, query *FilterQuery, handler FilterLogsHandler, buffer int) (sub *Subscription, _ error) {
// Subscribe to the event internally
ch := make(chan vm.Log, buffer)
- sub, err := ec.client.SubscribeFilterLogs(ctx.context, query.query, ch)
+ rawSub, err := ec.client.SubscribeFilterLogs(ctx.context, query.query, ch)
if err != nil {
return nil, err
}
@@ -219,44 +220,44 @@ func (ec *EthereumClient) SubscribeFilterLogs(ctx *Context, query *FilterQuery,
case log := <-ch:
handler.OnFilterLogs(&Log{&log})
- case err := <-sub.Err():
+ case err := <-rawSub.Err():
handler.OnError(err.Error())
return
}
}
}()
- return &Subscription{sub}, nil
+ return &Subscription{rawSub}, nil
}
// Pending State
// GetPendingBalanceAt returns the wei balance of the given account in the pending state.
-func (ec *EthereumClient) GetPendingBalanceAt(ctx *Context, account *Address) (*BigInt, error) {
- balance, err := ec.client.PendingBalanceAt(ctx.context, account.address)
- return &BigInt{balance}, err
+func (ec *EthereumClient) GetPendingBalanceAt(ctx *Context, account *Address) (balance *BigInt, _ error) {
+ rawBalance, err := ec.client.PendingBalanceAt(ctx.context, account.address)
+ return &BigInt{rawBalance}, err
}
// GetPendingStorageAt returns the value of key in the contract storage of the given account in the pending state.
-func (ec *EthereumClient) GetPendingStorageAt(ctx *Context, account *Address, key *Hash) ([]byte, error) {
+func (ec *EthereumClient) GetPendingStorageAt(ctx *Context, account *Address, key *Hash) (storage []byte, _ error) {
return ec.client.PendingStorageAt(ctx.context, account.address, key.hash)
}
// GetPendingCodeAt returns the contract code of the given account in the pending state.
-func (ec *EthereumClient) GetPendingCodeAt(ctx *Context, account *Address) ([]byte, error) {
+func (ec *EthereumClient) GetPendingCodeAt(ctx *Context, account *Address) (code []byte, _ error) {
return ec.client.PendingCodeAt(ctx.context, account.address)
}
// GetPendingNonceAt returns the account nonce of the given account in the pending state.
// This is the nonce that should be used for the next transaction.
-func (ec *EthereumClient) GetPendingNonceAt(ctx *Context, account *Address) (int64, error) {
- nonce, err := ec.client.PendingNonceAt(ctx.context, account.address)
- return int64(nonce), err
+func (ec *EthereumClient) GetPendingNonceAt(ctx *Context, account *Address) (nonce int64, _ error) {
+ rawNonce, err := ec.client.PendingNonceAt(ctx.context, account.address)
+ return int64(rawNonce), err
}
// GetPendingTransactionCount returns the total number of transactions in the pending state.
-func (ec *EthereumClient) GetPendingTransactionCount(ctx *Context) (int, error) {
- count, err := ec.client.PendingTransactionCount(ctx.context)
- return int(count), err
+func (ec *EthereumClient) GetPendingTransactionCount(ctx *Context) (count int, _ error) {
+ rawCount, err := ec.client.PendingTransactionCount(ctx.context)
+ return int(rawCount), err
}
// Contract Calling
@@ -267,7 +268,7 @@ func (ec *EthereumClient) GetPendingTransactionCount(ctx *Context) (int, error)
// blockNumber selects the block height at which the call runs. It can be <0, in which
// case the code is taken from the latest known block. Note that state from very old
// blocks might not be available.
-func (ec *EthereumClient) CallContract(ctx *Context, msg *CallMsg, number int64) ([]byte, error) {
+func (ec *EthereumClient) CallContract(ctx *Context, msg *CallMsg, number int64) (output []byte, _ error) {
if number < 0 {
return ec.client.CallContract(ctx.context, msg.msg, nil)
}
@@ -276,24 +277,24 @@ func (ec *EthereumClient) CallContract(ctx *Context, msg *CallMsg, number int64)
// PendingCallContract executes a message call transaction using the EVM.
// The state seen by the contract call is the pending state.
-func (ec *EthereumClient) PendingCallContract(ctx *Context, msg *CallMsg) ([]byte, error) {
+func (ec *EthereumClient) PendingCallContract(ctx *Context, msg *CallMsg) (output []byte, _ error) {
return ec.client.PendingCallContract(ctx.context, msg.msg)
}
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction.
-func (ec *EthereumClient) SuggestGasPrice(ctx *Context) (*BigInt, error) {
- price, err := ec.client.SuggestGasPrice(ctx.context)
- return &BigInt{price}, err
+func (ec *EthereumClient) SuggestGasPrice(ctx *Context) (price *BigInt, _ error) {
+ rawPrice, err := ec.client.SuggestGasPrice(ctx.context)
+ return &BigInt{rawPrice}, err
}
// EstimateGas tries to estimate the gas needed to execute a specific transaction based on
// the current pending state of the backend blockchain. There is no guarantee that this is
// the true gas limit requirement as other transactions may be added or removed by miners,
// but it should provide a basis for setting a reasonable default.
-func (ec *EthereumClient) EstimateGas(ctx *Context, msg *CallMsg) (*BigInt, error) {
- price, err := ec.client.EstimateGas(ctx.context, msg.msg)
- return &BigInt{price}, err
+func (ec *EthereumClient) EstimateGas(ctx *Context, msg *CallMsg) (gas *BigInt, _ error) {
+ rawGas, err := ec.client.EstimateGas(ctx.context, msg.msg)
+ return &BigInt{rawGas}, err
}
// SendTransaction injects a signed transaction into the pending pool for execution.
diff --git a/mobile/ethereum.go b/mobile/ethereum.go
index 6e8046ac9..94f707a87 100644
--- a/mobile/ethereum.go
+++ b/mobile/ethereum.go
@@ -93,7 +93,7 @@ func (t *Topics) Size() int {
}
// Get returns the topic list at the given index from the slice.
-func (t *Topics) Get(index int) (*Hashes, error) {
+func (t *Topics) Get(index int) (hashes *Hashes, _ error) {
if index < 0 || index >= len(t.topics) {
return nil, errors.New("index out of bounds")
}
diff --git a/mobile/geth.go b/mobile/geth.go
index 7ea4b2f65..af0054cdc 100644
--- a/mobile/geth.go
+++ b/mobile/geth.go
@@ -99,7 +99,7 @@ type Node struct {
}
// NewNode creates and configures a new Geth node.
-func NewNode(datadir string, config *NodeConfig) (*Node, error) {
+func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
// If no or partial configurations were specified, use defaults
if config == nil {
config = NewNodeConfig()
@@ -124,7 +124,7 @@ func NewNode(datadir string, config *NodeConfig) (*Node, error) {
NAT: nat.Any(),
MaxPeers: config.MaxPeers,
}
- stack, err := node.New(nodeConf)
+ rawStack, err := node.New(nodeConf)
if err != nil {
return nil, err
}
@@ -153,14 +153,14 @@ func NewNode(datadir string, config *NodeConfig) (*Node, error) {
GpobaseStepUp: 100,
GpobaseCorrectionFactor: 110,
}
- if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
+ if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return les.New(ctx, ethConf)
}); err != nil {
return nil, fmt.Errorf("ethereum init: %v", err)
}
// If netstats reporting is requested, do it
if config.EthereumNetStats != "" {
- if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
+ if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
var lesServ *les.LightEthereum
ctx.Service(&lesServ)
@@ -172,11 +172,11 @@ func NewNode(datadir string, config *NodeConfig) (*Node, error) {
}
// Register the Whisper protocol if requested
if config.WhisperEnabled {
- if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisperv2.New(), nil }); err != nil {
+ if err := rawStack.Register(func(*node.ServiceContext) (node.Service, error) { return whisperv2.New(), nil }); err != nil {
return nil, fmt.Errorf("whisper init: %v", err)
}
}
- return &Node{stack}, nil
+ return &Node{rawStack}, nil
}
// Start creates a live P2P node and starts running it.
@@ -191,7 +191,7 @@ func (n *Node) Stop() error {
}
// GetEthereumClient retrieves a client to access the Ethereum subsystem.
-func (n *Node) GetEthereumClient() (*EthereumClient, error) {
+func (n *Node) GetEthereumClient() (client *EthereumClient, _ error) {
rpc, err := n.node.Attach()
if err != nil {
return nil, err
diff --git a/mobile/interface.go b/mobile/interface.go
index b585b8642..10eac5f72 100644
--- a/mobile/interface.go
+++ b/mobile/interface.go
@@ -131,7 +131,7 @@ func (i *Interfaces) Size() int {
}
// Get returns the bigint at the given index from the slice.
-func (i *Interfaces) Get(index int) (*Interface, error) {
+func (i *Interfaces) Get(index int) (iface *Interface, _ error) {
if index < 0 || index >= len(i.objects) {
return nil, errors.New("index out of bounds")
}
diff --git a/mobile/p2p.go b/mobile/p2p.go
index 97ae626dc..e717d4004 100644
--- a/mobile/p2p.go
+++ b/mobile/p2p.go
@@ -66,7 +66,7 @@ func (pi *PeerInfos) Size() int {
}
// Get returns the peer info at the given index from the slice.
-func (pi *PeerInfos) Get(index int) (*PeerInfo, error) {
+func (pi *PeerInfos) Get(index int) (info *PeerInfo, _ error) {
if index < 0 || index >= len(pi.infos) {
return nil, errors.New("index out of bounds")
}
diff --git a/mobile/primitives.go b/mobile/primitives.go
index 28f402d4f..54b25df59 100644
--- a/mobile/primitives.go
+++ b/mobile/primitives.go
@@ -32,7 +32,7 @@ func (s *Strings) Size() int {
}
// Get returns the string at the given index from the slice.
-func (s *Strings) Get(index int) (string, error) {
+func (s *Strings) Get(index int) (str string, _ error) {
if index < 0 || index >= len(s.strs) {
return "", errors.New("index out of bounds")
}
diff --git a/mobile/types.go b/mobile/types.go
index bb5ccc625..9ea70ea9b 100644
--- a/mobile/types.go
+++ b/mobile/types.go
@@ -89,7 +89,7 @@ func (h *Headers) Size() int {
}
// Get returns the header at the given index from the slice.
-func (h *Headers) Get(index int) (*Header, error) {
+func (h *Headers) Get(index int) (header *Header, _ error) {
if index < 0 || index >= len(h.headers) {
return nil, errors.New("index out of bounds")
}
@@ -142,7 +142,7 @@ func (tx *Transaction) GetHash() *Hash { return &Hash{tx.tx.Hash()} }
func (tx *Transaction) GetSigHash() *Hash { return &Hash{tx.tx.SigHash(types.HomesteadSigner{})} }
func (tx *Transaction) GetCost() *BigInt { return &BigInt{tx.tx.Cost()} }
-func (tx *Transaction) GetFrom() (*Address, error) {
+func (tx *Transaction) GetFrom() (address *Address, _ error) {
from, err := types.Sender(types.HomesteadSigner{}, tx.tx)
return &Address{from}, err
}
@@ -154,25 +154,25 @@ func (tx *Transaction) GetTo() *Address {
return nil
}
-func (tx *Transaction) WithSignature(sig []byte) (*Transaction, error) {
- t, err := tx.tx.WithSignature(types.HomesteadSigner{}, sig)
- return &Transaction{t}, err
+func (tx *Transaction) WithSignature(sig []byte) (signedTx *Transaction, _ error) {
+ rawTx, err := tx.tx.WithSignature(types.HomesteadSigner{}, sig)
+ return &Transaction{rawTx}, err
}
// Transactions represents a slice of transactions.
type Transactions struct{ txs types.Transactions }
// Size returns the number of transactions in the slice.
-func (t *Transactions) Size() int {
- return len(t.txs)
+func (txs *Transactions) Size() int {
+ return len(txs.txs)
}
// Get returns the transaction at the given index from the slice.
-func (t *Transactions) Get(index int) (*Transaction, error) {
- if index < 0 || index >= len(t.txs) {
+func (txs *Transactions) Get(index int) (tx *Transaction, _ error) {
+ if index < 0 || index >= len(txs.txs) {
return nil, errors.New("index out of bounds")
}
- return &Transaction{t.txs[index]}, nil
+ return &Transaction{txs.txs[index]}, nil
}
// Receipt represents the results of a transaction.
diff --git a/mobile/vm.go b/mobile/vm.go
index a68917ca6..cb098d390 100644
--- a/mobile/vm.go
+++ b/mobile/vm.go
@@ -48,7 +48,7 @@ func (l *Logs) Size() int {
}
// Get returns the log at the given index from the slice.
-func (l *Logs) Get(index int) (*Log, error) {
+func (l *Logs) Get(index int) (log *Log, _ error) {
if index < 0 || index >= len(l.logs) {
return nil, errors.New("index out of bounds")
}
diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go
index 53cfac6f9..8bca37ffe 100644
--- a/p2p/discover/udp_test.go
+++ b/p2p/discover/udp_test.go
@@ -374,7 +374,7 @@ func TestUDP_successfulPing(t *testing.T) {
if 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)
}
if int(n.UDP) != test.remoteaddr.Port {
diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go
index d1c48904e..74d485836 100644
--- a/p2p/discv5/net.go
+++ b/p2p/discv5/net.go
@@ -126,8 +126,15 @@ type topicRegisterReq struct {
}
type topicSearchReq struct {
- topic Topic
- found chan<- string
+ topic Topic
+ found chan<- *Node
+ lookup chan<- bool
+ delay time.Duration
+}
+
+type topicSearchResult struct {
+ target lookupInfo
+ nodes []*Node
}
type timeoutEvent struct {
@@ -263,16 +270,23 @@ func (net *Network) lookup(target common.Hash, stopOnMatch bool) []*Node {
break
}
// Wait for the next reply.
- for _, n := range <-reply {
- if n != nil && !seen[n.ID] {
- seen[n.ID] = true
- result.push(n, bucketSize)
- if stopOnMatch && n.sha == target {
- return result.entries
+ select {
+ case nodes := <-reply:
+ for _, n := range nodes {
+ if n != nil && !seen[n.ID] {
+ seen[n.ID] = true
+ result.push(n, bucketSize)
+ if stopOnMatch && n.sha == target {
+ return result.entries
+ }
}
}
+ pendingQueries--
+ case <-time.After(respTimeout):
+ // forget all pending requests, start new ones
+ pendingQueries = 0
+ reply = make(chan []*Node, alpha)
}
- pendingQueries--
}
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) {
- select {
- case net.topicSearchReq <- topicSearchReq{topic, found}:
- case <-net.closed:
- return
- }
- select {
- case <-net.closed:
- case <-stop:
+func (net *Network) SearchTopic(topic Topic, setPeriod <-chan time.Duration, found chan<- *Node, lookup chan<- bool) {
+ for {
select {
- case net.topicSearchReq <- topicSearchReq{topic, nil}:
case <-net.closed:
+ return
+ case delay, ok := <-setPeriod:
+ select {
+ case net.topicSearchReq <- topicSearchReq{topic: topic, found: found, lookup: lookup, delay: delay}:
+ case <-net.closed:
+ return
+ }
+ if !ok {
+ return
+ }
}
}
}
@@ -347,6 +363,13 @@ func (net *Network) reqTableOp(f func()) (called bool) {
// TODO: external address handling.
+type topicSearchInfo struct {
+ lookupChn chan<- bool
+ period time.Duration
+}
+
+const maxSearchCount = 5
+
func (net *Network) loop() {
var (
refreshTimer = time.NewTicker(autoRefreshInterval)
@@ -385,10 +408,12 @@ func (net *Network) loop() {
topicRegisterLookupTarget lookupInfo
topicRegisterLookupDone chan []*Node
topicRegisterLookupTick = time.NewTimer(0)
- topicSearchLookupTarget lookupInfo
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
statsDump := time.NewTicker(10 * time.Second)
@@ -504,21 +529,52 @@ loop:
case req := <-net.topicSearchReq:
if refreshDone == nil {
debugLog("<-net.topicSearchReq")
- if req.found == nil {
- net.ticketStore.removeSearchTopic(req.topic)
+ info, ok := searchInfo[req.topic]
+ if ok {
+ if req.delay == time.Duration(0) {
+ delete(searchInfo, req.topic)
+ net.ticketStore.removeSearchTopic(req.topic)
+ } else {
+ info.period = req.delay
+ searchInfo[req.topic] = info
+ }
continue
}
- net.ticketStore.addSearchTopic(req.topic, req.found)
- if (topicSearchLookupTarget.target == common.Hash{}) {
- topicSearchLookupDone <- nil
+ 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)
+ topicSearch <- req.topic
}
} else {
searchReqWhenRefreshDone = append(searchReqWhenRefreshDone, req)
}
- case nodes := <-topicSearchLookupDone:
- debugLog("<-topicSearchLookupDone")
- net.ticketStore.searchLookupDone(topicSearchLookupTarget, nodes, func(n *Node) []byte {
+ case topic := <-topicSearch:
+ if activeSearchCount < maxSearchCount {
+ 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())
return n.pingEcho
}, func(n *Node, topic Topic) []byte {
@@ -531,11 +587,6 @@ loop:
return nil
}
})
- topicSearchLookupTarget = net.ticketStore.nextSearchLookup()
- target := topicSearchLookupTarget.target
- if (target != common.Hash{}) {
- go func() { topicSearchLookupDone <- net.lookup(target, false) }()
- }
case <-statsDump.C:
debugLog("<-statsDump.C")
@@ -708,7 +759,7 @@ func (net *Network) internNodeFromNeighbours(sender *net.UDPAddr, rn rpcNode) (n
}
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)
}
return n, err
diff --git a/p2p/discv5/node.go b/p2p/discv5/node.go
index b6b6f149d..b2025ebcb 100644
--- a/p2p/discv5/node.go
+++ b/p2p/discv5/node.go
@@ -17,7 +17,6 @@
package discv5
import (
- "bytes"
"crypto/ecdsa"
"crypto/elliptic"
"encoding/hex"
@@ -81,7 +80,7 @@ func (n *Node) addrEqual(a *net.UDPAddr) bool {
if ipv4 := a.IP.To4(); ipv4 != nil {
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.
diff --git a/p2p/discv5/ticket.go b/p2p/discv5/ticket.go
index 202504314..752fdc9b4 100644
--- a/p2p/discv5/ticket.go
+++ b/p2p/discv5/ticket.go
@@ -138,16 +138,12 @@ type ticketStore struct {
nextTicketReg mclock.AbsTime
searchTopicMap map[Topic]searchTopic
- searchTopicList []Topic
- searchTopicPtr int
nextTopicQueryCleanup mclock.AbsTime
queriesSent map[*Node]map[common.Hash]sentQuery
- radiusLookupCnt int
}
type searchTopic struct {
- foundChn chan<- string
- listIdx int
+ foundChn chan<- *Node
}
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)
if s.searchTopicMap[t].foundChn == nil {
- s.searchTopicList = append(s.searchTopicList, t)
- s.searchTopicMap[t] = searchTopic{foundChn: foundChn, listIdx: len(s.searchTopicList) - 1}
+ s.searchTopicMap[t] = searchTopic{foundChn: foundChn}
}
}
func (s *ticketStore) removeSearchTopic(t Topic) {
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)
}
}
@@ -247,20 +235,13 @@ func (s *ticketStore) nextRegisterLookup() (lookup lookupInfo, delay time.Durati
return lookupInfo{}, 40 * time.Second
}
-func (s *ticketStore) nextSearchLookup() lookupInfo {
- if len(s.searchTopicList) == 0 {
- return lookupInfo{}
- }
- if s.searchTopicPtr >= len(s.searchTopicList) {
- s.searchTopicPtr = 0
- }
- topic := s.searchTopicList[s.searchTopicPtr]
- s.searchTopicPtr++
- target := s.radius[topic].nextTarget(s.radiusLookupCnt >= searchForceQuery)
+func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo {
+ tr := s.radius[topic]
+ target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery)
if target.radiusLookup {
- s.radiusLookupCnt++
+ tr.radiusLookupCnt++
} else {
- s.radiusLookupCnt = 0
+ tr.radiusLookupCnt = 0
}
return target
}
@@ -662,9 +643,9 @@ func (s *ticketStore) gotTopicNodes(from *Node, hash common.Hash, nodes []rpcNod
if ip.IsUnspecified() || ip.IsLoopback() {
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 {
- case chn <- enode:
+ case chn <- n:
default:
return false
}
@@ -677,6 +658,8 @@ type topicRadius struct {
topicHashPrefix uint64
radius, minRadius uint64
buckets []topicRadiusBucket
+ converged bool
+ radiusLookupCnt int
}
type topicRadiusEvent int
@@ -706,7 +689,7 @@ func (b *topicRadiusBucket) update(now mclock.AbsTime) {
b.lastTime = now
for target, tm := range b.lookupSent {
- if now-tm > mclock.AbsTime(pingTimeout) {
+ if now-tm > mclock.AbsTime(respTimeout) {
b.weights[trNoAdjust] += 1
delete(b.lookupSent, target)
}
@@ -906,6 +889,7 @@ func (r *topicRadius) recalcRadius() (radius uint64, radiusLookup int) {
if radiusLookup == -1 {
// no more radius lookups needed at the moment, return a radius
+ r.converged = true
rad := maxBucket
if minRadBucket < rad {
rad = minRadBucket
diff --git a/p2p/discv5/udp.go b/p2p/discv5/udp.go
index a6114e032..b43f6d198 100644
--- a/p2p/discv5/udp.go
+++ b/p2p/discv5/udp.go
@@ -196,7 +196,7 @@ func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint {
}
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) {
diff --git a/p2p/nat/nat_test.go b/p2p/nat/nat_test.go
index a079e7a22..469101e99 100644
--- a/p2p/nat/nat_test.go
+++ b/p2p/nat/nat_test.go
@@ -17,7 +17,6 @@
package nat
import (
- "bytes"
"net"
"testing"
"time"
@@ -56,7 +55,7 @@ func TestAutoDiscRace(t *testing.T) {
t.Errorf("result %d: unexpected error: %v", i, rval.err)
}
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)
}
}
diff --git a/params/version.go b/params/version.go
index f8c0d3c9a..ba7a57381 100644
--- a/params/version.go
+++ b/params/version.go
@@ -21,7 +21,7 @@ import "fmt"
const (
VersionMajor = 1 // Major 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
)
diff --git a/swarm/network/protocol.go b/swarm/network/protocol.go
index a3ffd338f..4fffaac6d 100644
--- a/swarm/network/protocol.go
+++ b/swarm/network/protocol.go
@@ -51,7 +51,7 @@ const (
Version = 0
ProtocolLength = uint64(8)
ProtocolMaxMsgSize = 10 * 1024 * 1024
- NetworkId = 322
+ NetworkId = 3
)
const (
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index 117bb4b28..dc5872d98 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -18,7 +18,6 @@ package tests
import (
"bytes"
- "encoding/hex"
"fmt"
"io"
"math/big"
@@ -29,9 +28,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/params"
@@ -207,39 +204,21 @@ func runStateTest(chainConfig *params.ChainConfig, test VmTest) error {
}
func RunState(chainConfig *params.ChainConfig, statedb *state.StateDB, env, tx map[string]string) ([]byte, vm.Logs, *big.Int, error) {
- var (
- data = common.FromHex(tx["data"])
- gas = common.Big(tx["gasLimit"])
- price = common.Big(tx["gasPrice"])
- value = common.Big(tx["value"])
- nonce = common.Big(tx["nonce"]).Uint64()
- )
-
- var to *common.Address
- if len(tx["to"]) > 2 {
- t := common.HexToAddress(tx["to"])
- to = &t
- }
+ environment, msg := NewEVMEnvironment(false, chainConfig, statedb, env, tx)
// Set pre compiled contracts
vm.Precompiled = vm.PrecompiledContracts()
gaspool := new(core.GasPool).AddGas(common.Big(env["currentGasLimit"]))
- key, _ := hex.DecodeString(tx["secretKey"])
- addr := crypto.PubkeyToAddress(crypto.ToECDSA(key).PublicKey)
- message := types.NewMessage(addr, to, nonce, value, gas, price, data, true)
- vmenv := NewEnvFromMap(chainConfig, statedb, env, tx)
- vmenv.origin = addr
-
root, _ := statedb.Commit(false)
statedb.Reset(root)
snapshot := statedb.Snapshot()
- ret, _, err := core.ApplyMessage(vmenv, message, gaspool)
+ ret, gasUsed, err := core.ApplyMessage(environment, msg, gaspool)
if core.IsNonceErr(err) || core.IsInvalidTxErr(err) || core.IsGasLimitErr(err) {
statedb.RevertToSnapshot(snapshot)
}
- statedb.Commit(chainConfig.IsEIP158(vmenv.BlockNumber()))
+ statedb.Commit(chainConfig.IsEIP158(environment.Context.BlockNumber))
- return ret, vmenv.state.Logs(), vmenv.Gas, err
+ return ret, statedb.Logs(), gasUsed, err
}
diff --git a/tests/util.go b/tests/util.go
index 53955a47f..b545a0cc8 100644
--- a/tests/util.go
+++ b/tests/util.go
@@ -18,6 +18,7 @@ package tests
import (
"bytes"
+ "encoding/hex"
"fmt"
"math/big"
"os"
@@ -148,137 +149,63 @@ type VmTest struct {
PostStateRoot string
}
-type Env struct {
- chainConfig *params.ChainConfig
- depth int
- state *state.StateDB
- skipTransfer bool
- initial bool
- Gas *big.Int
+func NewEVMEnvironment(vmTest bool, chainConfig *params.ChainConfig, statedb *state.StateDB, envValues map[string]string, tx map[string]string) (*vm.Environment, core.Message) {
+ var (
+ data = common.FromHex(tx["data"])
+ gas = common.Big(tx["gasLimit"])
+ price = common.Big(tx["gasPrice"])
+ value = common.Big(tx["value"])
+ nonce = common.Big(tx["nonce"]).Uint64()
+ )
- origin common.Address
- parent common.Hash
- coinbase common.Address
-
- number *big.Int
- time *big.Int
- difficulty *big.Int
- gasLimit *big.Int
-
- vmTest bool
-
- evm *vm.EVM
-}
-
-func NewEnv(chainConfig *params.ChainConfig, state *state.StateDB) *Env {
- env := &Env{
- chainConfig: chainConfig,
- state: state,
+ origin := common.HexToAddress(tx["caller"])
+ if len(tx["secretKey"]) > 0 {
+ key, _ := hex.DecodeString(tx["secretKey"])
+ origin = crypto.PubkeyToAddress(crypto.ToECDSA(key).PublicKey)
}
- return env
-}
-
-func NewEnvFromMap(chainConfig *params.ChainConfig, state *state.StateDB, envValues map[string]string, exeValues map[string]string) *Env {
- env := NewEnv(chainConfig, state)
-
- env.origin = common.HexToAddress(exeValues["caller"])
- env.parent = common.HexToHash(envValues["previousHash"])
- env.coinbase = common.HexToAddress(envValues["currentCoinbase"])
- env.number = common.Big(envValues["currentNumber"])
- env.time = common.Big(envValues["currentTimestamp"])
- env.difficulty = common.Big(envValues["currentDifficulty"])
- env.gasLimit = common.Big(envValues["currentGasLimit"])
- env.Gas = new(big.Int)
-
- env.evm = vm.New(env, vm.Config{
- EnableJit: EnableJit,
- ForceJit: ForceJit,
- })
- return env
-}
-
-func (self *Env) ChainConfig() *params.ChainConfig { return self.chainConfig }
-func (self *Env) Vm() vm.Vm { return self.evm }
-func (self *Env) Origin() common.Address { return self.origin }
-func (self *Env) BlockNumber() *big.Int { return self.number }
-func (self *Env) Coinbase() common.Address { return self.coinbase }
-func (self *Env) Time() *big.Int { return self.time }
-func (self *Env) Difficulty() *big.Int { return self.difficulty }
-func (self *Env) Db() vm.Database { return self.state }
-func (self *Env) GasLimit() *big.Int { return self.gasLimit }
-func (self *Env) VmType() vm.Type { return vm.StdVmTy }
-func (self *Env) GetHash(n uint64) common.Hash {
- return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String())))
-}
-func (self *Env) AddLog(log *vm.Log) {
- self.state.AddLog(log)
-}
-func (self *Env) Depth() int { return self.depth }
-func (self *Env) SetDepth(i int) { self.depth = i }
-func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool {
- if self.skipTransfer {
- if self.initial {
- self.initial = false
- return true
- }
+ var to *common.Address
+ if len(tx["to"]) > 2 {
+ t := common.HexToAddress(tx["to"])
+ to = &t
}
- return self.state.GetBalance(from).Cmp(balance) >= 0
-}
-func (self *Env) SnapshotDatabase() int {
- return self.state.Snapshot()
-}
-func (self *Env) RevertToSnapshot(snapshot int) {
- self.state.RevertToSnapshot(snapshot)
-}
+ msg := types.NewMessage(origin, to, nonce, value, gas, price, data, true)
-func (self *Env) Transfer(from, to vm.Account, amount *big.Int) {
- if self.skipTransfer {
- return
- }
- core.Transfer(from, to, amount)
-}
-
-func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- if self.vmTest && self.depth > 0 {
- caller.ReturnGas(gas, price)
-
- return nil, nil
+ initialCall := true
+ canTransfer := func(db vm.StateDB, address common.Address, amount *big.Int) bool {
+ if vmTest {
+ if initialCall {
+ initialCall = false
+ return true
+ }
+ }
+ return core.CanTransfer(db, address, amount)
}
- ret, err := core.Call(self, caller, addr, data, gas, price, value)
- self.Gas = gas
-
- return ret, err
-
-}
-func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- if self.vmTest && self.depth > 0 {
- caller.ReturnGas(gas, price)
-
- return nil, nil
+ transfer := func(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
+ if vmTest {
+ return
+ }
+ core.Transfer(db, sender, recipient, amount)
}
- return core.CallCode(self, caller, addr, data, gas, price, value)
-}
-func (self *Env) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
- if self.vmTest && self.depth > 0 {
- caller.ReturnGas(gas, price)
-
- return nil, nil
+ context := vm.Context{
+ CanTransfer: canTransfer,
+ Transfer: transfer,
+ GetHash: func(n uint64) common.Hash {
+ return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String())))
+ },
+
+ Origin: origin,
+ Coinbase: common.HexToAddress(envValues["currentCoinbase"]),
+ BlockNumber: common.Big(envValues["currentNumber"]),
+ Time: common.Big(envValues["currentTimestamp"]),
+ GasLimit: common.Big(envValues["currentGasLimit"]),
+ Difficulty: common.Big(envValues["currentDifficulty"]),
+ GasPrice: price,
}
- return core.DelegateCall(self, caller, addr, data, gas, price)
-}
-
-func (self *Env) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
- if self.vmTest {
- caller.ReturnGas(gas, price)
-
- nonce := self.state.GetNonce(caller.Address())
- obj := self.state.GetOrNewStateObject(crypto.CreateAddress(caller.Address(), nonce))
-
- return nil, obj.Address(), nil
- } else {
- return core.Create(self, caller, data, gas, price, value)
+ if context.GasPrice == nil {
+ context.GasPrice = new(big.Int)
}
+ return vm.NewEnvironment(context, statedb, chainConfig, vm.Config{NoRecursion: vmTest}), msg
}
diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go
index 50660134b..e23fda5ad 100644
--- a/tests/vm_test_util.go
+++ b/tests/vm_test_util.go
@@ -211,30 +211,23 @@ func runVmTest(test VmTest) error {
return nil
}
-func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, *big.Int, error) {
+func RunVm(statedb *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, *big.Int, error) {
+ chainConfig := &params.ChainConfig{
+ HomesteadBlock: params.MainNetHomesteadBlock,
+ DAOForkBlock: params.MainNetDAOForkBlock,
+ DAOForkSupport: true,
+ }
var (
to = common.HexToAddress(exec["address"])
from = common.HexToAddress(exec["caller"])
data = common.FromHex(exec["data"])
gas = common.Big(exec["gas"])
- price = common.Big(exec["gasPrice"])
value = common.Big(exec["value"])
)
- // Reset the pre-compiled contracts for VM tests.
+ caller := statedb.GetOrNewStateObject(from)
vm.Precompiled = make(map[string]*vm.PrecompiledAccount)
- caller := state.GetOrNewStateObject(from)
-
- chainConfig := &params.ChainConfig{
- HomesteadBlock: params.MainNetHomesteadBlock,
- DAOForkBlock: params.MainNetDAOForkBlock,
- DAOForkSupport: true,
- }
- vmenv := NewEnvFromMap(chainConfig, state, env, exec)
- vmenv.vmTest = true
- vmenv.skipTransfer = true
- vmenv.initial = true
- ret, err := vmenv.Call(caller, to, data, gas, price, value)
-
- return ret, vmenv.state.Logs(), vmenv.Gas, err
+ environment, _ := NewEVMEnvironment(true, chainConfig, statedb, env, exec)
+ ret, err := environment.Call(caller, to, data, gas, value)
+ return ret, statedb.Logs(), gas, err
}