Compare commits
66 Commits
Author | SHA1 | Date | |
---|---|---|---|
ff07d54843 | |||
e53879328c | |||
b792412d31 | |||
49c6f1053c | |||
4d960f6dc6 | |||
8941665896 | |||
9cc0f60666 | |||
fdb8edf5ea | |||
9ba9fe818d | |||
92224d27b1 | |||
157a4bd926 | |||
29d6881112 | |||
e2692921e1 | |||
b63138c3ec | |||
07311f3157 | |||
17637ed1bb | |||
f15828e901 | |||
dadd689359 | |||
b750cab56a | |||
485748c416 | |||
080699f7df | |||
8e35f54931 | |||
d44f1a77ee | |||
4181046488 | |||
d7c398b638 | |||
5f5d0aa4ff | |||
9f1520b4c0 | |||
a98e8c0889 | |||
ee445a2ba4 | |||
b2c226cb7d | |||
13614f4e1c | |||
4f9ccdd70f | |||
4e36b1e3da | |||
f12f8a6c14 | |||
c57c54ce96 | |||
c8130df1d9 | |||
af8a742d00 | |||
e67500aa15 | |||
a6d3bf6fc3 | |||
3e617f3cd6 | |||
0fe35b907a | |||
3fc7c97827 | |||
7f79d249a6 | |||
f138374027 | |||
f52a1ae849 | |||
3bc0fe1ee3 | |||
fa0cc27400 | |||
4cb29bde2e | |||
2dcf75a722 | |||
671fd94e25 | |||
717d2f6f9e | |||
8cb95cb916 | |||
56b446190a | |||
86f9e836be | |||
a90a170361 | |||
889a5e0cf1 | |||
9f8bc00cf5 | |||
3363a1c227 | |||
fc9939c4e1 | |||
7267f796e6 | |||
7dfeceb8cc | |||
3807e520ec | |||
7625b1a4f4 | |||
1fc5cc1b59 | |||
61ccb43487 | |||
bf24b120d7 |
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@ -9,9 +9,7 @@ and help.
|
||||
|
||||
If you'd like to contribute to go-ethereum please fork, fix, commit and
|
||||
send a pull request. Commits which do not comply with the coding standards
|
||||
are ignored (use gofmt!). If you send pull requests make absolute sure that you
|
||||
commit on the `develop` branch and that you do not merge to master.
|
||||
Commits that are directly based on master are simply ignored.
|
||||
are ignored (use gofmt!).
|
||||
|
||||
See [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
|
||||
for more details on configuring your environment, testing, and
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
22
build/ci.go
22
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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
153
build/nsis.pathupdate.nsh
Normal file
153
build/nsis.pathupdate.nsh
Normal file
@ -0,0 +1,153 @@
|
||||
!include "WinMessages.nsh"
|
||||
|
||||
; see https://support.microsoft.com/en-us/kb/104011
|
||||
!define Environ 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
|
||||
; HKEY_LOCAL_MACHINE = 0x80000002
|
||||
|
||||
; AddToPath - Appends dir to PATH
|
||||
; (does not work on Win9x/ME)
|
||||
;
|
||||
; Usage:
|
||||
; Push "dir"
|
||||
; Call AddToPath
|
||||
Function AddToPath
|
||||
Exch $0
|
||||
Push $1
|
||||
Push $2
|
||||
Push $3
|
||||
Push $4
|
||||
|
||||
; NSIS ReadRegStr returns empty string on string overflow
|
||||
; Native calls are used here to check actual length of PATH
|
||||
; $4 = RegOpenKey(HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Control\Session Manager\Environment", &$3)
|
||||
System::Call "advapi32::RegOpenKey(i 0x80000002, t'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', *i.r3) i.r4"
|
||||
IntCmp $4 0 0 done done
|
||||
|
||||
; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2))
|
||||
; RegCloseKey($3)
|
||||
System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4"
|
||||
System::Call "advapi32::RegCloseKey(i $3)"
|
||||
|
||||
IntCmp $4 234 0 +4 +4 ; $4 == ERROR_MORE_DATA
|
||||
DetailPrint "AddToPath: original length $2 > ${NSIS_MAX_STRLEN}"
|
||||
MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}"
|
||||
Goto done
|
||||
|
||||
IntCmp $4 0 +5 ; $4 != NO_ERROR
|
||||
IntCmp $4 2 +3 ; $4 != ERROR_FILE_NOT_FOUND
|
||||
DetailPrint "AddToPath: unexpected error code $4"
|
||||
Goto done
|
||||
StrCpy $1 ""
|
||||
|
||||
; Check if already in PATH
|
||||
Push "$1;"
|
||||
Push "$0;"
|
||||
Call StrStr
|
||||
Pop $2
|
||||
StrCmp $2 "" 0 done
|
||||
Push "$1;"
|
||||
Push "$0\;"
|
||||
Call StrStr
|
||||
Pop $2
|
||||
StrCmp $2 "" 0 done
|
||||
|
||||
; Prevent NSIS string overflow
|
||||
StrLen $2 $0
|
||||
StrLen $3 $1
|
||||
IntOp $2 $2 + $3
|
||||
IntOp $2 $2 + 2 ; $2 = strlen(dir) + strlen(PATH) + sizeof(";")
|
||||
IntCmp $2 ${NSIS_MAX_STRLEN} +4 +4 0
|
||||
DetailPrint "AddToPath: new length $2 > ${NSIS_MAX_STRLEN}"
|
||||
MessageBox MB_OK "PATH not updated, new length $2 > ${NSIS_MAX_STRLEN}."
|
||||
Goto done
|
||||
|
||||
; Append dir to PATH
|
||||
DetailPrint "Add to PATH: $0"
|
||||
StrCpy $2 $1 1 -1
|
||||
StrCmp $2 ";" 0 +2
|
||||
StrCpy $1 $1 -1 ; remove trailing ';'
|
||||
StrCmp $1 "" +2 ; no leading ';'
|
||||
StrCpy $0 "$1;$0"
|
||||
|
||||
WriteRegExpandStr ${Environ} "PATH" $0
|
||||
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
|
||||
|
||||
done:
|
||||
Pop $4
|
||||
Pop $3
|
||||
Pop $2
|
||||
Pop $1
|
||||
Pop $0
|
||||
FunctionEnd
|
||||
|
||||
|
||||
; RemoveFromPath - Removes dir from PATH
|
||||
;
|
||||
; Usage:
|
||||
; Push "dir"
|
||||
; Call RemoveFromPath
|
||||
Function un.RemoveFromPath
|
||||
Exch $0
|
||||
Push $1
|
||||
Push $2
|
||||
Push $3
|
||||
Push $4
|
||||
Push $5
|
||||
Push $6
|
||||
|
||||
; NSIS ReadRegStr returns empty string on string overflow
|
||||
; Native calls are used here to check actual length of PATH
|
||||
; $4 = RegOpenKey(HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Control\Session Manager\Environment", &$3)
|
||||
System::Call "advapi32::RegOpenKey(i 0x80000002, t'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', *i.r3) i.r4"
|
||||
IntCmp $4 0 0 done done
|
||||
|
||||
; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2))
|
||||
; RegCloseKey($3)
|
||||
System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4"
|
||||
System::Call "advapi32::RegCloseKey(i $3)"
|
||||
|
||||
IntCmp $4 234 0 +4 +4 ; $4 == ERROR_MORE_DATA
|
||||
DetailPrint "RemoveFromPath: original length $2 > ${NSIS_MAX_STRLEN}"
|
||||
MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}"
|
||||
Goto done
|
||||
|
||||
IntCmp $4 0 +5 ; $4 != NO_ERROR
|
||||
IntCmp $4 2 +3 ; $4 != ERROR_FILE_NOT_FOUND
|
||||
DetailPrint "RemoveFromPath: unexpected error code $4"
|
||||
Goto done
|
||||
StrCpy $1 ""
|
||||
|
||||
; length < ${NSIS_MAX_STRLEN} -> ReadRegStr can be used
|
||||
ReadRegStr $1 ${Environ} "PATH"
|
||||
StrCpy $5 $1 1 -1
|
||||
StrCmp $5 ";" +2
|
||||
StrCpy $1 "$1;" ; ensure trailing ';'
|
||||
Push $1
|
||||
Push "$0;"
|
||||
Call un.StrStr
|
||||
Pop $2 ; pos of our dir
|
||||
StrCmp $2 "" done
|
||||
|
||||
DetailPrint "Remove from PATH: $0"
|
||||
StrLen $3 "$0;"
|
||||
StrLen $4 $2
|
||||
StrCpy $5 $1 -$4 ; $5 is now the part before the path to remove
|
||||
StrCpy $6 $2 "" $3 ; $6 is now the part after the path to remove
|
||||
StrCpy $3 "$5$6"
|
||||
StrCpy $5 $3 1 -1
|
||||
StrCmp $5 ";" 0 +2
|
||||
StrCpy $3 $3 -1 ; remove trailing ';'
|
||||
WriteRegExpandStr ${Environ} "PATH" $3
|
||||
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
|
||||
|
||||
done:
|
||||
Pop $6
|
||||
Pop $5
|
||||
Pop $4
|
||||
Pop $3
|
||||
Pop $2
|
||||
Pop $1
|
||||
Pop $0
|
||||
FunctionEnd
|
||||
|
||||
|
@ -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}"
|
||||
|
139
cmd/evm/main.go
139
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)
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
@ -39,6 +40,18 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
initCommand = cli.Command{
|
||||
Action: initGenesis,
|
||||
Name: "init",
|
||||
Usage: "Bootstrap and initialize a new genesis block",
|
||||
ArgsUsage: "<genesisPath>",
|
||||
Category: "BLOCKCHAIN COMMANDS",
|
||||
Description: `
|
||||
The init command initializes a new genesis block and definition for the network.
|
||||
This is a destructive action and changes the network in which you will be
|
||||
participating.
|
||||
`,
|
||||
}
|
||||
importCommand = cli.Command{
|
||||
Action: importChain,
|
||||
Name: "import",
|
||||
@ -95,6 +108,30 @@ Use "ethereum dump 0" to dump the genesis block.
|
||||
}
|
||||
)
|
||||
|
||||
// initGenesis will initialise the given JSON format genesis file and writes it as
|
||||
// the zero'd block (i.e. genesis) or will fail hard if it can't succeed.
|
||||
func initGenesis(ctx *cli.Context) error {
|
||||
genesisPath := ctx.Args().First()
|
||||
if len(genesisPath) == 0 {
|
||||
utils.Fatalf("must supply path to genesis JSON file")
|
||||
}
|
||||
|
||||
stack := makeFullNode(ctx)
|
||||
chaindb := utils.MakeChainDatabase(ctx, stack)
|
||||
|
||||
genesisFile, err := os.Open(genesisPath)
|
||||
if err != nil {
|
||||
utils.Fatalf("failed to read genesis file: %v", err)
|
||||
}
|
||||
|
||||
block, err := core.WriteGenesisBlock(chaindb, genesisFile)
|
||||
if err != nil {
|
||||
utils.Fatalf("failed to write genesis block: %v", err)
|
||||
}
|
||||
glog.V(logger.Info).Infof("successfully wrote genesis block and/or chain rule set: %x", block.Hash())
|
||||
return nil
|
||||
}
|
||||
|
||||
func importChain(ctx *cli.Context) error {
|
||||
if len(ctx.Args()) != 1 {
|
||||
utils.Fatalf("This command requires an argument.")
|
||||
|
@ -83,75 +83,28 @@ var daoGenesisForkBlock = big.NewInt(314)
|
||||
// TestDAOForkBlockNewChain tests that the DAO hard-fork number and the nodes support/opposition is correctly
|
||||
// set in the database after various initialization procedures and invocations.
|
||||
func TestDAOForkBlockNewChain(t *testing.T) {
|
||||
for _, arg := range []struct {
|
||||
for i, arg := range []struct {
|
||||
testnet bool
|
||||
genesis string
|
||||
votes [][2]bool
|
||||
expectBlock *big.Int
|
||||
expectVote bool
|
||||
}{
|
||||
// Test DAO Default Mainnet
|
||||
{false, "", [][2]bool{{false, false}}, params.MainNetDAOForkBlock, true},
|
||||
// test DAO Support Mainnet
|
||||
{false, "", [][2]bool{{true, false}}, params.MainNetDAOForkBlock, true},
|
||||
// test DAO Oppose Mainnet
|
||||
{false, "", [][2]bool{{false, true}}, params.MainNetDAOForkBlock, false},
|
||||
// test DAO Switch To Support Mainnet
|
||||
{false, "", [][2]bool{{false, true}, {true, false}}, params.MainNetDAOForkBlock, true},
|
||||
// test DAO Switch To Oppose Mainnet
|
||||
{false, "", [][2]bool{{true, false}, {false, true}}, params.MainNetDAOForkBlock, false},
|
||||
{false, "", params.MainNetDAOForkBlock, true},
|
||||
// test DAO Default Testnet
|
||||
{true, "", [][2]bool{{false, false}}, params.TestNetDAOForkBlock, true},
|
||||
// test DAO Support Testnet
|
||||
{true, "", [][2]bool{{true, false}}, params.TestNetDAOForkBlock, true},
|
||||
// test DAO Oppose Testnet
|
||||
{true, "", [][2]bool{{false, true}}, params.TestNetDAOForkBlock, false},
|
||||
// test DAO Switch To Support Testnet
|
||||
{true, "", [][2]bool{{false, true}, {true, false}}, params.TestNetDAOForkBlock, true},
|
||||
// test DAO Switch To Oppose Testnet
|
||||
{true, "", [][2]bool{{true, false}, {false, true}}, params.TestNetDAOForkBlock, false},
|
||||
{true, "", params.TestNetDAOForkBlock, true},
|
||||
// test DAO Init Old Privnet
|
||||
{false, daoOldGenesis, [][2]bool{}, nil, false},
|
||||
// test DAO Default Old Privnet
|
||||
{false, daoOldGenesis, [][2]bool{{false, false}}, nil, false},
|
||||
// test DAO Support Old Privnet
|
||||
{false, daoOldGenesis, [][2]bool{{true, false}}, nil, true},
|
||||
// test DAO Oppose Old Privnet
|
||||
{false, daoOldGenesis, [][2]bool{{false, true}}, nil, false},
|
||||
// test DAO Switch To Support Old Privnet
|
||||
{false, daoOldGenesis, [][2]bool{{false, true}, {true, false}}, nil, true},
|
||||
// test DAO Switch To Oppose Old Privnet
|
||||
{false, daoOldGenesis, [][2]bool{{true, false}, {false, true}}, nil, false},
|
||||
// test DAO Init No Fork Privnet
|
||||
{false, daoNoForkGenesis, [][2]bool{}, daoGenesisForkBlock, false},
|
||||
{false, daoOldGenesis, nil, false},
|
||||
// test DAO Default No Fork Privnet
|
||||
{false, daoNoForkGenesis, [][2]bool{{false, false}}, daoGenesisForkBlock, false},
|
||||
// test DAO Support No Fork Privnet
|
||||
{false, daoNoForkGenesis, [][2]bool{{true, false}}, daoGenesisForkBlock, true},
|
||||
// test DAO Oppose No Fork Privnet
|
||||
{false, daoNoForkGenesis, [][2]bool{{false, true}}, daoGenesisForkBlock, false},
|
||||
// test DAO Switch To Support No Fork Privnet
|
||||
{false, daoNoForkGenesis, [][2]bool{{false, true}, {true, false}}, daoGenesisForkBlock, true},
|
||||
// test DAO Switch To Oppose No Fork Privnet
|
||||
{false, daoNoForkGenesis, [][2]bool{{true, false}, {false, true}}, daoGenesisForkBlock, false},
|
||||
// test DAO Init Pro Fork Privnet
|
||||
{false, daoProForkGenesis, [][2]bool{}, daoGenesisForkBlock, true},
|
||||
{false, daoNoForkGenesis, daoGenesisForkBlock, false},
|
||||
// test DAO Default Pro Fork Privnet
|
||||
{false, daoProForkGenesis, [][2]bool{{false, false}}, daoGenesisForkBlock, true},
|
||||
// test DAO Support Pro Fork Privnet
|
||||
{false, daoProForkGenesis, [][2]bool{{true, false}}, daoGenesisForkBlock, true},
|
||||
// test DAO Oppose Pro Fork Privnet
|
||||
{false, daoProForkGenesis, [][2]bool{{false, true}}, daoGenesisForkBlock, false},
|
||||
// test DAO Switch To Support Pro Fork Privnet
|
||||
{false, daoProForkGenesis, [][2]bool{{false, true}, {true, false}}, daoGenesisForkBlock, true},
|
||||
// test DAO Switch To Oppose Pro Fork Privnet
|
||||
{false, daoProForkGenesis, [][2]bool{{true, false}, {false, true}}, daoGenesisForkBlock, false},
|
||||
{false, daoProForkGenesis, daoGenesisForkBlock, true},
|
||||
} {
|
||||
testDAOForkBlockNewChain(t, arg.testnet, arg.genesis, arg.votes, arg.expectBlock, arg.expectVote)
|
||||
testDAOForkBlockNewChain(t, i, arg.testnet, arg.genesis, arg.expectBlock, arg.expectVote)
|
||||
}
|
||||
}
|
||||
|
||||
func testDAOForkBlockNewChain(t *testing.T, testnet bool, genesis string, votes [][2]bool, expectBlock *big.Int, expectVote bool) {
|
||||
func testDAOForkBlockNewChain(t *testing.T, test int, testnet bool, genesis string, expectBlock *big.Int, expectVote bool) {
|
||||
// Create a temporary data directory to use and inspect later
|
||||
datadir := tmpdir(t)
|
||||
defer os.RemoveAll(datadir)
|
||||
@ -160,21 +113,15 @@ func testDAOForkBlockNewChain(t *testing.T, testnet bool, genesis string, votes
|
||||
if genesis != "" {
|
||||
json := filepath.Join(datadir, "genesis.json")
|
||||
if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil {
|
||||
t.Fatalf("failed to write genesis file: %v", err)
|
||||
t.Fatalf("test %d: failed to write genesis file: %v", test, err)
|
||||
}
|
||||
runGeth(t, "--datadir", datadir, "init", json).cmd.Wait()
|
||||
}
|
||||
for _, vote := range votes {
|
||||
} else {
|
||||
// Force chain initialization
|
||||
args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir}
|
||||
if testnet {
|
||||
args = append(args, "--testnet")
|
||||
}
|
||||
if vote[0] {
|
||||
args = append(args, "--support-dao-fork")
|
||||
}
|
||||
if vote[1] {
|
||||
args = append(args, "--oppose-dao-fork")
|
||||
}
|
||||
geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...)
|
||||
geth.cmd.Wait()
|
||||
}
|
||||
@ -185,7 +132,7 @@ func testDAOForkBlockNewChain(t *testing.T, testnet bool, genesis string, votes
|
||||
}
|
||||
db, err := ethdb.NewLDBDatabase(path, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open test database: %v", err)
|
||||
t.Fatalf("test %d: failed to open test database: %v", test, err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
@ -198,20 +145,20 @@ func testDAOForkBlockNewChain(t *testing.T, testnet bool, genesis string, votes
|
||||
}
|
||||
config, err := core.GetChainConfig(db, genesisHash)
|
||||
if err != nil {
|
||||
t.Errorf("failed to retrieve chain config: %v", err)
|
||||
t.Errorf("test %d: failed to retrieve chain config: %v", test, err)
|
||||
return // we want to return here, the other checks can't make it past this point (nil panic).
|
||||
}
|
||||
// Validate the DAO hard-fork block number against the expected value
|
||||
if config.DAOForkBlock == nil {
|
||||
if expectBlock != nil {
|
||||
t.Errorf("dao hard-fork block mismatch: have nil, want %v", expectBlock)
|
||||
t.Errorf("test %d: dao hard-fork block mismatch: have nil, want %v", test, expectBlock)
|
||||
}
|
||||
} else if expectBlock == nil {
|
||||
t.Errorf("dao hard-fork block mismatch: have %v, want nil", config.DAOForkBlock)
|
||||
t.Errorf("test %d: dao hard-fork block mismatch: have %v, want nil", test, config.DAOForkBlock)
|
||||
} else if config.DAOForkBlock.Cmp(expectBlock) != 0 {
|
||||
t.Errorf("dao hard-fork block mismatch: have %v, want %v", config.DAOForkBlock, expectBlock)
|
||||
t.Errorf("test %d: dao hard-fork block mismatch: have %v, want %v", test, config.DAOForkBlock, expectBlock)
|
||||
}
|
||||
if config.DAOForkSupport != expectVote {
|
||||
t.Errorf("dao hard-fork support mismatch: have %v, want %v", config.DAOForkSupport, expectVote)
|
||||
t.Errorf("test %d: dao hard-fork support mismatch: have %v, want %v", test, config.DAOForkSupport, expectVote)
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
||||
}
|
@ -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")
|
||||
}
|
||||
}()
|
||||
}
|
144
cmd/geth/main.go
144
cmd/geth/main.go
@ -20,20 +20,15 @@ package main
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/ethash"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/ethereum/go-ethereum/contracts/release"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
@ -64,59 +59,26 @@ func init() {
|
||||
app.HideVersion = true // we have a command to print the version
|
||||
app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
|
||||
app.Commands = []cli.Command{
|
||||
// See chaincmd.go:
|
||||
initCommand,
|
||||
importCommand,
|
||||
exportCommand,
|
||||
upgradedbCommand,
|
||||
removedbCommand,
|
||||
dumpCommand,
|
||||
// See monitorcmd.go:
|
||||
monitorCommand,
|
||||
// See accountcmd.go:
|
||||
accountCommand,
|
||||
walletCommand,
|
||||
// See consolecmd.go:
|
||||
consoleCommand,
|
||||
attachCommand,
|
||||
javascriptCommand,
|
||||
{
|
||||
Action: makedag,
|
||||
Name: "makedag",
|
||||
Usage: "Generate ethash DAG (for testing)",
|
||||
ArgsUsage: "<blockNum> <outputDir>",
|
||||
Category: "MISCELLANEOUS COMMANDS",
|
||||
Description: `
|
||||
The makedag command generates an ethash DAG in /tmp/dag.
|
||||
|
||||
This command exists to support the system testing project.
|
||||
Regular users do not need to execute it.
|
||||
`,
|
||||
},
|
||||
{
|
||||
Action: version,
|
||||
Name: "version",
|
||||
Usage: "Print version numbers",
|
||||
ArgsUsage: " ",
|
||||
Category: "MISCELLANEOUS COMMANDS",
|
||||
Description: `
|
||||
The output of this command is supposed to be machine-readable.
|
||||
`,
|
||||
},
|
||||
{
|
||||
Action: initGenesis,
|
||||
Name: "init",
|
||||
Usage: "Bootstrap and initialize a new genesis block",
|
||||
ArgsUsage: "<genesisPath>",
|
||||
Category: "BLOCKCHAIN COMMANDS",
|
||||
Description: `
|
||||
The init command initializes a new genesis block and definition for the network.
|
||||
This is a destructive action and changes the network in which you will be
|
||||
participating.
|
||||
`,
|
||||
},
|
||||
{
|
||||
Action: license,
|
||||
Name: "license",
|
||||
Usage: "Display license information",
|
||||
ArgsUsage: " ",
|
||||
Category: "MISCELLANEOUS COMMANDS",
|
||||
},
|
||||
// See misccmd.go:
|
||||
makedagCommand,
|
||||
versionCommand,
|
||||
licenseCommand,
|
||||
}
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
@ -140,8 +102,6 @@ participating.
|
||||
utils.MaxPendingPeersFlag,
|
||||
utils.EtherbaseFlag,
|
||||
utils.GasPriceFlag,
|
||||
utils.SupportDAOFork,
|
||||
utils.OpposeDAOFork,
|
||||
utils.MinerThreadsFlag,
|
||||
utils.MiningEnabledFlag,
|
||||
utils.AutoDAGFlag,
|
||||
@ -232,30 +192,6 @@ func geth(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// initGenesis will initialise the given JSON format genesis file and writes it as
|
||||
// the zero'd block (i.e. genesis) or will fail hard if it can't succeed.
|
||||
func initGenesis(ctx *cli.Context) error {
|
||||
genesisPath := ctx.Args().First()
|
||||
if len(genesisPath) == 0 {
|
||||
utils.Fatalf("must supply path to genesis JSON file")
|
||||
}
|
||||
|
||||
stack := makeFullNode(ctx)
|
||||
chaindb := utils.MakeChainDatabase(ctx, stack)
|
||||
|
||||
genesisFile, err := os.Open(genesisPath)
|
||||
if err != nil {
|
||||
utils.Fatalf("failed to read genesis file: %v", err)
|
||||
}
|
||||
|
||||
block, err := core.WriteGenesisBlock(chaindb, genesisFile)
|
||||
if err != nil {
|
||||
utils.Fatalf("failed to write genesis block: %v", err)
|
||||
}
|
||||
glog.V(logger.Info).Infof("successfully wrote genesis block and/or chain rule set: %x", block.Hash())
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeFullNode(ctx *cli.Context) *node.Node {
|
||||
// Create the default extradata and construct the base node
|
||||
var clientInfo = struct {
|
||||
@ -330,65 +266,3 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makedag(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
wrongArgs := func() {
|
||||
utils.Fatalf(`Usage: geth makedag <block number> <outputdir>`)
|
||||
}
|
||||
switch {
|
||||
case len(args) == 2:
|
||||
blockNum, err := strconv.ParseUint(args[0], 0, 64)
|
||||
dir := args[1]
|
||||
if err != nil {
|
||||
wrongArgs()
|
||||
} else {
|
||||
dir = filepath.Clean(dir)
|
||||
// seems to require a trailing slash
|
||||
if !strings.HasSuffix(dir, "/") {
|
||||
dir = dir + "/"
|
||||
}
|
||||
_, err = ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
utils.Fatalf("Can't find dir")
|
||||
}
|
||||
fmt.Println("making DAG, this could take awhile...")
|
||||
ethash.MakeDAG(blockNum, dir)
|
||||
}
|
||||
default:
|
||||
wrongArgs()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func version(ctx *cli.Context) error {
|
||||
fmt.Println(strings.Title(clientIdentifier))
|
||||
fmt.Println("Version:", params.Version)
|
||||
if gitCommit != "" {
|
||||
fmt.Println("Git Commit:", gitCommit)
|
||||
}
|
||||
fmt.Println("Protocol Versions:", eth.ProtocolVersions)
|
||||
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 license(_ *cli.Context) error {
|
||||
fmt.Println(`Geth 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.
|
||||
|
||||
Geth 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 geth. If not, see <http://www.gnu.org/licenses/>.
|
||||
`)
|
||||
return nil
|
||||
}
|
||||
|
128
cmd/geth/misccmd.go
Normal file
128
cmd/geth/misccmd.go
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright 2016 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/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/ethash"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
makedagCommand = cli.Command{
|
||||
Action: makedag,
|
||||
Name: "makedag",
|
||||
Usage: "Generate ethash DAG (for testing)",
|
||||
ArgsUsage: "<blockNum> <outputDir>",
|
||||
Category: "MISCELLANEOUS COMMANDS",
|
||||
Description: `
|
||||
The makedag command generates an ethash DAG in /tmp/dag.
|
||||
|
||||
This command exists to support the system testing project.
|
||||
Regular users do not need to execute it.
|
||||
`,
|
||||
}
|
||||
versionCommand = cli.Command{
|
||||
Action: version,
|
||||
Name: "version",
|
||||
Usage: "Print version numbers",
|
||||
ArgsUsage: " ",
|
||||
Category: "MISCELLANEOUS COMMANDS",
|
||||
Description: `
|
||||
The output of this command is supposed to be machine-readable.
|
||||
`,
|
||||
}
|
||||
licenseCommand = cli.Command{
|
||||
Action: license,
|
||||
Name: "license",
|
||||
Usage: "Display license information",
|
||||
ArgsUsage: " ",
|
||||
Category: "MISCELLANEOUS COMMANDS",
|
||||
}
|
||||
)
|
||||
|
||||
func makedag(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
wrongArgs := func() {
|
||||
utils.Fatalf(`Usage: geth makedag <block number> <outputdir>`)
|
||||
}
|
||||
switch {
|
||||
case len(args) == 2:
|
||||
blockNum, err := strconv.ParseUint(args[0], 0, 64)
|
||||
dir := args[1]
|
||||
if err != nil {
|
||||
wrongArgs()
|
||||
} else {
|
||||
dir = filepath.Clean(dir)
|
||||
// seems to require a trailing slash
|
||||
if !strings.HasSuffix(dir, "/") {
|
||||
dir = dir + "/"
|
||||
}
|
||||
_, err = ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
utils.Fatalf("Can't find dir")
|
||||
}
|
||||
fmt.Println("making DAG, this could take awhile...")
|
||||
ethash.MakeDAG(blockNum, dir)
|
||||
}
|
||||
default:
|
||||
wrongArgs()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func version(ctx *cli.Context) error {
|
||||
fmt.Println(strings.Title(clientIdentifier))
|
||||
fmt.Println("Version:", params.Version)
|
||||
if gitCommit != "" {
|
||||
fmt.Println("Git Commit:", gitCommit)
|
||||
}
|
||||
fmt.Println("Protocol Versions:", eth.ProtocolVersions)
|
||||
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 license(_ *cli.Context) error {
|
||||
fmt.Println(`Geth 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.
|
||||
|
||||
Geth 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 geth. If not, see <http://www.gnu.org/licenses/>.
|
||||
`)
|
||||
return nil
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
mroot.Hash = hash
|
||||
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)
|
||||
}
|
||||
|
||||
// Print the manifest. This is the only output to stdout.
|
||||
mrootJSON, _ := json.MarshalIndent(mroot, "", " ")
|
||||
fmt.Println(string(mrootJSON))
|
||||
// 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:]
|
||||
}
|
||||
}
|
||||
return path.Clean(os.ExpandEnv(p))
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
if usr, err := user.Current(); err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// client wraps interaction with the swarm HTTP gateway.
|
||||
@ -88,24 +112,40 @@ type client struct {
|
||||
}
|
||||
|
||||
// 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"`
|
||||
type manifestEntry struct {
|
||||
Hash string `json:"hash,omitempty"`
|
||||
ContentType string `json:"contentType,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
func (c *client) uploadFile(file string, fi os.FileInfo) (manifest, error) {
|
||||
// manifest is the JSON representation of a swarm manifest.
|
||||
type manifest struct {
|
||||
Entries []manifestEntry `json:"entries,omitempty"`
|
||||
}
|
||||
|
||||
func (c *client) uploadFile(file string, fi os.FileInfo) (manifestEntry, error) {
|
||||
hash, err := c.uploadFileContent(file, fi)
|
||||
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() {
|
@ -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)
|
||||
|
@ -125,7 +125,7 @@ var (
|
||||
}
|
||||
TestNetFlag = cli.BoolFlag{
|
||||
Name: "testnet",
|
||||
Usage: "Morden network: pre-configured test network with modified starting nonces (replay protection)",
|
||||
Usage: "Ropsten network: pre-configured test network",
|
||||
}
|
||||
DevModeFlag = cli.BoolFlag{
|
||||
Name: "dev",
|
||||
@ -177,15 +177,6 @@ var (
|
||||
Usage: "Number of trie node generations to keep in memory",
|
||||
Value: int(state.MaxTrieCacheGen),
|
||||
}
|
||||
// Fork settings
|
||||
SupportDAOFork = cli.BoolFlag{
|
||||
Name: "support-dao-fork",
|
||||
Usage: "Updates the chain rules to support the DAO hard-fork",
|
||||
}
|
||||
OpposeDAOFork = cli.BoolFlag{
|
||||
Name: "oppose-dao-fork",
|
||||
Usage: "Updates the chain rules to oppose the DAO hard-fork",
|
||||
}
|
||||
// Miner settings
|
||||
MiningEnabledFlag = cli.BoolFlag{
|
||||
Name: "mine",
|
||||
@ -901,13 +892,6 @@ func MakeChainConfigFromDb(ctx *cli.Context, db ethdb.Database) *params.ChainCon
|
||||
config.ChainId = params.MainNetChainID
|
||||
}
|
||||
}
|
||||
// Force override any existing configs if explicitly requested
|
||||
switch {
|
||||
case ctx.GlobalBool(SupportDAOFork.Name):
|
||||
config.DAOForkSupport = true
|
||||
case ctx.GlobalBool(OpposeDAOFork.Name):
|
||||
config.DAOForkSupport = false
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
|
@ -988,7 +988,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 +1062,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 +1174,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})
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -61,7 +61,6 @@ type ChainEvent struct {
|
||||
|
||||
type ChainSideEvent struct {
|
||||
Block *types.Block
|
||||
Logs vm.Logs
|
||||
}
|
||||
|
||||
type PendingBlockEvent struct {
|
||||
|
73
core/evm.go
Normal file
73
core/evm.go
Normal file
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
ret, vmerr = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.value)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && IsValueTransferErr(err) {
|
||||
return nil, nil, nil, InvalidTxError(err)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -124,6 +124,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 +178,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 +187,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 +239,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
|
||||
@ -372,10 +379,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 +413,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 +434,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 +516,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 +652,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)
|
||||
|
@ -51,6 +51,80 @@ 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()
|
||||
|
||||
@ -97,9 +171,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 +183,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 +199,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 +302,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 +314,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 +373,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 +531,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 +644,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 +814,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 +838,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,6 +234,8 @@ func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amo
|
||||
func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce }
|
||||
func (tx *Transaction) CheckNonce() bool { return true }
|
||||
|
||||
// To returns the recipient address of the transaction.
|
||||
// It returns nil if the transaction is a contract creation.
|
||||
func (tx *Transaction) To() *common.Address {
|
||||
if tx.data.Recipient == nil {
|
||||
return nil
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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
|
||||
}
|
||||
|
||||
// Database is a EVM database for full state querying.
|
||||
type Database interface {
|
||||
GetAccount(common.Address) Account
|
||||
CreateAccount(common.Address) Account
|
||||
|
||||
AddBalance(common.Address, *big.Int)
|
||||
GetBalance(common.Address) *big.Int
|
||||
|
||||
GetNonce(common.Address) uint64
|
||||
SetNonce(common.Address, uint64)
|
||||
|
||||
GetCodeHash(common.Address) common.Hash
|
||||
GetCodeSize(common.Address) int
|
||||
GetCode(common.Address) []byte
|
||||
SetCode(common.Address, []byte)
|
||||
|
||||
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
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var (
|
||||
snapshot = env.StateDB.Snapshot()
|
||||
to = env.StateDB.GetAccount(caller.Address())
|
||||
)
|
||||
|
||||
// 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()
|
||||
|
||||
ret, err := env.EVM().Run(contract, input)
|
||||
if err != nil {
|
||||
contract.UseGas(contract.Gas)
|
||||
|
||||
env.StateDB.RevertToSnapshot(snapshot)
|
||||
}
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// 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 }
|
||||
|
@ -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")
|
||||
)
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
97
core/vm/interface.go
Normal file
97
core/vm/interface.go
Normal file
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
142
core/vm/log.go
142
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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
68
core/vm/noop.go
Normal file
68
core/vm/noop.go
Normal file
@ -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) {}
|
@ -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
|
||||
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 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,
|
||||
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)
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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{})
|
||||
}
|
||||
|
101
core/vm_env.go
101
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)
|
||||
}
|
||||
|
27
eth/api.go
27
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)
|
||||
|
@ -56,7 +56,7 @@ func (b *EthApiBackend) SetHead(number uint64) {
|
||||
func (b *EthApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) {
|
||||
// Pending block is only known by the miner
|
||||
if blockNr == rpc.PendingBlockNumber {
|
||||
block, _ := b.eth.miner.Pending()
|
||||
block := b.eth.miner.PendingBlock()
|
||||
return block.Header(), nil
|
||||
}
|
||||
// Otherwise resolve and return the block
|
||||
@ -69,7 +69,7 @@ func (b *EthApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNum
|
||||
func (b *EthApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) {
|
||||
// Pending block is only known by the miner
|
||||
if blockNr == rpc.PendingBlockNumber {
|
||||
block, _ := b.eth.miner.Pending()
|
||||
block := b.eth.miner.PendingBlock()
|
||||
return block, nil
|
||||
}
|
||||
// Otherwise resolve and return the block
|
||||
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
12
eth/sync.go
12
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()
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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{})
|
||||
|
@ -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,33 +144,48 @@ 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 <-headSub.Chan():
|
||||
// Exhaust events to avoid reporting too frequently
|
||||
for exhausted := false; !exhausted; {
|
||||
select {
|
||||
case <-headSub.Chan():
|
||||
default:
|
||||
exhausted = true
|
||||
}
|
||||
case list := <-s.histCh:
|
||||
if err = s.reportHistory(out, list); err != nil {
|
||||
glog.V(logger.Warn).Infof("Block history report failed: %v", err)
|
||||
}
|
||||
if err = s.reportBlock(out); err != nil {
|
||||
case head, ok := <-headSub.Chan():
|
||||
if !ok { // node stopped
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
if err = s.reportBlock(out, head.Data.(core.ChainHeadEvent).Block); err != nil {
|
||||
glog.V(logger.Warn).Infof("Block stats report failed: %v", err)
|
||||
}
|
||||
case <-txSub.Chan():
|
||||
if err = s.reportPending(out); err != nil {
|
||||
glog.V(logger.Warn).Infof("Post-block transaction stats report failed: %v", err)
|
||||
}
|
||||
case _, ok := <-txSub.Chan():
|
||||
if !ok { // node stopped
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
// Exhaust events to avoid reporting too frequently
|
||||
for exhausted := false; !exhausted; {
|
||||
select {
|
||||
@ -175,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 {
|
||||
@ -187,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.
|
||||
@ -221,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,
|
||||
}
|
||||
@ -241,11 +342,11 @@ 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); err != nil {
|
||||
if err := s.reportBlock(out, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.reportPending(out); err != nil {
|
||||
@ -259,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()
|
||||
|
||||
@ -273,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{}{
|
||||
@ -292,12 +396,16 @@ func (s *Service) reportLatency(in *json.Decoder, out *json.Encoder) error {
|
||||
|
||||
// blockStats is the information to report about individual blocks.
|
||||
type blockStats struct {
|
||||
Number *big.Int `json:"number"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
Diff string `json:"difficulty"`
|
||||
TotalDiff string `json:"totalDifficulty"`
|
||||
Txs txStats `json:"transactions"`
|
||||
Uncles uncleStats `json:"uncles"`
|
||||
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"`
|
||||
Diff string `json:"difficulty"`
|
||||
TotalDiff string `json:"totalDifficulty"`
|
||||
Txs txStats `json:"transactions"`
|
||||
Uncles uncleStats `json:"uncles"`
|
||||
}
|
||||
|
||||
// txStats is a custom wrapper around a transaction array to force serializing
|
||||
@ -323,42 +431,107 @@ 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) error {
|
||||
// Gather the head block infos from the local blockchain
|
||||
func (s *Service) reportBlock(out *json.Encoder, block *types.Block) error {
|
||||
// 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
|
||||
)
|
||||
if s.eth != nil {
|
||||
// Full nodes have all needed information available
|
||||
block := s.eth.BlockChain().CurrentBlock()
|
||||
|
||||
head = s.eth.BlockChain().CurrentHeader()
|
||||
td = s.eth.BlockChain().GetTd(head.Hash(), head.Number.Uint64())
|
||||
if block == nil {
|
||||
block = s.eth.BlockChain().CurrentBlock()
|
||||
}
|
||||
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
|
||||
head = s.les.BlockChain().CurrentHeader()
|
||||
td = s.les.BlockChain().GetTd(head.Hash(), head.Number.Uint64())
|
||||
if block != nil {
|
||||
header = block.Header()
|
||||
} else {
|
||||
header = s.les.BlockChain().CurrentHeader()
|
||||
}
|
||||
td = s.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
|
||||
}
|
||||
// Assemble the block stats report and send it to the server
|
||||
// Assemble and return the block stats
|
||||
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()
|
||||
}
|
||||
start := head.Number.Int64() - historyUpdateRange
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
for i := uint64(start); i <= head.Number.Uint64(); i++ {
|
||||
indexes = append(indexes, i)
|
||||
}
|
||||
}
|
||||
// Gather the batch of blocks to report
|
||||
history := make([]*blockStats, len(indexes))
|
||||
for i, number := range indexes {
|
||||
if s.eth != nil {
|
||||
history[i] = s.assembleBlockStats(s.eth.BlockChain().GetBlockByNumber(number))
|
||||
} else {
|
||||
history[i] = s.assembleBlockStats(types.NewBlockWithHeader(s.les.BlockChain().GetHeaderByNumber(number)))
|
||||
}
|
||||
}
|
||||
// Assemble the history report and send it to the server
|
||||
stats := map[string]interface{}{
|
||||
"id": s.node,
|
||||
"block": &blockStats{
|
||||
Number: head.Number,
|
||||
Hash: head.Hash(),
|
||||
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
|
||||
|
@ -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)
|
||||
|
@ -1273,8 +1273,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 +1290,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() {
|
||||
|
@ -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)
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
776
les/fetcher.go
776
les/fetcher.go
@ -23,135 +23,374 @@ 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
|
||||
chain *light.LightChain
|
||||
|
||||
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{}
|
||||
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
|
||||
// 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()
|
||||
}
|
||||
//fmt.Println("notify", p.id, head.Number, head.ReorgDepth, head.haveHeaders)
|
||||
if !p.addNotify(head) {
|
||||
//fmt.Println("addNotify fail")
|
||||
f.pm.removePeer(p.id)
|
||||
}
|
||||
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()
|
||||
|
||||
hash := header.Hash()
|
||||
peerList := f.headAnnouncedBy[hash]
|
||||
if peerList == nil {
|
||||
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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
delete(f.headAnnouncedBy, hash)
|
||||
}
|
||||
|
||||
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 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
|
||||
}
|
||||
|
||||
n := fp.lastAnnounced
|
||||
for i := uint64(0); i < head.ReorgDepth; i++ {
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
peer.lock.RUnlock()
|
||||
n = n.parent
|
||||
}
|
||||
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 {
|
||||
// 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
|
||||
}
|
||||
peer.lock.Unlock()
|
||||
// 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
|
||||
}
|
||||
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if n != nil {
|
||||
for n.number < head.Number {
|
||||
nn := &fetcherTreeNode{number: n.number + 1, parent: n}
|
||||
n.children = append(n.children, nn)
|
||||
n = nn
|
||||
fp.nodeCnt++
|
||||
}
|
||||
n.hash = head.Hash
|
||||
n.td = head.Td
|
||||
fp.nodeByHash[n.hash] = n
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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) deliverHeaders(reqID uint64, headers []*types.Header) {
|
||||
f.deliverChn <- fetchResponse{reqID: reqID, headers: headers}
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 fp.bestConfirmed == nil || fp.root == nil || !f.checkKnownNode(p, fp.root) {
|
||||
f.syncing = true
|
||||
go func() {
|
||||
glog.V(logger.Debug).Infof("synchronising with peer %v", p.id)
|
||||
f.pm.synchronise(p)
|
||||
f.syncDone <- p
|
||||
}()
|
||||
return 0, false
|
||||
}
|
||||
|
||||
reqID := getNextReqID()
|
||||
n.requested = true
|
||||
cost := p.GetRequestCost(GetBlockHeadersMsg, int(amount))
|
||||
p.fcServer.SendRequest(reqID, cost)
|
||||
f.reqMu.Lock()
|
||||
f.requested[reqID] = fetchRequest{hash: n.hash, amount: amount, peer: p, sent: mclock.Now()}
|
||||
f.reqMu.Unlock()
|
||||
go p.RequestHeadersByHash(reqID, cost, n.hash, int(amount), 0, true)
|
||||
go func() {
|
||||
time.Sleep(hardRequestTimeout)
|
||||
f.timeoutChn <- reqID
|
||||
}()
|
||||
return reqID, true
|
||||
}
|
||||
|
||||
// requestAmount calculates the amount of headers to be downloaded starting
|
||||
// from a certain head backwards
|
||||
func (f *lightFetcher) requestAmount(p *peer, n *fetcherTreeNode) uint64 {
|
||||
amount := uint64(0)
|
||||
nn := n
|
||||
for nn != nil && !f.checkKnownNode(p, nn) {
|
||||
nn = nn.parent
|
||||
amount++
|
||||
}
|
||||
if nn == nil {
|
||||
amount = n.number
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
// requestedID tells if a certain reqID has been requested by the fetcher
|
||||
func (f *lightFetcher) requestedID(reqID uint64) bool {
|
||||
f.reqMu.RLock()
|
||||
_, ok := f.requested[reqID]
|
||||
@ -159,36 +398,51 @@ func (f *lightFetcher) requestedID(reqID uint64) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
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
|
||||
// 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 amount > 100 {
|
||||
f.syncing = true
|
||||
go func() {
|
||||
//fmt.Println("f.pm.synchronise(p)")
|
||||
f.pm.synchronise(p)
|
||||
//fmt.Println("sync done")
|
||||
f.syncDone <- struct{}{}
|
||||
}()
|
||||
return
|
||||
if bestTd == f.maxConfirmedTd {
|
||||
return nil, nil, 0
|
||||
}
|
||||
|
||||
reqID := f.odr.getNextReqID()
|
||||
f.reqMu.Lock()
|
||||
f.requested[reqID] = fetchRequest{hash: block.Hash, amount: amount, peer: p}
|
||||
f.reqMu.Unlock()
|
||||
cost := p.GetRequestCost(GetBlockHeadersMsg, int(amount))
|
||||
p.fcServer.SendRequest(reqID, cost)
|
||||
go p.RequestHeadersByHash(reqID, cost, block.Hash, int(amount), 0, true)
|
||||
go func() {
|
||||
time.Sleep(hardRequestTimeout)
|
||||
f.timeoutChn <- reqID
|
||||
}()
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
if fp.confirmedTd != nil && (maxTd == nil || maxTd.Cmp(fp.confirmedTd) > 0) {
|
||||
maxTd = fp.confirmedTd
|
||||
}
|
||||
}
|
||||
if maxTd != nil {
|
||||
f.updateMaxConfirmedTd(maxTd)
|
||||
}
|
||||
}
|
||||
|
||||
// checkAnnouncedHeaders updates peer's block tree if necessary after validating
|
||||
// a batch of headers. It searches for the latest header in the batch that has a
|
||||
// matching tree node (if any), and if it has not been marked as known already,
|
||||
// sets it and its parents to known (even those which are older than the currently
|
||||
// validated ones). Return value shows if all hashes, numbers and Tds matched
|
||||
// correctly to the announced values (otherwise the peer should be dropped).
|
||||
func (f *lightFetcher) checkAnnouncedHeaders(fp *fetcherPeerInfo, headers []*types.Header, tds []*big.Int) bool {
|
||||
var (
|
||||
n *fetcherTreeNode
|
||||
header *types.Header
|
||||
td *big.Int
|
||||
)
|
||||
|
||||
for i := len(headers) - 1; ; i-- {
|
||||
if i < 0 {
|
||||
if n == nil {
|
||||
// no more headers and nothing to match
|
||||
return true
|
||||
}
|
||||
// we ran out of recently delivered headers but have not reached a node known by this peer yet, continue matching
|
||||
td = f.chain.GetTd(header.ParentHash, header.Number.Uint64()-1)
|
||||
header = f.chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
|
||||
} else {
|
||||
header = headers[i]
|
||||
td = tds[i]
|
||||
}
|
||||
hash := header.Hash()
|
||||
number := header.Number.Uint64()
|
||||
if n == nil {
|
||||
n = fp.nodeByHash[hash]
|
||||
}
|
||||
if n != nil {
|
||||
if n.td == nil {
|
||||
// node was unannounced
|
||||
if nn := fp.nodeByHash[hash]; nn != nil {
|
||||
// if there was already a node with the same hash, continue there and drop this one
|
||||
nn.children = append(nn.children, n.children...)
|
||||
n.children = nil
|
||||
fp.deleteNode(n)
|
||||
n = nn
|
||||
} else {
|
||||
n.hash = hash
|
||||
n.td = td
|
||||
fp.nodeByHash[hash] = n
|
||||
}
|
||||
}
|
||||
h = h.next
|
||||
}
|
||||
peer.lock.Unlock()
|
||||
if remove {
|
||||
//fmt.Println("checkSync fail")
|
||||
f.pm.removePeer(peer.id)
|
||||
// check if it matches the header
|
||||
if n.hash != hash || n.number != number || n.td.Cmp(td) != 0 {
|
||||
// peer has previously made an invalid announcement
|
||||
return false
|
||||
}
|
||||
if n.known {
|
||||
// we reached a known node that matched our expectations, return with success
|
||||
return true
|
||||
}
|
||||
n.known = true
|
||||
if fp.confirmedTd == nil || td.Cmp(fp.confirmedTd) > 0 {
|
||||
fp.confirmedTd = td
|
||||
fp.bestConfirmed = n
|
||||
}
|
||||
n = n.parent
|
||||
if n == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *lightFetcher) syncLoop() {
|
||||
f.pm.wg.Add(1)
|
||||
defer f.pm.wg.Done()
|
||||
// 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})
|
||||
}
|
||||
}
|
||||
|
||||
srtoNotify := false
|
||||
// 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 {
|
||||
select {
|
||||
case <-f.pm.quitSync:
|
||||
if n.td != nil {
|
||||
delete(fp.nodeByHash, n.hash)
|
||||
}
|
||||
fp.nodeCnt--
|
||||
if len(n.children) == 0 {
|
||||
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, nn := range n.children {
|
||||
if i == 0 {
|
||||
n = nn
|
||||
} else {
|
||||
fp.deleteNode(nn)
|
||||
}
|
||||
case reqID := <-f.timeoutChn:
|
||||
f.reqMu.Lock()
|
||||
req, ok := f.requested[reqID]
|
||||
if ok {
|
||||
delete(f.requested, reqID)
|
||||
}
|
||||
f.reqMu.Unlock()
|
||||
if ok {
|
||||
//fmt.Println("hard timeout")
|
||||
f.pm.removePeer(req.peer.id)
|
||||
}
|
||||
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)
|
||||
}
|
||||
case <-f.syncDone:
|
||||
//fmt.Println("<-f.syncDone", f.syncing)
|
||||
f.checkSyncedHeaders()
|
||||
f.syncing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateStatsEntry items form a linked list that is expanded with a new item every time a new head with a higher Td
|
||||
// than the previous one has been downloaded and validated. The list contains a series of maximum confirmed Td values
|
||||
// and the time these values have been confirmed, both increasing monotonically. A maximum confirmed Td is calculated
|
||||
// both globally for all peers and also for each individual peer (meaning that the given peer has announced the head
|
||||
// and it has also been downloaded from any peer, either before or after the given announcement).
|
||||
// The linked list has a global tail where new confirmed Td entries are added and a separate head for each peer,
|
||||
// pointing to the next Td entry that is higher than the peer's max confirmed Td (nil if it has already confirmed
|
||||
// the current global head).
|
||||
type updateStatsEntry struct {
|
||||
time mclock.AbsTime
|
||||
td *big.Int
|
||||
next *updateStatsEntry
|
||||
}
|
||||
|
||||
// updateMaxConfirmedTd updates the block delay statistics of active peers. Whenever a new highest Td is confirmed,
|
||||
// adds it to the end of a linked list together with the time it has been confirmed. Then checks which peers have
|
||||
// already confirmed a head with the same or higher Td (which counts as zero block delay) and updates their statistics.
|
||||
// Those who have not confirmed such a head by now will be updated by a subsequent checkUpdateStats call with a
|
||||
// positive block delay value.
|
||||
func (f *lightFetcher) updateMaxConfirmedTd(td *big.Int) {
|
||||
if f.maxConfirmedTd == nil || td.Cmp(f.maxConfirmedTd) > 0 {
|
||||
f.maxConfirmedTd = td
|
||||
newEntry := &updateStatsEntry{
|
||||
time: mclock.Now(),
|
||||
td: td,
|
||||
}
|
||||
if f.lastUpdateStats != nil {
|
||||
f.lastUpdateStats.next = newEntry
|
||||
}
|
||||
f.lastUpdateStats = newEntry
|
||||
for p, _ := range f.peers {
|
||||
f.checkUpdateStats(p, newEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkUpdateStats checks those peers who have not confirmed a certain highest Td (or a larger one) by the time it
|
||||
// has been confirmed by another peer. If they have confirmed such a head by now, their stats are updated with the
|
||||
// block delay which is (this peer's confirmation time)-(first confirmation time). After blockDelayTimeout has passed,
|
||||
// the stats are updated with blockDelayTimeout value. In either case, the confirmed or timed out updateStatsEntry
|
||||
// items are removed from the head of the linked list.
|
||||
// If a new entry has been added to the global tail, it is passed as a parameter here even though this function
|
||||
// assumes that it has already been added, so that if the peer's list is empty (all heads confirmed, head is nil),
|
||||
// it can set the new head to newEntry.
|
||||
func (f *lightFetcher) checkUpdateStats(p *peer, newEntry *updateStatsEntry) {
|
||||
now := mclock.Now()
|
||||
fp := f.peers[p]
|
||||
if fp == nil {
|
||||
glog.V(logger.Debug).Infof("checkUpdateStats: unknown peer")
|
||||
return
|
||||
}
|
||||
if newEntry != nil && fp.firstUpdateStats == nil {
|
||||
fp.firstUpdateStats = newEntry
|
||||
}
|
||||
for fp.firstUpdateStats != nil && fp.firstUpdateStats.time <= now-mclock.AbsTime(blockDelayTimeout) {
|
||||
f.pm.serverPool.adjustBlockDelay(p.poolEntry, blockDelayTimeout)
|
||||
fp.firstUpdateStats = fp.firstUpdateStats.next
|
||||
}
|
||||
if fp.confirmedTd != nil {
|
||||
for fp.firstUpdateStats != nil && fp.firstUpdateStats.td.Cmp(fp.confirmedTd) <= 0 {
|
||||
f.pm.serverPool.adjustBlockDelay(p.poolEntry, time.Duration(now-fp.firstUpdateStats.time))
|
||||
fp.firstUpdateStats = fp.firstUpdateStats.next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
117
les/handler.go
117
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)
|
||||
|
||||
|
@ -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) {
|
||||
|
||||
}
|
||||
|
73
les/odr.go
73
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[:])
|
||||
}
|
||||
|
@ -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++
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
84
les/peer.go
84
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
|
||||
}
|
||||
|
||||
|
173
les/randselect.go
Normal file
173
les/randselect.go
Normal file
@ -0,0 +1,173 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package les implements the Light Ethereum Subprotocol.
|
||||
package les
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// wrsItem interface should be implemented by any entries that are to be selected from
|
||||
// a weightedRandomSelect set. Note that recalculating monotonously decreasing item
|
||||
// weights on-demand (without constantly calling update) is allowed
|
||||
type wrsItem interface {
|
||||
Weight() int64
|
||||
}
|
||||
|
||||
// weightedRandomSelect is capable of weighted random selection from a set of items
|
||||
type weightedRandomSelect struct {
|
||||
root *wrsNode
|
||||
idx map[wrsItem]int
|
||||
}
|
||||
|
||||
// newWeightedRandomSelect returns a new weightedRandomSelect structure
|
||||
func newWeightedRandomSelect() *weightedRandomSelect {
|
||||
return &weightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[wrsItem]int)}
|
||||
}
|
||||
|
||||
// update updates an item's weight, adds it if it was non-existent or removes it if
|
||||
// the new weight is zero. Note that explicitly updating decreasing weights is not necessary.
|
||||
func (w *weightedRandomSelect) update(item wrsItem) {
|
||||
w.setWeight(item, item.Weight())
|
||||
}
|
||||
|
||||
// remove removes an item from the set
|
||||
func (w *weightedRandomSelect) remove(item wrsItem) {
|
||||
w.setWeight(item, 0)
|
||||
}
|
||||
|
||||
// setWeight sets an item's weight to a specific value (removes it if zero)
|
||||
func (w *weightedRandomSelect) setWeight(item wrsItem, weight int64) {
|
||||
idx, ok := w.idx[item]
|
||||
if ok {
|
||||
w.root.setWeight(idx, weight)
|
||||
if weight == 0 {
|
||||
delete(w.idx, item)
|
||||
}
|
||||
} else {
|
||||
if weight != 0 {
|
||||
if w.root.itemCnt == w.root.maxItems {
|
||||
// add a new level
|
||||
newRoot := &wrsNode{sumWeight: w.root.sumWeight, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches}
|
||||
newRoot.items[0] = w.root
|
||||
newRoot.weights[0] = w.root.sumWeight
|
||||
w.root = newRoot
|
||||
}
|
||||
w.idx[item] = w.root.insert(item, weight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// choose randomly selects an item from the set, with a chance proportional to its
|
||||
// current weight. If the weight of the chosen element has been decreased since the
|
||||
// last stored value, returns it with a newWeight/oldWeight chance, otherwise just
|
||||
// updates its weight and selects another one
|
||||
func (w *weightedRandomSelect) choose() wrsItem {
|
||||
for {
|
||||
if w.root.sumWeight == 0 {
|
||||
return nil
|
||||
}
|
||||
val := rand.Int63n(w.root.sumWeight)
|
||||
choice, lastWeight := w.root.choose(val)
|
||||
weight := choice.Weight()
|
||||
if weight != lastWeight {
|
||||
w.setWeight(choice, weight)
|
||||
}
|
||||
if weight >= lastWeight || rand.Int63n(lastWeight) < weight {
|
||||
return choice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const wrsBranches = 8 // max number of branches in the wrsNode tree
|
||||
|
||||
// wrsNode is a node of a tree structure that can store wrsItems or further wrsNodes.
|
||||
type wrsNode struct {
|
||||
items [wrsBranches]interface{}
|
||||
weights [wrsBranches]int64
|
||||
sumWeight int64
|
||||
level, itemCnt, maxItems int
|
||||
}
|
||||
|
||||
// insert recursively inserts a new item to the tree and returns the item index
|
||||
func (n *wrsNode) insert(item wrsItem, weight int64) int {
|
||||
branch := 0
|
||||
for n.items[branch] != nil && (n.level == 0 || n.items[branch].(*wrsNode).itemCnt == n.items[branch].(*wrsNode).maxItems) {
|
||||
branch++
|
||||
if branch == wrsBranches {
|
||||
panic(nil)
|
||||
}
|
||||
}
|
||||
n.itemCnt++
|
||||
n.sumWeight += weight
|
||||
n.weights[branch] += weight
|
||||
if n.level == 0 {
|
||||
n.items[branch] = item
|
||||
return branch
|
||||
} else {
|
||||
var subNode *wrsNode
|
||||
if n.items[branch] == nil {
|
||||
subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1}
|
||||
n.items[branch] = subNode
|
||||
} else {
|
||||
subNode = n.items[branch].(*wrsNode)
|
||||
}
|
||||
subIdx := subNode.insert(item, weight)
|
||||
return subNode.maxItems*branch + subIdx
|
||||
}
|
||||
}
|
||||
|
||||
// setWeight updates the weight of a certain item (which should exist) and returns
|
||||
// the change of the last weight value stored in the tree
|
||||
func (n *wrsNode) setWeight(idx int, weight int64) int64 {
|
||||
if n.level == 0 {
|
||||
oldWeight := n.weights[idx]
|
||||
n.weights[idx] = weight
|
||||
diff := weight - oldWeight
|
||||
n.sumWeight += diff
|
||||
if weight == 0 {
|
||||
n.items[idx] = nil
|
||||
n.itemCnt--
|
||||
}
|
||||
return diff
|
||||
}
|
||||
branchItems := n.maxItems / wrsBranches
|
||||
branch := idx / branchItems
|
||||
diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight)
|
||||
n.weights[branch] += diff
|
||||
n.sumWeight += diff
|
||||
if weight == 0 {
|
||||
n.itemCnt--
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
// choose recursively selects an item from the tree and returns it along with its weight
|
||||
func (n *wrsNode) choose(val int64) (wrsItem, int64) {
|
||||
for i, w := range n.weights {
|
||||
if val < w {
|
||||
if n.level == 0 {
|
||||
return n.items[i].(wrsItem), n.weights[i]
|
||||
} else {
|
||||
return n.items[i].(*wrsNode).choose(val)
|
||||
}
|
||||
} else {
|
||||
val -= w
|
||||
}
|
||||
}
|
||||
panic(nil)
|
||||
}
|
67
les/randselect_test.go
Normal file
67
les/randselect_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package les
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testWrsItem struct {
|
||||
idx int
|
||||
widx *int
|
||||
}
|
||||
|
||||
func (t *testWrsItem) Weight() int64 {
|
||||
w := *t.widx
|
||||
if w == -1 || w == t.idx {
|
||||
return int64(t.idx + 1)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func TestWeightedRandomSelect(t *testing.T) {
|
||||
testFn := func(cnt int) {
|
||||
s := newWeightedRandomSelect()
|
||||
w := -1
|
||||
list := make([]testWrsItem, cnt)
|
||||
for i, _ := range list {
|
||||
list[i] = testWrsItem{idx: i, widx: &w}
|
||||
s.update(&list[i])
|
||||
}
|
||||
w = rand.Intn(cnt)
|
||||
c := s.choose()
|
||||
if c == nil {
|
||||
t.Errorf("expected item, got nil")
|
||||
} else {
|
||||
if c.(*testWrsItem).idx != w {
|
||||
t.Errorf("expected another item")
|
||||
}
|
||||
}
|
||||
w = -2
|
||||
if s.choose() != nil {
|
||||
t.Errorf("expected nil, got item")
|
||||
}
|
||||
}
|
||||
testFn(1)
|
||||
testFn(10)
|
||||
testFn(100)
|
||||
testFn(1000)
|
||||
testFn(10000)
|
||||
testFn(100000)
|
||||
testFn(1000000)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
766
les/serverpool.go
Normal file
766
les/serverpool.go
Normal file
@ -0,0 +1,766 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package les implements the Light Ethereum Subprotocol.
|
||||
package les
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
const (
|
||||
// After a connection has been ended or timed out, there is a waiting period
|
||||
// before it can be selected for connection again.
|
||||
// waiting period = base delay * (1 + random(1))
|
||||
// base delay = shortRetryDelay for the first shortRetryCnt times after a
|
||||
// successful connection, after that longRetryDelay is applied
|
||||
shortRetryCnt = 5
|
||||
shortRetryDelay = time.Second * 5
|
||||
longRetryDelay = time.Minute * 10
|
||||
// maxNewEntries is the maximum number of newly discovered (never connected) nodes.
|
||||
// If the limit is reached, the least recently discovered one is thrown out.
|
||||
maxNewEntries = 1000
|
||||
// maxKnownEntries is the maximum number of known (already connected) nodes.
|
||||
// If the limit is reached, the least recently connected one is thrown out.
|
||||
// (not that unlike new entries, known entries are persistent)
|
||||
maxKnownEntries = 1000
|
||||
// target for simultaneously connected servers
|
||||
targetServerCount = 5
|
||||
// target for servers selected from the known table
|
||||
// (we leave room for trying new ones if there is any)
|
||||
targetKnownSelect = 3
|
||||
// after dialTimeout, consider the server unavailable and adjust statistics
|
||||
dialTimeout = time.Second * 30
|
||||
// targetConnTime is the minimum expected connection duration before a server
|
||||
// drops a client without any specific reason
|
||||
targetConnTime = time.Minute * 10
|
||||
// new entry selection weight calculation based on most recent discovery time:
|
||||
// unity until discoverExpireStart, then exponential decay with discoverExpireConst
|
||||
discoverExpireStart = time.Minute * 20
|
||||
discoverExpireConst = time.Minute * 20
|
||||
// known entry selection weight is dropped by a factor of exp(-failDropLn) after
|
||||
// each unsuccessful connection (restored after a successful one)
|
||||
failDropLn = 0.1
|
||||
// known node connection success and quality statistics have a long term average
|
||||
// and a short term value which is adjusted exponentially with a factor of
|
||||
// pstatRecentAdjust with each dial/connection and also returned exponentially
|
||||
// to the average with the time constant pstatReturnToMeanTC
|
||||
pstatRecentAdjust = 0.1
|
||||
pstatReturnToMeanTC = time.Hour
|
||||
// node address selection weight is dropped by a factor of exp(-addrFailDropLn) after
|
||||
// each unsuccessful connection (restored after a successful one)
|
||||
addrFailDropLn = math.Ln2
|
||||
// responseScoreTC and delayScoreTC are exponential decay time constants for
|
||||
// calculating selection chances from response times and block delay times
|
||||
responseScoreTC = time.Millisecond * 100
|
||||
delayScoreTC = time.Second * 5
|
||||
timeoutPow = 10
|
||||
// peerSelectMinWeight is added to calculated weights at request peer selection
|
||||
// to give poorly performing peers a little chance of coming back
|
||||
peerSelectMinWeight = 0.005
|
||||
// initStatsWeight is used to initialize previously unknown peers with good
|
||||
// statistics to give a chance to prove themselves
|
||||
initStatsWeight = 1
|
||||
)
|
||||
|
||||
// serverPool implements a pool for storing and selecting newly discovered and already
|
||||
// known light server nodes. It received discovered nodes, stores statistics about
|
||||
// known nodes and takes care of always having enough good quality servers connected.
|
||||
type serverPool struct {
|
||||
db ethdb.Database
|
||||
dbKey []byte
|
||||
server *p2p.Server
|
||||
quit chan struct{}
|
||||
wg *sync.WaitGroup
|
||||
connWg sync.WaitGroup
|
||||
|
||||
discSetPeriod chan time.Duration
|
||||
discNodes chan *discv5.Node
|
||||
discLookups chan bool
|
||||
|
||||
entries map[discover.NodeID]*poolEntry
|
||||
lock sync.Mutex
|
||||
timeout, enableRetry chan *poolEntry
|
||||
adjustStats chan poolStatAdjust
|
||||
|
||||
knownQueue, newQueue poolEntryQueue
|
||||
knownSelect, newSelect *weightedRandomSelect
|
||||
knownSelected, newSelected int
|
||||
fastDiscover bool
|
||||
}
|
||||
|
||||
// newServerPool creates a new serverPool instance
|
||||
func newServerPool(db ethdb.Database, dbPrefix []byte, server *p2p.Server, topic discv5.Topic, quit chan struct{}, wg *sync.WaitGroup) *serverPool {
|
||||
pool := &serverPool{
|
||||
db: db,
|
||||
dbKey: append(dbPrefix, []byte(topic)...),
|
||||
server: server,
|
||||
quit: quit,
|
||||
wg: wg,
|
||||
entries: make(map[discover.NodeID]*poolEntry),
|
||||
timeout: make(chan *poolEntry, 1),
|
||||
adjustStats: make(chan poolStatAdjust, 100),
|
||||
enableRetry: make(chan *poolEntry, 1),
|
||||
knownSelect: newWeightedRandomSelect(),
|
||||
newSelect: newWeightedRandomSelect(),
|
||||
fastDiscover: true,
|
||||
}
|
||||
pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry)
|
||||
pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry)
|
||||
wg.Add(1)
|
||||
pool.loadNodes()
|
||||
pool.checkDial()
|
||||
|
||||
if pool.server.DiscV5 != nil {
|
||||
pool.discSetPeriod = make(chan time.Duration, 1)
|
||||
pool.discNodes = make(chan *discv5.Node, 100)
|
||||
pool.discLookups = make(chan bool, 100)
|
||||
go pool.server.DiscV5.SearchTopic(topic, pool.discSetPeriod, pool.discNodes, pool.discLookups)
|
||||
}
|
||||
|
||||
go pool.eventLoop()
|
||||
return pool
|
||||
}
|
||||
|
||||
// connect should be called upon any incoming connection. If the connection has been
|
||||
// dialed by the server pool recently, the appropriate pool entry is returned.
|
||||
// Otherwise, the connection should be rejected.
|
||||
// Note that whenever a connection has been accepted and a pool entry has been returned,
|
||||
// disconnect should also always be called.
|
||||
func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry {
|
||||
pool.lock.Lock()
|
||||
defer pool.lock.Unlock()
|
||||
entry := pool.entries[p.ID()]
|
||||
if entry == nil {
|
||||
return nil
|
||||
}
|
||||
glog.V(logger.Debug).Infof("connecting to %v, state: %v", p.id, entry.state)
|
||||
if entry.state != psDialed {
|
||||
return nil
|
||||
}
|
||||
pool.connWg.Add(1)
|
||||
entry.peer = p
|
||||
entry.state = psConnected
|
||||
addr := &poolEntryAddress{
|
||||
ip: ip,
|
||||
port: port,
|
||||
lastSeen: mclock.Now(),
|
||||
}
|
||||
entry.lastConnected = addr
|
||||
entry.addr = make(map[string]*poolEntryAddress)
|
||||
entry.addr[addr.strKey()] = addr
|
||||
entry.addrSelect = *newWeightedRandomSelect()
|
||||
entry.addrSelect.update(addr)
|
||||
return entry
|
||||
}
|
||||
|
||||
// registered should be called after a successful handshake
|
||||
func (pool *serverPool) registered(entry *poolEntry) {
|
||||
glog.V(logger.Debug).Infof("registered %v", entry.id.String())
|
||||
pool.lock.Lock()
|
||||
defer pool.lock.Unlock()
|
||||
|
||||
entry.state = psRegistered
|
||||
entry.regTime = mclock.Now()
|
||||
if !entry.known {
|
||||
pool.newQueue.remove(entry)
|
||||
entry.known = true
|
||||
}
|
||||
pool.knownQueue.setLatest(entry)
|
||||
entry.shortRetry = shortRetryCnt
|
||||
}
|
||||
|
||||
// disconnect should be called when ending a connection. Service quality statistics
|
||||
// can be updated optionally (not updated if no registration happened, in this case
|
||||
// only connection statistics are updated, just like in case of timeout)
|
||||
func (pool *serverPool) disconnect(entry *poolEntry) {
|
||||
glog.V(logger.Debug).Infof("disconnected %v", entry.id.String())
|
||||
pool.lock.Lock()
|
||||
defer pool.lock.Unlock()
|
||||
|
||||
if entry.state == psRegistered {
|
||||
connTime := mclock.Now() - entry.regTime
|
||||
connAdjust := float64(connTime) / float64(targetConnTime)
|
||||
if connAdjust > 1 {
|
||||
connAdjust = 1
|
||||
}
|
||||
stopped := false
|
||||
select {
|
||||
case <-pool.quit:
|
||||
stopped = true
|
||||
default:
|
||||
}
|
||||
if stopped {
|
||||
entry.connectStats.add(1, connAdjust)
|
||||
} else {
|
||||
entry.connectStats.add(connAdjust, 1)
|
||||
}
|
||||
}
|
||||
|
||||
entry.state = psNotConnected
|
||||
if entry.knownSelected {
|
||||
pool.knownSelected--
|
||||
} else {
|
||||
pool.newSelected--
|
||||
}
|
||||
pool.setRetryDial(entry)
|
||||
pool.connWg.Done()
|
||||
}
|
||||
|
||||
const (
|
||||
pseBlockDelay = iota
|
||||
pseResponseTime
|
||||
pseResponseTimeout
|
||||
)
|
||||
|
||||
// poolStatAdjust records are sent to adjust peer block delay/response time statistics
|
||||
type poolStatAdjust struct {
|
||||
adjustType int
|
||||
entry *poolEntry
|
||||
time time.Duration
|
||||
}
|
||||
|
||||
// adjustBlockDelay adjusts the block announce delay statistics of a node
|
||||
func (pool *serverPool) adjustBlockDelay(entry *poolEntry, time time.Duration) {
|
||||
pool.adjustStats <- poolStatAdjust{pseBlockDelay, entry, time}
|
||||
}
|
||||
|
||||
// adjustResponseTime adjusts the request response time statistics of a node
|
||||
func (pool *serverPool) adjustResponseTime(entry *poolEntry, time time.Duration, timeout bool) {
|
||||
if timeout {
|
||||
pool.adjustStats <- poolStatAdjust{pseResponseTimeout, entry, time}
|
||||
} else {
|
||||
pool.adjustStats <- poolStatAdjust{pseResponseTime, entry, time}
|
||||
}
|
||||
}
|
||||
|
||||
type selectPeerItem struct {
|
||||
peer *peer
|
||||
weight int64
|
||||
}
|
||||
|
||||
func (sp selectPeerItem) Weight() int64 {
|
||||
return sp.weight
|
||||
}
|
||||
|
||||
// selectPeer selects a suitable peer for a request
|
||||
func (pool *serverPool) selectPeer(canSend func(*peer) (bool, uint64)) *peer {
|
||||
pool.lock.Lock()
|
||||
defer pool.lock.Unlock()
|
||||
|
||||
sel := newWeightedRandomSelect()
|
||||
for _, entry := range pool.entries {
|
||||
if entry.state == psRegistered {
|
||||
p := entry.peer
|
||||
ok, cost := canSend(p)
|
||||
if ok {
|
||||
w := int64(1000000000 * (peerSelectMinWeight + math.Exp(-(entry.responseStats.recentAvg()+float64(cost))/float64(responseScoreTC))*math.Pow((1-entry.timeoutStats.recentAvg()), timeoutPow)))
|
||||
sel.update(selectPeerItem{peer: p, weight: w})
|
||||
}
|
||||
}
|
||||
}
|
||||
choice := sel.choose()
|
||||
if choice == nil {
|
||||
return nil
|
||||
}
|
||||
return choice.(selectPeerItem).peer
|
||||
}
|
||||
|
||||
// eventLoop handles pool events and mutex locking for all internal functions
|
||||
func (pool *serverPool) eventLoop() {
|
||||
lookupCnt := 0
|
||||
var convTime mclock.AbsTime
|
||||
pool.discSetPeriod <- time.Millisecond * 100
|
||||
for {
|
||||
select {
|
||||
case entry := <-pool.timeout:
|
||||
pool.lock.Lock()
|
||||
if !entry.removed {
|
||||
pool.checkDialTimeout(entry)
|
||||
}
|
||||
pool.lock.Unlock()
|
||||
|
||||
case entry := <-pool.enableRetry:
|
||||
pool.lock.Lock()
|
||||
if !entry.removed {
|
||||
entry.delayedRetry = false
|
||||
pool.updateCheckDial(entry)
|
||||
}
|
||||
pool.lock.Unlock()
|
||||
|
||||
case adj := <-pool.adjustStats:
|
||||
pool.lock.Lock()
|
||||
switch adj.adjustType {
|
||||
case pseBlockDelay:
|
||||
adj.entry.delayStats.add(float64(adj.time), 1)
|
||||
case pseResponseTime:
|
||||
adj.entry.responseStats.add(float64(adj.time), 1)
|
||||
adj.entry.timeoutStats.add(0, 1)
|
||||
case pseResponseTimeout:
|
||||
adj.entry.timeoutStats.add(1, 1)
|
||||
}
|
||||
pool.lock.Unlock()
|
||||
|
||||
case node := <-pool.discNodes:
|
||||
pool.lock.Lock()
|
||||
now := mclock.Now()
|
||||
id := discover.NodeID(node.ID)
|
||||
entry := pool.entries[id]
|
||||
if entry == nil {
|
||||
glog.V(logger.Debug).Infof("discovered %v", node.String())
|
||||
entry = &poolEntry{
|
||||
id: id,
|
||||
addr: make(map[string]*poolEntryAddress),
|
||||
addrSelect: *newWeightedRandomSelect(),
|
||||
shortRetry: shortRetryCnt,
|
||||
}
|
||||
pool.entries[id] = entry
|
||||
// initialize previously unknown peers with good statistics to give a chance to prove themselves
|
||||
entry.connectStats.add(1, initStatsWeight)
|
||||
entry.delayStats.add(0, initStatsWeight)
|
||||
entry.responseStats.add(0, initStatsWeight)
|
||||
entry.timeoutStats.add(0, initStatsWeight)
|
||||
}
|
||||
entry.lastDiscovered = now
|
||||
addr := &poolEntryAddress{
|
||||
ip: node.IP,
|
||||
port: node.TCP,
|
||||
}
|
||||
if a, ok := entry.addr[addr.strKey()]; ok {
|
||||
addr = a
|
||||
} else {
|
||||
entry.addr[addr.strKey()] = addr
|
||||
}
|
||||
addr.lastSeen = now
|
||||
entry.addrSelect.update(addr)
|
||||
if !entry.known {
|
||||
pool.newQueue.setLatest(entry)
|
||||
}
|
||||
pool.updateCheckDial(entry)
|
||||
pool.lock.Unlock()
|
||||
|
||||
case conv := <-pool.discLookups:
|
||||
if conv {
|
||||
if lookupCnt == 0 {
|
||||
convTime = mclock.Now()
|
||||
}
|
||||
lookupCnt++
|
||||
if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) {
|
||||
pool.fastDiscover = false
|
||||
pool.discSetPeriod <- time.Minute
|
||||
}
|
||||
}
|
||||
|
||||
case <-pool.quit:
|
||||
close(pool.discSetPeriod)
|
||||
pool.connWg.Wait()
|
||||
pool.saveNodes()
|
||||
pool.wg.Done()
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loadNodes loads known nodes and their statistics from the database
|
||||
func (pool *serverPool) loadNodes() {
|
||||
enc, err := pool.db.Get(pool.dbKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var list []*poolEntry
|
||||
err = rlp.DecodeBytes(enc, &list)
|
||||
if err != nil {
|
||||
glog.V(logger.Debug).Infof("node list decode error: %v", err)
|
||||
return
|
||||
}
|
||||
for _, e := range list {
|
||||
glog.V(logger.Debug).Infof("loaded server stats %016x fails: %v connStats: %v / %v delayStats: %v / %v responseStats: %v / %v timeoutStats: %v / %v", e.id[0:8], e.lastConnected.fails, e.connectStats.avg, e.connectStats.weight, time.Duration(e.delayStats.avg), e.delayStats.weight, time.Duration(e.responseStats.avg), e.responseStats.weight, e.timeoutStats.avg, e.timeoutStats.weight)
|
||||
pool.entries[e.id] = e
|
||||
pool.knownQueue.setLatest(e)
|
||||
pool.knownSelect.update((*knownEntry)(e))
|
||||
}
|
||||
}
|
||||
|
||||
// saveNodes saves known nodes and their statistics into the database. Nodes are
|
||||
// ordered from least to most recently connected.
|
||||
func (pool *serverPool) saveNodes() {
|
||||
list := make([]*poolEntry, len(pool.knownQueue.queue))
|
||||
for i, _ := range list {
|
||||
list[i] = pool.knownQueue.fetchOldest()
|
||||
}
|
||||
enc, err := rlp.EncodeToBytes(list)
|
||||
if err == nil {
|
||||
pool.db.Put(pool.dbKey, enc)
|
||||
}
|
||||
}
|
||||
|
||||
// removeEntry removes a pool entry when the entry count limit is reached.
|
||||
// Note that it is called by the new/known queues from which the entry has already
|
||||
// been removed so removing it from the queues is not necessary.
|
||||
func (pool *serverPool) removeEntry(entry *poolEntry) {
|
||||
pool.newSelect.remove((*discoveredEntry)(entry))
|
||||
pool.knownSelect.remove((*knownEntry)(entry))
|
||||
entry.removed = true
|
||||
delete(pool.entries, entry.id)
|
||||
}
|
||||
|
||||
// setRetryDial starts the timer which will enable dialing a certain node again
|
||||
func (pool *serverPool) setRetryDial(entry *poolEntry) {
|
||||
delay := longRetryDelay
|
||||
if entry.shortRetry > 0 {
|
||||
entry.shortRetry--
|
||||
delay = shortRetryDelay
|
||||
}
|
||||
delay += time.Duration(rand.Int63n(int64(delay) + 1))
|
||||
entry.delayedRetry = true
|
||||
go func() {
|
||||
select {
|
||||
case <-pool.quit:
|
||||
case <-time.After(delay):
|
||||
select {
|
||||
case <-pool.quit:
|
||||
case pool.enableRetry <- entry:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// updateCheckDial is called when an entry can potentially be dialed again. It updates
|
||||
// its selection weights and checks if new dials can/should be made.
|
||||
func (pool *serverPool) updateCheckDial(entry *poolEntry) {
|
||||
pool.newSelect.update((*discoveredEntry)(entry))
|
||||
pool.knownSelect.update((*knownEntry)(entry))
|
||||
pool.checkDial()
|
||||
}
|
||||
|
||||
// checkDial checks if new dials can/should be made. It tries to select servers both
|
||||
// based on good statistics and recent discovery.
|
||||
func (pool *serverPool) checkDial() {
|
||||
fillWithKnownSelects := !pool.fastDiscover
|
||||
for pool.knownSelected < targetKnownSelect {
|
||||
entry := pool.knownSelect.choose()
|
||||
if entry == nil {
|
||||
fillWithKnownSelects = false
|
||||
break
|
||||
}
|
||||
pool.dial((*poolEntry)(entry.(*knownEntry)), true)
|
||||
}
|
||||
for pool.knownSelected+pool.newSelected < targetServerCount {
|
||||
entry := pool.newSelect.choose()
|
||||
if entry == nil {
|
||||
break
|
||||
}
|
||||
pool.dial((*poolEntry)(entry.(*discoveredEntry)), false)
|
||||
}
|
||||
if fillWithKnownSelects {
|
||||
// no more newly discovered nodes to select and since fast discover period
|
||||
// is over, we probably won't find more in the near future so select more
|
||||
// known entries if possible
|
||||
for pool.knownSelected < targetServerCount {
|
||||
entry := pool.knownSelect.choose()
|
||||
if entry == nil {
|
||||
break
|
||||
}
|
||||
pool.dial((*poolEntry)(entry.(*knownEntry)), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dial initiates a new connection
|
||||
func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) {
|
||||
if entry.state != psNotConnected {
|
||||
return
|
||||
}
|
||||
entry.state = psDialed
|
||||
entry.knownSelected = knownSelected
|
||||
if knownSelected {
|
||||
pool.knownSelected++
|
||||
} else {
|
||||
pool.newSelected++
|
||||
}
|
||||
addr := entry.addrSelect.choose().(*poolEntryAddress)
|
||||
glog.V(logger.Debug).Infof("dialing %v out of %v, known: %v", entry.id.String()+"@"+addr.strKey(), len(entry.addr), knownSelected)
|
||||
entry.dialed = addr
|
||||
go func() {
|
||||
pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port))
|
||||
select {
|
||||
case <-pool.quit:
|
||||
case <-time.After(dialTimeout):
|
||||
select {
|
||||
case <-pool.quit:
|
||||
case pool.timeout <- entry:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// checkDialTimeout checks if the node is still in dialed state and if so, resets it
|
||||
// and adjusts connection statistics accordingly.
|
||||
func (pool *serverPool) checkDialTimeout(entry *poolEntry) {
|
||||
if entry.state != psDialed {
|
||||
return
|
||||
}
|
||||
glog.V(logger.Debug).Infof("timeout %v", entry.id.String()+"@"+entry.dialed.strKey())
|
||||
entry.state = psNotConnected
|
||||
if entry.knownSelected {
|
||||
pool.knownSelected--
|
||||
} else {
|
||||
pool.newSelected--
|
||||
}
|
||||
entry.connectStats.add(0, 1)
|
||||
entry.dialed.fails++
|
||||
pool.setRetryDial(entry)
|
||||
}
|
||||
|
||||
const (
|
||||
psNotConnected = iota
|
||||
psDialed
|
||||
psConnected
|
||||
psRegistered
|
||||
)
|
||||
|
||||
// poolEntry represents a server node and stores its current state and statistics.
|
||||
type poolEntry struct {
|
||||
peer *peer
|
||||
id discover.NodeID
|
||||
addr map[string]*poolEntryAddress
|
||||
lastConnected, dialed *poolEntryAddress
|
||||
addrSelect weightedRandomSelect
|
||||
|
||||
lastDiscovered mclock.AbsTime
|
||||
known, knownSelected bool
|
||||
connectStats, delayStats poolStats
|
||||
responseStats, timeoutStats poolStats
|
||||
state int
|
||||
regTime mclock.AbsTime
|
||||
queueIdx int
|
||||
removed bool
|
||||
|
||||
delayedRetry bool
|
||||
shortRetry int
|
||||
}
|
||||
|
||||
func (e *poolEntry) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats})
|
||||
}
|
||||
|
||||
func (e *poolEntry) DecodeRLP(s *rlp.Stream) error {
|
||||
var entry struct {
|
||||
ID discover.NodeID
|
||||
IP net.IP
|
||||
Port uint16
|
||||
Fails uint
|
||||
CStat, DStat, RStat, TStat poolStats
|
||||
}
|
||||
if err := s.Decode(&entry); err != nil {
|
||||
return err
|
||||
}
|
||||
addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()}
|
||||
e.id = entry.ID
|
||||
e.addr = make(map[string]*poolEntryAddress)
|
||||
e.addr[addr.strKey()] = addr
|
||||
e.addrSelect = *newWeightedRandomSelect()
|
||||
e.addrSelect.update(addr)
|
||||
e.lastConnected = addr
|
||||
e.connectStats = entry.CStat
|
||||
e.delayStats = entry.DStat
|
||||
e.responseStats = entry.RStat
|
||||
e.timeoutStats = entry.TStat
|
||||
e.shortRetry = shortRetryCnt
|
||||
e.known = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// discoveredEntry implements wrsItem
|
||||
type discoveredEntry poolEntry
|
||||
|
||||
// Weight calculates random selection weight for newly discovered entries
|
||||
func (e *discoveredEntry) Weight() int64 {
|
||||
if e.state != psNotConnected || e.delayedRetry {
|
||||
return 0
|
||||
}
|
||||
t := time.Duration(mclock.Now() - e.lastDiscovered)
|
||||
if t <= discoverExpireStart {
|
||||
return 1000000000
|
||||
} else {
|
||||
return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst)))
|
||||
}
|
||||
}
|
||||
|
||||
// knownEntry implements wrsItem
|
||||
type knownEntry poolEntry
|
||||
|
||||
// Weight calculates random selection weight for known entries
|
||||
func (e *knownEntry) Weight() int64 {
|
||||
if e.state != psNotConnected || !e.known || e.delayedRetry {
|
||||
return 0
|
||||
}
|
||||
return int64(1000000000 * e.connectStats.recentAvg() * math.Exp(-float64(e.lastConnected.fails)*failDropLn-e.responseStats.recentAvg()/float64(responseScoreTC)-e.delayStats.recentAvg()/float64(delayScoreTC)) * math.Pow((1-e.timeoutStats.recentAvg()), timeoutPow))
|
||||
}
|
||||
|
||||
// poolEntryAddress is a separate object because currently it is necessary to remember
|
||||
// multiple potential network addresses for a pool entry. This will be removed after
|
||||
// the final implementation of v5 discovery which will retrieve signed and serial
|
||||
// numbered advertisements, making it clear which IP/port is the latest one.
|
||||
type poolEntryAddress struct {
|
||||
ip net.IP
|
||||
port uint16
|
||||
lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db
|
||||
fails uint // connection failures since last successful connection (persistent)
|
||||
}
|
||||
|
||||
func (a *poolEntryAddress) Weight() int64 {
|
||||
t := time.Duration(mclock.Now() - a.lastSeen)
|
||||
return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1
|
||||
}
|
||||
|
||||
func (a *poolEntryAddress) strKey() string {
|
||||
return a.ip.String() + ":" + strconv.Itoa(int(a.port))
|
||||
}
|
||||
|
||||
// poolStats implement statistics for a certain quantity with a long term average
|
||||
// and a short term value which is adjusted exponentially with a factor of
|
||||
// pstatRecentAdjust with each update and also returned exponentially to the
|
||||
// average with the time constant pstatReturnToMeanTC
|
||||
type poolStats struct {
|
||||
sum, weight, avg, recent float64
|
||||
lastRecalc mclock.AbsTime
|
||||
}
|
||||
|
||||
// init initializes stats with a long term sum/update count pair retrieved from the database
|
||||
func (s *poolStats) init(sum, weight float64) {
|
||||
s.sum = sum
|
||||
s.weight = weight
|
||||
var avg float64
|
||||
if weight > 0 {
|
||||
avg = s.sum / weight
|
||||
}
|
||||
s.avg = avg
|
||||
s.recent = avg
|
||||
s.lastRecalc = mclock.Now()
|
||||
}
|
||||
|
||||
// recalc recalculates recent value return-to-mean and long term average
|
||||
func (s *poolStats) recalc() {
|
||||
now := mclock.Now()
|
||||
s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC))
|
||||
if s.sum == 0 {
|
||||
s.avg = 0
|
||||
} else {
|
||||
if s.sum > s.weight*1e30 {
|
||||
s.avg = 1e30
|
||||
} else {
|
||||
s.avg = s.sum / s.weight
|
||||
}
|
||||
}
|
||||
s.lastRecalc = now
|
||||
}
|
||||
|
||||
// add updates the stats with a new value
|
||||
func (s *poolStats) add(value, weight float64) {
|
||||
s.weight += weight
|
||||
s.sum += value * weight
|
||||
s.recalc()
|
||||
}
|
||||
|
||||
// recentAvg returns the short-term adjusted average
|
||||
func (s *poolStats) recentAvg() float64 {
|
||||
s.recalc()
|
||||
return s.recent
|
||||
}
|
||||
|
||||
func (s *poolStats) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)})
|
||||
}
|
||||
|
||||
func (s *poolStats) DecodeRLP(st *rlp.Stream) error {
|
||||
var stats struct {
|
||||
SumUint, WeightUint uint64
|
||||
}
|
||||
if err := st.Decode(&stats); err != nil {
|
||||
return err
|
||||
}
|
||||
s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint))
|
||||
return nil
|
||||
}
|
||||
|
||||
// poolEntryQueue keeps track of its least recently accessed entries and removes
|
||||
// them when the number of entries reaches the limit
|
||||
type poolEntryQueue struct {
|
||||
queue map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value
|
||||
newPtr, oldPtr, maxCnt int
|
||||
removeFromPool func(*poolEntry)
|
||||
}
|
||||
|
||||
// newPoolEntryQueue returns a new poolEntryQueue
|
||||
func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue {
|
||||
return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool}
|
||||
}
|
||||
|
||||
// fetchOldest returns and removes the least recently accessed entry
|
||||
func (q *poolEntryQueue) fetchOldest() *poolEntry {
|
||||
if len(q.queue) == 0 {
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
if e := q.queue[q.oldPtr]; e != nil {
|
||||
delete(q.queue, q.oldPtr)
|
||||
q.oldPtr++
|
||||
return e
|
||||
}
|
||||
q.oldPtr++
|
||||
}
|
||||
}
|
||||
|
||||
// remove removes an entry from the queue
|
||||
func (q *poolEntryQueue) remove(entry *poolEntry) {
|
||||
if q.queue[entry.queueIdx] == entry {
|
||||
delete(q.queue, entry.queueIdx)
|
||||
}
|
||||
}
|
||||
|
||||
// setLatest adds or updates a recently accessed entry. It also checks if an old entry
|
||||
// needs to be removed and removes it from the parent pool too with a callback function.
|
||||
func (q *poolEntryQueue) setLatest(entry *poolEntry) {
|
||||
if q.queue[entry.queueIdx] == entry {
|
||||
delete(q.queue, entry.queueIdx)
|
||||
} else {
|
||||
if len(q.queue) == q.maxCnt {
|
||||
e := q.fetchOldest()
|
||||
q.remove(e)
|
||||
q.removeFromPool(e)
|
||||
}
|
||||
}
|
||||
entry.queueIdx = q.newPtr
|
||||
q.queue[entry.queueIdx] = entry
|
||||
q.newPtr++
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
15
light/odr.go
15
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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...)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -500,7 +500,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 +510,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
|
||||
|
119
light/vm_env.go
119
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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
@ -187,6 +183,15 @@ func (self *Miner) Pending() (*types.Block, *state.StateDB) {
|
||||
return self.worker.pending()
|
||||
}
|
||||
|
||||
// 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
|
||||
// change between multiple method calls
|
||||
func (self *Miner) PendingBlock() *types.Block {
|
||||
return self.worker.pendingBlock()
|
||||
}
|
||||
|
||||
func (self *Miner) SetEtherbase(addr common.Address) {
|
||||
self.coinbase = addr
|
||||
self.worker.setEtherbase(addr)
|
||||
|
@ -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()
|
||||
|
118
miner/unconfirmed.go
Normal file
118
miner/unconfirmed.go
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package miner
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
// headerRetriever is used by the unconfirmed block set to verify whether a previously
|
||||
// mined block is part of the canonical chain or not.
|
||||
type headerRetriever interface {
|
||||
// GetHeaderByNumber retrieves the canonical header associated with a block number.
|
||||
GetHeaderByNumber(number uint64) *types.Header
|
||||
}
|
||||
|
||||
// unconfirmedBlock is a small collection of metadata about a locally mined block
|
||||
// that is placed into a unconfirmed set for canonical chain inclusion tracking.
|
||||
type unconfirmedBlock struct {
|
||||
index uint64
|
||||
hash common.Hash
|
||||
}
|
||||
|
||||
// unconfirmedBlocks implements a data structure to maintain locally mined blocks
|
||||
// have have not yet reached enough maturity to guarantee chain inclusion. It is
|
||||
// used by the miner to provide logs to the user when a previously mined block
|
||||
// has a high enough guarantee to not be reorged out of te canonical chain.
|
||||
type unconfirmedBlocks struct {
|
||||
chain headerRetriever // Blockchain to verify canonical status through
|
||||
depth uint // Depth after which to discard previous blocks
|
||||
blocks *ring.Ring // Block infos to allow canonical chain cross checks
|
||||
lock sync.RWMutex // Protects the fields from concurrent access
|
||||
}
|
||||
|
||||
// newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks.
|
||||
func newUnconfirmedBlocks(chain headerRetriever, depth uint) *unconfirmedBlocks {
|
||||
return &unconfirmedBlocks{
|
||||
chain: chain,
|
||||
depth: depth,
|
||||
}
|
||||
}
|
||||
|
||||
// Insert adds a new block to the set of unconfirmed ones.
|
||||
func (set *unconfirmedBlocks) Insert(index uint64, hash common.Hash) {
|
||||
// If a new block was mined locally, shift out any old enough blocks
|
||||
set.Shift(index)
|
||||
|
||||
// Create the new item as its own ring
|
||||
item := ring.New(1)
|
||||
item.Value = &unconfirmedBlock{
|
||||
index: index,
|
||||
hash: hash,
|
||||
}
|
||||
// Set as the initial ring or append to the end
|
||||
set.lock.Lock()
|
||||
defer set.lock.Unlock()
|
||||
|
||||
if set.blocks == nil {
|
||||
set.blocks = item
|
||||
} else {
|
||||
set.blocks.Move(-1).Link(item)
|
||||
}
|
||||
// Display a log for the user to notify of a new mined block unconfirmed
|
||||
glog.V(logger.Info).Infof("🔨 mined potential block #%d [%x…], waiting for %d blocks to confirm", index, hash.Bytes()[:4], set.depth)
|
||||
}
|
||||
|
||||
// Shift drops all unconfirmed blocks from the set which exceed the unconfirmed sets depth
|
||||
// allowance, checking them against the canonical chain for inclusion or staleness
|
||||
// report.
|
||||
func (set *unconfirmedBlocks) Shift(height uint64) {
|
||||
set.lock.Lock()
|
||||
defer set.lock.Unlock()
|
||||
|
||||
for set.blocks != nil {
|
||||
// Retrieve the next unconfirmed block and abort if too fresh
|
||||
next := set.blocks.Value.(*unconfirmedBlock)
|
||||
if next.index+uint64(set.depth) > height {
|
||||
break
|
||||
}
|
||||
// Block seems to exceed depth allowance, check for canonical status
|
||||
header := set.chain.GetHeaderByNumber(next.index)
|
||||
switch {
|
||||
case header == nil:
|
||||
glog.V(logger.Warn).Infof("failed to retrieve header of mined block #%d [%x…]", next.index, next.hash.Bytes()[:4])
|
||||
case header.Hash() == next.hash:
|
||||
glog.V(logger.Info).Infof("🔗 mined block #%d [%x…] reached canonical chain", next.index, next.hash.Bytes()[:4])
|
||||
default:
|
||||
glog.V(logger.Info).Infof("⑂ mined block #%d [%x…] became a side fork", next.index, next.hash.Bytes()[:4])
|
||||
}
|
||||
// Drop the block out of the ring
|
||||
if set.blocks.Value == set.blocks.Next().Value {
|
||||
set.blocks = nil
|
||||
} else {
|
||||
set.blocks = set.blocks.Move(-1)
|
||||
set.blocks.Unlink(1)
|
||||
set.blocks = set.blocks.Move(1)
|
||||
}
|
||||
}
|
||||
}
|
85
miner/unconfirmed_test.go
Normal file
85
miner/unconfirmed_test.go
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package miner
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// noopHeaderRetriever is an implementation of headerRetriever that always
|
||||
// returns nil for any requested headers.
|
||||
type noopHeaderRetriever struct{}
|
||||
|
||||
func (r *noopHeaderRetriever) GetHeaderByNumber(number uint64) *types.Header {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tests that inserting blocks into the unconfirmed set accumulates them until
|
||||
// the desired depth is reached, after which they begin to be dropped.
|
||||
func TestUnconfirmedInsertBounds(t *testing.T) {
|
||||
limit := uint(10)
|
||||
|
||||
pool := newUnconfirmedBlocks(new(noopHeaderRetriever), limit)
|
||||
for depth := uint64(0); depth < 2*uint64(limit); depth++ {
|
||||
// Insert multiple blocks for the same level just to stress it
|
||||
for i := 0; i < int(depth); i++ {
|
||||
pool.Insert(depth, common.Hash([32]byte{byte(depth), byte(i)}))
|
||||
}
|
||||
// Validate that no blocks below the depth allowance are left in
|
||||
pool.blocks.Do(func(block interface{}) {
|
||||
if block := block.(*unconfirmedBlock); block.index+uint64(limit) <= depth {
|
||||
t.Errorf("depth %d: block %x not dropped", depth, block.hash)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that shifting blocks out of the unconfirmed set works both for normal
|
||||
// cases as well as for corner cases such as empty sets, empty shifts or full
|
||||
// shifts.
|
||||
func TestUnconfirmedShifts(t *testing.T) {
|
||||
// Create a pool with a few blocks on various depths
|
||||
limit, start := uint(10), uint64(25)
|
||||
|
||||
pool := newUnconfirmedBlocks(new(noopHeaderRetriever), limit)
|
||||
for depth := start; depth < start+uint64(limit); depth++ {
|
||||
pool.Insert(depth, common.Hash([32]byte{byte(depth)}))
|
||||
}
|
||||
// Try to shift below the limit and ensure no blocks are dropped
|
||||
pool.Shift(start + uint64(limit) - 1)
|
||||
if n := pool.blocks.Len(); n != int(limit) {
|
||||
t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit)
|
||||
}
|
||||
// Try to shift half the blocks out and verify remainder
|
||||
pool.Shift(start + uint64(limit) - 1 + uint64(limit/2))
|
||||
if n := pool.blocks.Len(); n != int(limit)/2 {
|
||||
t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit/2)
|
||||
}
|
||||
// Try to shift all the remaining blocks out and verify emptyness
|
||||
pool.Shift(start + 2*uint64(limit))
|
||||
if n := pool.blocks.Len(); n != 0 {
|
||||
t.Errorf("unconfirmed count mismatch: have %d, want %d", n, 0)
|
||||
}
|
||||
// Try to shift out from the empty set and make sure it doesn't break
|
||||
pool.Shift(start + 3*uint64(limit))
|
||||
if n := pool.blocks.Len(); n != 0 {
|
||||
t.Errorf("unconfirmed count mismatch: have %d, want %d", n, 0)
|
||||
}
|
||||
}
|
118
miner/worker.go
118
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()
|
||||
@ -176,6 +179,21 @@ func (self *worker) pending() (*types.Block, *state.StateDB) {
|
||||
return self.current.Block, self.current.state.Copy()
|
||||
}
|
||||
|
||||
func (self *worker) pendingBlock() *types.Block {
|
||||
self.currentMu.Lock()
|
||||
defer self.currentMu.Unlock()
|
||||
|
||||
if atomic.LoadInt32(&self.mining) == 0 {
|
||||
return types.NewBlock(
|
||||
self.current.header,
|
||||
self.current.txs,
|
||||
nil,
|
||||
self.current.receipts,
|
||||
)
|
||||
}
|
||||
return self.current.Block
|
||||
}
|
||||
|
||||
func (self *worker) start() {
|
||||
self.mu.Lock()
|
||||
defer self.mu.Unlock()
|
||||
@ -248,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
|
||||
@ -334,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()
|
||||
@ -396,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
|
||||
}
|
||||
@ -414,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()
|
||||
@ -492,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 {
|
||||
@ -504,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)
|
||||
@ -546,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)
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user