mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-13 07:23:46 +00:00
Merge branch 'opmaster' into portal1
Signed-off-by: Chen Kai <281165273grape@gmail.com>
This commit is contained in:
commit
b724ba2b79
82 changed files with 1287 additions and 5388 deletions
|
|
@ -232,7 +232,7 @@ func abigen(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -52,10 +53,10 @@ func main() {
|
||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
||||||
glogger.Verbosity(log.Lvl(*verbosity))
|
glogger.Verbosity(slog.Level(*verbosity))
|
||||||
glogger.Vmodule(*vmodule)
|
glogger.Vmodule(*vmodule)
|
||||||
log.Root().SetHandler(glogger)
|
log.SetDefault(log.NewLogger(glogger))
|
||||||
|
|
||||||
natm, err := nat.Parse(*natdesc)
|
natm, err := nat.Parse(*natdesc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ import (
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const legalWarning = `
|
const legalWarning = `
|
||||||
|
|
@ -492,7 +493,7 @@ func initialize(c *cli.Context) error {
|
||||||
if usecolor {
|
if usecolor {
|
||||||
output = colorable.NewColorable(logOutput)
|
output = colorable.NewColorable(logOutput)
|
||||||
}
|
}
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(output, log.TerminalFormat(usecolor))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(output, slog.Level(c.Int(logLevelFlag.Name)), usecolor)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ func runTests(ctx *cli.Context, tests []utesting.Test) error {
|
||||||
}
|
}
|
||||||
// Disable logging unless explicitly enabled.
|
// Disable logging unless explicitly enabled.
|
||||||
if !ctx.IsSet("verbosity") && !ctx.IsSet("vmodule") {
|
if !ctx.IsSet("verbosity") && !ctx.IsSet("vmodule") {
|
||||||
log.Root().SetHandler(log.DiscardHandler())
|
log.SetDefault(log.NewLogger(log.DiscardHandler()))
|
||||||
}
|
}
|
||||||
// Run the tests.
|
// Run the tests.
|
||||||
var run = utesting.RunTests
|
var run = utesting.RunTests
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||||
|
|
@ -85,7 +86,13 @@ func blockTestCmd(ctx *cli.Context) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
test := tests[name]
|
test := tests[name]
|
||||||
if err := test.Run(false, rawdb.HashScheme, tracer); err != nil {
|
if err := test.Run(false, rawdb.HashScheme, tracer, func(res error, chain *core.BlockChain) {
|
||||||
|
if ctx.Bool(DumpFlag.Name) {
|
||||||
|
if state, _ := chain.State(); state != nil {
|
||||||
|
fmt.Println(string(state.Dump(nil)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
return fmt.Errorf("test %v: %w", name, err)
|
return fmt.Errorf("test %v: %w", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type header -field-override headerMarshaling -out gen_header.go
|
//go:generate go run github.com/fjl/gencodec -type header -field-override headerMarshaling -out gen_header.go
|
||||||
|
|
@ -216,9 +217,9 @@ func (i *bbInput) sealClique(block *types.Block) (*types.Block, error) {
|
||||||
// BuildBlock constructs a block from the given inputs.
|
// BuildBlock constructs a block from the given inputs.
|
||||||
func BuildBlock(ctx *cli.Context) error {
|
func BuildBlock(ctx *cli.Context) error {
|
||||||
// Configure the go-ethereum logger
|
// Configure the go-ethereum logger
|
||||||
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
||||||
glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name)))
|
glogger.Verbosity(slog.Level(ctx.Int(VerbosityFlag.Name)))
|
||||||
log.Root().SetHandler(glogger)
|
log.SetDefault(log.NewLogger(glogger))
|
||||||
|
|
||||||
baseDir, err := createBasedir(ctx)
|
baseDir, err := createBasedir(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/tests"
|
"github.com/ethereum/go-ethereum/tests"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type result struct {
|
type result struct {
|
||||||
|
|
@ -66,9 +67,9 @@ func (r *result) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
func Transaction(ctx *cli.Context) error {
|
func Transaction(ctx *cli.Context) error {
|
||||||
// Configure the go-ethereum logger
|
// Configure the go-ethereum logger
|
||||||
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
||||||
glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name)))
|
glogger.Verbosity(slog.Level(ctx.Int(VerbosityFlag.Name)))
|
||||||
log.Root().SetHandler(glogger)
|
log.SetDefault(log.NewLogger(glogger))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||||
|
|
@ -81,9 +83,9 @@ type input struct {
|
||||||
|
|
||||||
func Transition(ctx *cli.Context) error {
|
func Transition(ctx *cli.Context) error {
|
||||||
// Configure the go-ethereum logger
|
// Configure the go-ethereum logger
|
||||||
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
|
||||||
glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name)))
|
glogger.Verbosity(slog.Level(ctx.Int(VerbosityFlag.Name)))
|
||||||
log.Root().SetHandler(glogger)
|
log.SetDefault(log.NewLogger(glogger))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
|
|
|
||||||
|
|
@ -473,11 +473,6 @@ func dump(ctx *cli.Context) error {
|
||||||
if ctx.Bool(utils.IterativeOutputFlag.Name) {
|
if ctx.Bool(utils.IterativeOutputFlag.Name) {
|
||||||
state.IterativeDump(conf, json.NewEncoder(os.Stdout))
|
state.IterativeDump(conf, json.NewEncoder(os.Stdout))
|
||||||
} else {
|
} else {
|
||||||
if conf.OnlyWithAddresses {
|
|
||||||
fmt.Fprintf(os.Stderr, "If you want to include accounts with missing preimages, you need iterative output, since"+
|
|
||||||
" otherwise the accounts will overwrite each other in the resulting mapping.")
|
|
||||||
return errors.New("incompatible options")
|
|
||||||
}
|
|
||||||
fmt.Println(string(state.Dump(conf)))
|
fmt.Println(string(state.Dump(conf)))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/internal/reexec"
|
"github.com/ethereum/go-ethereum/internal/reexec"
|
||||||
)
|
)
|
||||||
|
|
@ -98,6 +99,53 @@ func testConsoleLogging(t *testing.T, format string, tStart, tEnd int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJsonLogging(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
haveB, err := runSelf("--log.format", "json", "logtest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
readFile, err := os.Open("testdata/logging/logtest-json.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wantLines := split(readFile)
|
||||||
|
haveLines := split(bytes.NewBuffer(haveB))
|
||||||
|
for i, wantLine := range wantLines {
|
||||||
|
if i > len(haveLines)-1 {
|
||||||
|
t.Fatalf("format %v, line %d missing, want:%v", "json", i, wantLine)
|
||||||
|
}
|
||||||
|
haveLine := haveLines[i]
|
||||||
|
for strings.Contains(haveLine, "Unknown config environment variable") {
|
||||||
|
// This can happen on CI runs. Drop it.
|
||||||
|
haveLines = append(haveLines[:i], haveLines[i+1:]...)
|
||||||
|
haveLine = haveLines[i]
|
||||||
|
}
|
||||||
|
var have, want []byte
|
||||||
|
{
|
||||||
|
var h map[string]any
|
||||||
|
if err := json.Unmarshal([]byte(haveLine), &h); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h["t"] = "xxx"
|
||||||
|
have, _ = json.Marshal(h)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var w map[string]any
|
||||||
|
if err := json.Unmarshal([]byte(wantLine), &w); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
w["t"] = "xxx"
|
||||||
|
want, _ = json.Marshal(w)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(have, want) {
|
||||||
|
// show an intelligent diff
|
||||||
|
t.Logf(nicediff(have, want))
|
||||||
|
t.Errorf("file content wrong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestVmodule(t *testing.T) {
|
func TestVmodule(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
checkOutput := func(level int, want, wantNot string) {
|
checkOutput := func(level int, want, wantNot string) {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
@ -49,7 +50,9 @@ func (c customQuotedStringer) String() string {
|
||||||
// logTest is an entry point which spits out some logs. This is used by testing
|
// logTest is an entry point which spits out some logs. This is used by testing
|
||||||
// to verify expected outputs
|
// to verify expected outputs
|
||||||
func logTest(ctx *cli.Context) error {
|
func logTest(ctx *cli.Context) error {
|
||||||
log.ResetGlobalState()
|
// clear field padding map
|
||||||
|
debug.ResetLogging()
|
||||||
|
|
||||||
{ // big.Int
|
{ // big.Int
|
||||||
ba, _ := new(big.Int).SetString("111222333444555678999", 10) // "111,222,333,444,555,678,999"
|
ba, _ := new(big.Int).SetString("111222333444555678999", 10) // "111,222,333,444,555,678,999"
|
||||||
bb, _ := new(big.Int).SetString("-111222333444555678999", 10) // "-111,222,333,444,555,678,999"
|
bb, _ := new(big.Int).SetString("-111222333444555678999", 10) // "-111,222,333,444,555,678,999"
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,8 @@ var (
|
||||||
utils.GpoMaxGasPriceFlag,
|
utils.GpoMaxGasPriceFlag,
|
||||||
utils.GpoIgnoreGasPriceFlag,
|
utils.GpoIgnoreGasPriceFlag,
|
||||||
configFileFlag,
|
configFileFlag,
|
||||||
|
utils.LogDebugFlag,
|
||||||
|
utils.LogBacktraceAtFlag,
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags)
|
}, utils.NetworkFlags, utils.DatabaseFlags)
|
||||||
|
|
||||||
rpcFlags = []cli.Flag{
|
rpcFlags = []cli.Flag{
|
||||||
|
|
|
||||||
|
|
@ -580,11 +580,11 @@ func dumpState(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
da := &state.DumpAccount{
|
da := &state.DumpAccount{
|
||||||
Balance: account.Balance.String(),
|
Balance: account.Balance.String(),
|
||||||
Nonce: account.Nonce,
|
Nonce: account.Nonce,
|
||||||
Root: account.Root.Bytes(),
|
Root: account.Root.Bytes(),
|
||||||
CodeHash: account.CodeHash,
|
CodeHash: account.CodeHash,
|
||||||
SecureKey: accIt.Hash().Bytes(),
|
AddressHash: accIt.Hash().Bytes(),
|
||||||
}
|
}
|
||||||
if !conf.SkipCode && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
|
if !conf.SkipCode && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
|
||||||
da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash))
|
da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash))
|
||||||
|
|
|
||||||
100
cmd/geth/testdata/logging/logtest-json.txt
vendored
100
cmd/geth/testdata/logging/logtest-json.txt
vendored
|
|
@ -1,49 +1,51 @@
|
||||||
{"111,222,333,444,555,678,999":"111222333444555678999","lvl":"info","msg":"big.Int","t":"2023-11-09T08:33:19.464383209+01:00"}
|
{"t":"2023-11-22T15:42:00.407963+08:00","lvl":"info","msg":"big.Int","111,222,333,444,555,678,999":"111222333444555678999"}
|
||||||
{"-111,222,333,444,555,678,999":"-111222333444555678999","lvl":"info","msg":"-big.Int","t":"2023-11-09T08:33:19.46455928+01:00"}
|
{"t":"2023-11-22T15:42:00.408084+08:00","lvl":"info","msg":"-big.Int","-111,222,333,444,555,678,999":"-111222333444555678999"}
|
||||||
{"11,122,233,344,455,567,899,900":"11122233344455567899900","lvl":"info","msg":"big.Int","t":"2023-11-09T08:33:19.464582073+01:00"}
|
{"t":"2023-11-22T15:42:00.408092+08:00","lvl":"info","msg":"big.Int","11,122,233,344,455,567,899,900":"11122233344455567899900"}
|
||||||
{"-11,122,233,344,455,567,899,900":"-11122233344455567899900","lvl":"info","msg":"-big.Int","t":"2023-11-09T08:33:19.464594846+01:00"}
|
{"t":"2023-11-22T15:42:00.408097+08:00","lvl":"info","msg":"-big.Int","-11,122,233,344,455,567,899,900":"-11122233344455567899900"}
|
||||||
{"111,222,333,444,555,678,999":"0x607851afc94ca2517","lvl":"info","msg":"uint256","t":"2023-11-09T08:33:19.464607873+01:00"}
|
{"t":"2023-11-22T15:42:00.408127+08:00","lvl":"info","msg":"uint256","111,222,333,444,555,678,999":"111222333444555678999"}
|
||||||
{"11,122,233,344,455,567,899,900":"0x25aeffe8aaa1ef67cfc","lvl":"info","msg":"uint256","t":"2023-11-09T08:33:19.464694639+01:00"}
|
{"t":"2023-11-22T15:42:00.408133+08:00","lvl":"info","msg":"uint256","11,122,233,344,455,567,899,900":"11122233344455567899900"}
|
||||||
{"1,000,000":1000000,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464708835+01:00"}
|
{"t":"2023-11-22T15:42:00.408137+08:00","lvl":"info","msg":"int64","1,000,000":1000000}
|
||||||
{"-1,000,000":-1000000,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464725054+01:00"}
|
{"t":"2023-11-22T15:42:00.408145+08:00","lvl":"info","msg":"int64","-1,000,000":-1000000}
|
||||||
{"9,223,372,036,854,775,807":9223372036854775807,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464735773+01:00"}
|
{"t":"2023-11-22T15:42:00.408149+08:00","lvl":"info","msg":"int64","9,223,372,036,854,775,807":9223372036854775807}
|
||||||
{"-9,223,372,036,854,775,808":-9223372036854775808,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464744532+01:00"}
|
{"t":"2023-11-22T15:42:00.408153+08:00","lvl":"info","msg":"int64","-9,223,372,036,854,775,808":-9223372036854775808}
|
||||||
{"1,000,000":1000000,"lvl":"info","msg":"uint64","t":"2023-11-09T08:33:19.464752807+01:00"}
|
{"t":"2023-11-22T15:42:00.408156+08:00","lvl":"info","msg":"uint64","1,000,000":1000000}
|
||||||
{"18,446,744,073,709,551,615":18446744073709551615,"lvl":"info","msg":"uint64","t":"2023-11-09T08:33:19.464779296+01:00"}
|
{"t":"2023-11-22T15:42:00.40816+08:00","lvl":"info","msg":"uint64","18,446,744,073,709,551,615":18446744073709551615}
|
||||||
{"key":"special \r\n\t chars","lvl":"info","msg":"Special chars in value","t":"2023-11-09T08:33:19.464794181+01:00"}
|
{"t":"2023-11-22T15:42:00.408164+08:00","lvl":"info","msg":"Special chars in value","key":"special \r\n\t chars"}
|
||||||
{"lvl":"info","msg":"Special chars in key","special \n\t chars":"value","t":"2023-11-09T08:33:19.464827197+01:00"}
|
{"t":"2023-11-22T15:42:00.408167+08:00","lvl":"info","msg":"Special chars in key","special \n\t chars":"value"}
|
||||||
{"lvl":"info","msg":"nospace","nospace":"nospace","t":"2023-11-09T08:33:19.464841118+01:00"}
|
{"t":"2023-11-22T15:42:00.408171+08:00","lvl":"info","msg":"nospace","nospace":"nospace"}
|
||||||
{"lvl":"info","msg":"with space","t":"2023-11-09T08:33:19.464862818+01:00","with nospace":"with nospace"}
|
{"t":"2023-11-22T15:42:00.408174+08:00","lvl":"info","msg":"with space","with nospace":"with nospace"}
|
||||||
{"key":"\u001b[1G\u001b[K\u001b[1A","lvl":"info","msg":"Bash escapes in value","t":"2023-11-09T08:33:19.464876802+01:00"}
|
{"t":"2023-11-22T15:42:00.408178+08:00","lvl":"info","msg":"Bash escapes in value","key":"\u001b[1G\u001b[K\u001b[1A"}
|
||||||
{"\u001b[1G\u001b[K\u001b[1A":"value","lvl":"info","msg":"Bash escapes in key","t":"2023-11-09T08:33:19.464885416+01:00"}
|
{"t":"2023-11-22T15:42:00.408182+08:00","lvl":"info","msg":"Bash escapes in key","\u001b[1G\u001b[K\u001b[1A":"value"}
|
||||||
{"key":"value","lvl":"info","msg":"Bash escapes in message \u001b[1G\u001b[K\u001b[1A end","t":"2023-11-09T08:33:19.464906946+01:00"}
|
{"t":"2023-11-22T15:42:00.408186+08:00","lvl":"info","msg":"Bash escapes in message \u001b[1G\u001b[K\u001b[1A end","key":"value"}
|
||||||
{"\u001b[35mColored\u001b[0m[":"\u001b[35mColored\u001b[0m[","lvl":"info","msg":"\u001b[35mColored\u001b[0m[","t":"2023-11-09T08:33:19.464921455+01:00"}
|
{"t":"2023-11-22T15:42:00.408194+08:00","lvl":"info","msg":"\u001b[35mColored\u001b[0m[","\u001b[35mColored\u001b[0m[":"\u001b[35mColored\u001b[0m["}
|
||||||
{"2562047h47m16.854s":"2562047h47m16.854s","lvl":"info","msg":"Custom Stringer value","t":"2023-11-09T08:33:19.464943893+01:00"}
|
{"t":"2023-11-22T15:42:00.408197+08:00","lvl":"info","msg":"an error message with quotes","error":"this is an 'error'"}
|
||||||
{"key":"lazy value","lvl":"info","msg":"Lazy evaluation of value","t":"2023-11-09T08:33:19.465013552+01:00"}
|
{"t":"2023-11-22T15:42:00.408202+08:00","lvl":"info","msg":"Custom Stringer value","2562047h47m16.854s":"2562047h47m16.854s"}
|
||||||
{"lvl":"info","msg":"A message with wonky 💩 characters","t":"2023-11-09T08:33:19.465069437+01:00"}
|
{"t":"2023-11-22T15:42:00.408208+08:00","lvl":"info","msg":"a custom stringer that emits quoted text","output":"output with 'quotes'"}
|
||||||
{"lvl":"info","msg":"A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩","t":"2023-11-09T08:33:19.465083053+01:00"}
|
{"t":"2023-11-22T15:42:00.408215+08:00","lvl":"info","msg":"Lazy evaluation of value","key":"lazy value"}
|
||||||
{"lvl":"info","msg":"A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above","t":"2023-11-09T08:33:19.465104289+01:00"}
|
{"t":"2023-11-22T15:42:00.408219+08:00","lvl":"info","msg":"A message with wonky 💩 characters"}
|
||||||
{"false":"false","lvl":"info","msg":"boolean","t":"2023-11-09T08:33:19.465117185+01:00","true":"true"}
|
{"t":"2023-11-22T15:42:00.408222+08:00","lvl":"info","msg":"A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"}
|
||||||
{"foo":"beta","lvl":"info","msg":"repeated-key 1","t":"2023-11-09T08:33:19.465143425+01:00"}
|
{"t":"2023-11-22T15:42:00.408226+08:00","lvl":"info","msg":"A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above"}
|
||||||
{"lvl":"info","msg":"repeated-key 2","t":"2023-11-09T08:33:19.465156323+01:00","xx":"longer"}
|
{"t":"2023-11-22T15:42:00.408229+08:00","lvl":"info","msg":"boolean","true":true,"false":false}
|
||||||
{"lvl":"info","msg":"log at level info","t":"2023-11-09T08:33:19.465193158+01:00"}
|
{"t":"2023-11-22T15:42:00.408234+08:00","lvl":"info","msg":"repeated-key 1","foo":"alpha","foo":"beta"}
|
||||||
{"lvl":"warn","msg":"log at level warn","t":"2023-11-09T08:33:19.465228964+01:00"}
|
{"t":"2023-11-22T15:42:00.408237+08:00","lvl":"info","msg":"repeated-key 2","xx":"short","xx":"longer"}
|
||||||
{"lvl":"eror","msg":"log at level error","t":"2023-11-09T08:33:19.465240352+01:00"}
|
{"t":"2023-11-22T15:42:00.408241+08:00","lvl":"info","msg":"log at level info"}
|
||||||
{"a":"aligned left","bar":"short","lvl":"info","msg":"test","t":"2023-11-09T08:33:19.465247226+01:00"}
|
{"t":"2023-11-22T15:42:00.408244+08:00","lvl":"warn","msg":"log at level warn"}
|
||||||
{"a":1,"bar":"a long message","lvl":"info","msg":"test","t":"2023-11-09T08:33:19.465269028+01:00"}
|
{"t":"2023-11-22T15:42:00.408247+08:00","lvl":"eror","msg":"log at level error"}
|
||||||
{"a":"aligned right","bar":"short","lvl":"info","msg":"test","t":"2023-11-09T08:33:19.465313611+01:00"}
|
{"t":"2023-11-22T15:42:00.408251+08:00","lvl":"info","msg":"test","bar":"short","a":"aligned left"}
|
||||||
{"lvl":"info","msg":"The following logs should align so that the key-fields make 5 columns","t":"2023-11-09T08:33:19.465328188+01:00"}
|
{"t":"2023-11-22T15:42:00.408254+08:00","lvl":"info","msg":"test","bar":"a long message","a":1}
|
||||||
{"gas":1123123,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","lvl":"info","msg":"Inserted known block","number":1012,"other":"first","t":"2023-11-09T08:33:19.465350507+01:00","txs":200}
|
{"t":"2023-11-22T15:42:00.408258+08:00","lvl":"info","msg":"test","bar":"short","a":"aligned right"}
|
||||||
{"gas":1123,"hash":"0x0000000000000000000000000000000000000000000000000000000000001235","lvl":"info","msg":"Inserted new block","number":1,"other":"second","t":"2023-11-09T08:33:19.465387952+01:00","txs":2}
|
{"t":"2023-11-22T15:42:00.408261+08:00","lvl":"info","msg":"The following logs should align so that the key-fields make 5 columns"}
|
||||||
{"gas":1,"hash":"0x0000000000000000000000000000000000000000000000000000000000012322","lvl":"info","msg":"Inserted known block","number":99,"other":"third","t":"2023-11-09T08:33:19.465406687+01:00","txs":10}
|
{"t":"2023-11-22T15:42:00.408275+08:00","lvl":"info","msg":"Inserted known block","number":1012,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","txs":200,"gas":1123123,"other":"first"}
|
||||||
{"gas":99,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","lvl":"warn","msg":"Inserted known block","number":1012,"other":"fourth","t":"2023-11-09T08:33:19.465433025+01:00","txs":200}
|
{"t":"2023-11-22T15:42:00.408281+08:00","lvl":"info","msg":"Inserted new block","number":1,"hash":"0x0000000000000000000000000000000000000000000000000000000000001235","txs":2,"gas":1123,"other":"second"}
|
||||||
{"\u003cnil\u003e":"\u003cnil\u003e","lvl":"info","msg":"(*big.Int)(nil)","t":"2023-11-09T08:33:19.465450283+01:00"}
|
{"t":"2023-11-22T15:42:00.408287+08:00","lvl":"info","msg":"Inserted known block","number":99,"hash":"0x0000000000000000000000000000000000000000000000000000000000012322","txs":10,"gas":1,"other":"third"}
|
||||||
{"\u003cnil\u003e":"nil","lvl":"info","msg":"(*uint256.Int)(nil)","t":"2023-11-09T08:33:19.465472953+01:00"}
|
{"t":"2023-11-22T15:42:00.408296+08:00","lvl":"warn","msg":"Inserted known block","number":1012,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","txs":200,"gas":99,"other":"fourth"}
|
||||||
{"lvl":"info","msg":"(fmt.Stringer)(nil)","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465538633+01:00"}
|
{"t":"2023-11-22T15:42:00.4083+08:00","lvl":"info","msg":"(*big.Int)(nil)","<nil>":"<nil>"}
|
||||||
{"lvl":"info","msg":"nil-concrete-stringer","res":"nil","t":"2023-11-09T08:33:19.465552355+01:00"}
|
{"t":"2023-11-22T15:42:00.408303+08:00","lvl":"info","msg":"(*uint256.Int)(nil)","<nil>":"<nil>"}
|
||||||
{"lvl":"info","msg":"error(nil) ","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465601029+01:00"}
|
{"t":"2023-11-22T15:42:00.408311+08:00","lvl":"info","msg":"(fmt.Stringer)(nil)","res":null}
|
||||||
{"lvl":"info","msg":"nil-concrete-error","res":"","t":"2023-11-09T08:33:19.46561622+01:00"}
|
{"t":"2023-11-22T15:42:00.408318+08:00","lvl":"info","msg":"nil-concrete-stringer","res":"<nil>"}
|
||||||
{"lvl":"info","msg":"nil-custom-struct","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465638888+01:00"}
|
{"t":"2023-11-22T15:42:00.408322+08:00","lvl":"info","msg":"error(nil) ","res":null}
|
||||||
{"lvl":"info","msg":"raw nil","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465673664+01:00"}
|
{"t":"2023-11-22T15:42:00.408326+08:00","lvl":"info","msg":"nil-concrete-error","res":""}
|
||||||
{"lvl":"info","msg":"(*uint64)(nil)","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465700264+01:00"}
|
{"t":"2023-11-22T15:42:00.408334+08:00","lvl":"info","msg":"nil-custom-struct","res":null}
|
||||||
{"level":"level","lvl":"lvl","msg":"msg","t":"t","time":"time"}
|
{"t":"2023-11-22T15:42:00.40835+08:00","lvl":"info","msg":"raw nil","res":null}
|
||||||
|
{"t":"2023-11-22T15:42:00.408354+08:00","lvl":"info","msg":"(*uint64)(nil)","res":null}
|
||||||
|
{"t":"2023-11-22T15:42:00.408361+08:00","lvl":"info","msg":"Using keys 't', 'lvl', 'time', 'level' and 'msg'","t":"t","time":"time","lvl":"lvl","level":"level","msg":"msg"}
|
||||||
|
|
|
||||||
102
cmd/geth/testdata/logging/logtest-logfmt.txt
vendored
102
cmd/geth/testdata/logging/logtest-logfmt.txt
vendored
|
|
@ -1,51 +1,51 @@
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=big.Int 111,222,333,444,555,678,999=111222333444555678999
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=-big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=-big.Int -111,222,333,444,555,678,999=-111222333444555678999
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=big.Int 11,122,233,344,455,567,899,900=11122233344455567899900
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=-big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=-big.Int -11,122,233,344,455,567,899,900=-11122233344455567899900
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint256 111,222,333,444,555,678,999=111222333444555678999
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint256 11,122,233,344,455,567,899,900=11122233344455567899900
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 1,000,000=1,000,000
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 1,000,000=1000000
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 -1,000,000=-1,000,000
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 -1,000,000=-1000000
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 9,223,372,036,854,775,807=9223372036854775807
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 -9,223,372,036,854,775,808=-9223372036854775808
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint64 1,000,000=1,000,000
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint64 1,000,000=1000000
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint64 18,446,744,073,709,551,615=18446744073709551615
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Special chars in value" key="special \r\n\t chars"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Special chars in value" key="special \r\n\t chars"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Special chars in key" "special \n\t chars"=value
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Special chars in key" "special \n\t chars"=value
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nospace nospace=nospace
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nospace nospace=nospace
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="with space" "with nospace"="with nospace"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="with space" "with nospace"="with nospace"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Bash escapes in value" key="\x1b[1G\x1b[K\x1b[1A"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in value" key="\x1b[1G\x1b[K\x1b[1A"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Bash escapes in key" "\x1b[1G\x1b[K\x1b[1A"=value
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in key" "\x1b[1G\x1b[K\x1b[1A"=value
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m["
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m["
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="an error message with quotes" error="this is an 'error'"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="an error message with quotes" error="this is an 'error'"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Custom Stringer value" 2562047h47m16.854s=2562047h47m16.854s
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Custom Stringer value" 2562047h47m16.854s=2562047h47m16.854s
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="a custom stringer that emits quoted text" output="output with 'quotes'"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="a custom stringer that emits quoted text" output="output with 'quotes'"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Lazy evaluation of value" key="lazy value"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Lazy evaluation of value" key="lazy value"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="A message with wonky 💩 characters"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A message with wonky 💩 characters"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=boolean true=true false=false
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=boolean true=true false=false
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="repeated-key 1" foo=alpha foo=beta
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="repeated-key 1" foo=alpha foo=beta
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="repeated-key 2" xx=short xx=longer
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="repeated-key 2" xx=short xx=longer
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="log at level info"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="log at level info"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=warn msg="log at level warn"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=warn msg="log at level warn"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=eror msg="log at level error"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=eror msg="log at level error"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=test bar=short a="aligned left"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar=short a="aligned left"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=test bar="a long message" a=1
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar="a long message" a=1
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=test bar=short a="aligned right"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar=short a="aligned right"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="The following logs should align so that the key-fields make 5 columns"
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="The following logs should align so that the key-fields make 5 columns"
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=1,123,123 other=first
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=1123123 other=first
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Inserted new block" number=1 hash=0x0000000000000000000000000000000000000000000000000000000000001235 txs=2 gas=1123 other=second
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted new block" number=1 hash=0x0000000000000000000000000000000000000000000000000000000000001235 txs=2 gas=1123 other=second
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Inserted known block" number=99 hash=0x0000000000000000000000000000000000000000000000000000000000012322 txs=10 gas=1 other=third
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted known block" number=99 hash=0x0000000000000000000000000000000000000000000000000000000000012322 txs=10 gas=1 other=third
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=warn msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=99 other=fourth
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=warn msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=99 other=fourth
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(*big.Int)(nil) <nil>=<nil>
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*big.Int)(nil) <nil>=<nil>
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(*uint256.Int)(nil) <nil>=<nil>
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*uint256.Int)(nil) <nil>=<nil>
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(fmt.Stringer)(nil) res=nil
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(fmt.Stringer)(nil) res=<nil>
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nil-concrete-stringer res=nil
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-concrete-stringer res=<nil>
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="error(nil) " res=nil
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="error(nil) " res=<nil>
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nil-concrete-error res=
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-concrete-error res=""
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nil-custom-struct res=<nil>
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-custom-struct res=<nil>
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="raw nil" res=nil
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="raw nil" res=<nil>
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(*uint64)(nil) res=<nil>
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*uint64)(nil) res=<nil>
|
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Using keys 't', 'lvl', 'time', 'level' and 'msg'" t=t time=time lvl=lvl level=level msg=msg
|
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Using keys 't', 'lvl', 'time', 'level' and 'msg'" t=t time=time lvl=lvl level=level msg=msg
|
||||||
|
|
|
||||||
102
cmd/geth/testdata/logging/logtest-terminal.txt
vendored
102
cmd/geth/testdata/logging/logtest-terminal.txt
vendored
|
|
@ -1,52 +1,52 @@
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999
|
INFO [xx-xx|xx:xx:xx.xxx] big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] -big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999
|
INFO [xx-xx|xx:xx:xx.xxx] -big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
|
INFO [xx-xx|xx:xx:xx.xxx] big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] -big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900
|
INFO [xx-xx|xx:xx:xx.xxx] -big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999
|
INFO [xx-xx|xx:xx:xx.xxx] uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
|
INFO [xx-xx|xx:xx:xx.xxx] uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] int64 1,000,000=1,000,000
|
INFO [xx-xx|xx:xx:xx.xxx] int64 1,000,000=1,000,000
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] int64 -1,000,000=-1,000,000
|
INFO [xx-xx|xx:xx:xx.xxx] int64 -1,000,000=-1,000,000
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807
|
INFO [xx-xx|xx:xx:xx.xxx] int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808
|
INFO [xx-xx|xx:xx:xx.xxx] int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] uint64 1,000,000=1,000,000
|
INFO [xx-xx|xx:xx:xx.xxx] uint64 1,000,000=1,000,000
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615
|
INFO [xx-xx|xx:xx:xx.xxx] uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] Special chars in value key="special \r\n\t chars"
|
INFO [xx-xx|xx:xx:xx.xxx] Special chars in value key="special \r\n\t chars"
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] Special chars in key "special \n\t chars"=value
|
INFO [xx-xx|xx:xx:xx.xxx] Special chars in key "special \n\t chars"=value
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] nospace nospace=nospace
|
INFO [xx-xx|xx:xx:xx.xxx] nospace nospace=nospace
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] with space "with nospace"="with nospace"
|
INFO [xx-xx|xx:xx:xx.xxx] with space "with nospace"="with nospace"
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] Bash escapes in value key="\x1b[1G\x1b[K\x1b[1A"
|
INFO [xx-xx|xx:xx:xx.xxx] Bash escapes in value key="\x1b[1G\x1b[K\x1b[1A"
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] Bash escapes in key "\x1b[1G\x1b[K\x1b[1A"=value
|
INFO [xx-xx|xx:xx:xx.xxx] Bash escapes in key "\x1b[1G\x1b[K\x1b[1A"=value
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] "Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value
|
INFO [xx-xx|xx:xx:xx.xxx] "Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] "\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m["
|
INFO [xx-xx|xx:xx:xx.xxx] "\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m["
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] an error message with quotes error="this is an 'error'"
|
INFO [xx-xx|xx:xx:xx.xxx] an error message with quotes error="this is an 'error'"
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] Custom Stringer value 2562047h47m16.854s=2562047h47m16.854s
|
INFO [xx-xx|xx:xx:xx.xxx] Custom Stringer value 2562047h47m16.854s=2562047h47m16.854s
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] a custom stringer that emits quoted text output="output with 'quotes'"
|
INFO [xx-xx|xx:xx:xx.xxx] a custom stringer that emits quoted text output="output with 'quotes'"
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] Lazy evaluation of value key="lazy value"
|
INFO [xx-xx|xx:xx:xx.xxx] Lazy evaluation of value key="lazy value"
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] "A message with wonky 💩 characters"
|
INFO [xx-xx|xx:xx:xx.xxx] "A message with wonky 💩 characters"
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] "A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"
|
INFO [xx-xx|xx:xx:xx.xxx] "A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] A multiline message
|
INFO [xx-xx|xx:xx:xx.xxx] A multiline message
|
||||||
LALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above
|
LALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] boolean true=true false=false
|
INFO [xx-xx|xx:xx:xx.xxx] boolean true=true false=false
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] repeated-key 1 foo=alpha foo=beta
|
INFO [xx-xx|xx:xx:xx.xxx] repeated-key 1 foo=alpha foo=beta
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] repeated-key 2 xx=short xx=longer
|
INFO [xx-xx|xx:xx:xx.xxx] repeated-key 2 xx=short xx=longer
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] log at level info
|
INFO [xx-xx|xx:xx:xx.xxx] log at level info
|
||||||
WARN [XX-XX|XX:XX:XX.XXX] log at level warn
|
WARN [xx-xx|xx:xx:xx.xxx] log at level warn
|
||||||
ERROR[XX-XX|XX:XX:XX.XXX] log at level error
|
ERROR[xx-xx|xx:xx:xx.xxx] log at level error
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] test bar=short a="aligned left"
|
INFO [xx-xx|xx:xx:xx.xxx] test bar=short a="aligned left"
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] test bar="a long message" a=1
|
INFO [xx-xx|xx:xx:xx.xxx] test bar="a long message" a=1
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] test bar=short a="aligned right"
|
INFO [xx-xx|xx:xx:xx.xxx] test bar=short a="aligned right"
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] The following logs should align so that the key-fields make 5 columns
|
INFO [xx-xx|xx:xx:xx.xxx] The following logs should align so that the key-fields make 5 columns
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] Inserted known block number=1012 hash=000000..001234 txs=200 gas=1,123,123 other=first
|
INFO [xx-xx|xx:xx:xx.xxx] Inserted known block number=1012 hash=000000..001234 txs=200 gas=1,123,123 other=first
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] Inserted new block number=1 hash=000000..001235 txs=2 gas=1123 other=second
|
INFO [xx-xx|xx:xx:xx.xxx] Inserted new block number=1 hash=000000..001235 txs=2 gas=1123 other=second
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] Inserted known block number=99 hash=000000..012322 txs=10 gas=1 other=third
|
INFO [xx-xx|xx:xx:xx.xxx] Inserted known block number=99 hash=000000..012322 txs=10 gas=1 other=third
|
||||||
WARN [XX-XX|XX:XX:XX.XXX] Inserted known block number=1012 hash=000000..001234 txs=200 gas=99 other=fourth
|
WARN [xx-xx|xx:xx:xx.xxx] Inserted known block number=1012 hash=000000..001234 txs=200 gas=99 other=fourth
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] (*big.Int)(nil) <nil>=<nil>
|
INFO [xx-xx|xx:xx:xx.xxx] (*big.Int)(nil) <nil>=<nil>
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] (*uint256.Int)(nil) <nil>=<nil>
|
INFO [xx-xx|xx:xx:xx.xxx] (*uint256.Int)(nil) <nil>=<nil>
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] (fmt.Stringer)(nil) res=nil
|
INFO [xx-xx|xx:xx:xx.xxx] (fmt.Stringer)(nil) res=<nil>
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] nil-concrete-stringer res=nil
|
INFO [xx-xx|xx:xx:xx.xxx] nil-concrete-stringer res=<nil>
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] error(nil) res=nil
|
INFO [xx-xx|xx:xx:xx.xxx] error(nil) res=<nil>
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] nil-concrete-error res=
|
INFO [xx-xx|xx:xx:xx.xxx] nil-concrete-error res=
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] nil-custom-struct res=<nil>
|
INFO [xx-xx|xx:xx:xx.xxx] nil-custom-struct res=<nil>
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] raw nil res=nil
|
INFO [xx-xx|xx:xx:xx.xxx] raw nil res=<nil>
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] (*uint64)(nil) res=<nil>
|
INFO [xx-xx|xx:xx:xx.xxx] (*uint64)(nil) res=<nil>
|
||||||
INFO [XX-XX|XX:XX:XX.XXX] Using keys 't', 'lvl', 'time', 'level' and 'msg' t=t time=time lvl=lvl level=level msg=msg
|
INFO [xx-xx|xx:xx:xx.xxx] Using keys 't', 'lvl', 'time', 'level' and 'msg' t=t time=time lvl=lvl level=level msg=msg
|
||||||
|
|
|
||||||
|
|
@ -1389,6 +1389,13 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
|
||||||
log.Info(fmt.Sprintf("Using %s as db engine", dbEngine))
|
log.Info(fmt.Sprintf("Using %s as db engine", dbEngine))
|
||||||
cfg.DBEngine = dbEngine
|
cfg.DBEngine = dbEngine
|
||||||
}
|
}
|
||||||
|
// deprecation notice for log debug flags (TODO: find a more appropriate place to put these?)
|
||||||
|
if ctx.IsSet(LogBacktraceAtFlag.Name) {
|
||||||
|
log.Warn("log.backtrace flag is deprecated")
|
||||||
|
}
|
||||||
|
if ctx.IsSet(LogDebugFlag.Name) {
|
||||||
|
log.Warn("log.debug flag is deprecated")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSmartCard(ctx *cli.Context, cfg *node.Config) {
|
func setSmartCard(ctx *cli.Context, cfg *node.Config) {
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ var DeprecatedFlags = []cli.Flag{
|
||||||
LightMaxPeersFlag,
|
LightMaxPeersFlag,
|
||||||
LightNoPruneFlag,
|
LightNoPruneFlag,
|
||||||
LightNoSyncServeFlag,
|
LightNoSyncServeFlag,
|
||||||
|
LogBacktraceAtFlag,
|
||||||
|
LogDebugFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -118,6 +120,18 @@ var (
|
||||||
Usage: "Enables serving light clients before syncing (deprecated)",
|
Usage: "Enables serving light clients before syncing (deprecated)",
|
||||||
Category: flags.LightCategory,
|
Category: flags.LightCategory,
|
||||||
}
|
}
|
||||||
|
// Deprecated November 2023
|
||||||
|
LogBacktraceAtFlag = &cli.StringFlag{
|
||||||
|
Name: "log.backtrace",
|
||||||
|
Usage: "Request a stack trace at a specific logging statement (deprecated)",
|
||||||
|
Value: "",
|
||||||
|
Category: flags.DeprecatedCategory,
|
||||||
|
}
|
||||||
|
LogDebugFlag = &cli.BoolFlag{
|
||||||
|
Name: "log.debug",
|
||||||
|
Usage: "Prepends log messages with call-site location (deprecated)",
|
||||||
|
Category: flags.DeprecatedCategory,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// showDeprecated displays deprecated flags that will be soon removed from the codebase.
|
// showDeprecated displays deprecated flags that will be soon removed from the codebase.
|
||||||
|
|
|
||||||
|
|
@ -302,9 +302,22 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
|
||||||
if chain.Config().IsShanghai(header.Number, header.Time) {
|
if chain.Config().IsShanghai(header.Number, header.Time) {
|
||||||
return errors.New("clique does not support shanghai fork")
|
return errors.New("clique does not support shanghai fork")
|
||||||
}
|
}
|
||||||
|
// Verify the non-existence of withdrawalsHash.
|
||||||
|
if header.WithdrawalsHash != nil {
|
||||||
|
return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash)
|
||||||
|
}
|
||||||
if chain.Config().IsCancun(header.Number, header.Time) {
|
if chain.Config().IsCancun(header.Number, header.Time) {
|
||||||
return errors.New("clique does not support cancun fork")
|
return errors.New("clique does not support cancun fork")
|
||||||
}
|
}
|
||||||
|
// Verify the non-existence of cancun-specific header fields
|
||||||
|
switch {
|
||||||
|
case header.ExcessBlobGas != nil:
|
||||||
|
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas)
|
||||||
|
case header.BlobGasUsed != nil:
|
||||||
|
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed)
|
||||||
|
case header.ParentBeaconRoot != nil:
|
||||||
|
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot)
|
||||||
|
}
|
||||||
// All basic checks passed, verify cascading fields
|
// All basic checks passed, verify cascading fields
|
||||||
return c.verifyCascadingFields(chain, header, parents)
|
return c.verifyCascadingFields(chain, header, parents)
|
||||||
}
|
}
|
||||||
|
|
@ -753,6 +766,15 @@ func encodeSigHeader(w io.Writer, header *types.Header) {
|
||||||
if header.WithdrawalsHash != nil {
|
if header.WithdrawalsHash != nil {
|
||||||
panic("unexpected withdrawal hash value in clique")
|
panic("unexpected withdrawal hash value in clique")
|
||||||
}
|
}
|
||||||
|
if header.ExcessBlobGas != nil {
|
||||||
|
panic("unexpected excess blob gas value in clique")
|
||||||
|
}
|
||||||
|
if header.BlobGasUsed != nil {
|
||||||
|
panic("unexpected blob gas used value in clique")
|
||||||
|
}
|
||||||
|
if header.ParentBeaconRoot != nil {
|
||||||
|
panic("unexpected parent beacon root value in clique")
|
||||||
|
}
|
||||||
if err := rlp.Encode(w, enc); err != nil {
|
if err := rlp.Encode(w, enc); err != nil {
|
||||||
panic("can't encode: " + err.Error())
|
panic("can't encode: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -266,9 +266,22 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa
|
||||||
if chain.Config().IsShanghai(header.Number, header.Time) {
|
if chain.Config().IsShanghai(header.Number, header.Time) {
|
||||||
return errors.New("ethash does not support shanghai fork")
|
return errors.New("ethash does not support shanghai fork")
|
||||||
}
|
}
|
||||||
|
// Verify the non-existence of withdrawalsHash.
|
||||||
|
if header.WithdrawalsHash != nil {
|
||||||
|
return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash)
|
||||||
|
}
|
||||||
if chain.Config().IsCancun(header.Number, header.Time) {
|
if chain.Config().IsCancun(header.Number, header.Time) {
|
||||||
return errors.New("ethash does not support cancun fork")
|
return errors.New("ethash does not support cancun fork")
|
||||||
}
|
}
|
||||||
|
// Verify the non-existence of cancun-specific header fields
|
||||||
|
switch {
|
||||||
|
case header.ExcessBlobGas != nil:
|
||||||
|
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas)
|
||||||
|
case header.BlobGasUsed != nil:
|
||||||
|
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed)
|
||||||
|
case header.ParentBeaconRoot != nil:
|
||||||
|
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot)
|
||||||
|
}
|
||||||
// Add some fake checks for tests
|
// Add some fake checks for tests
|
||||||
if ethash.fakeDelay != nil {
|
if ethash.fakeDelay != nil {
|
||||||
time.Sleep(*ethash.fakeDelay)
|
time.Sleep(*ethash.fakeDelay)
|
||||||
|
|
@ -533,6 +546,15 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
|
||||||
if header.WithdrawalsHash != nil {
|
if header.WithdrawalsHash != nil {
|
||||||
panic("withdrawal hash set on ethash")
|
panic("withdrawal hash set on ethash")
|
||||||
}
|
}
|
||||||
|
if header.ExcessBlobGas != nil {
|
||||||
|
panic("excess blob gas set on ethash")
|
||||||
|
}
|
||||||
|
if header.BlobGasUsed != nil {
|
||||||
|
panic("blob gas used set on ethash")
|
||||||
|
}
|
||||||
|
if header.ParentBeaconRoot != nil {
|
||||||
|
panic("parent beacon root set on ethash")
|
||||||
|
}
|
||||||
rlp.Encode(hasher, enc)
|
rlp.Encode(hasher, enc)
|
||||||
hasher.Sum(hash[:0])
|
hasher.Sum(hash[:0])
|
||||||
return hash
|
return hash
|
||||||
|
|
|
||||||
|
|
@ -49,21 +49,24 @@ type DumpCollector interface {
|
||||||
|
|
||||||
// DumpAccount represents an account in the state.
|
// DumpAccount represents an account in the state.
|
||||||
type DumpAccount struct {
|
type DumpAccount struct {
|
||||||
Balance string `json:"balance"`
|
Balance string `json:"balance"`
|
||||||
Nonce uint64 `json:"nonce"`
|
Nonce uint64 `json:"nonce"`
|
||||||
Root hexutil.Bytes `json:"root"`
|
Root hexutil.Bytes `json:"root"`
|
||||||
CodeHash hexutil.Bytes `json:"codeHash"`
|
CodeHash hexutil.Bytes `json:"codeHash"`
|
||||||
Code hexutil.Bytes `json:"code,omitempty"`
|
Code hexutil.Bytes `json:"code,omitempty"`
|
||||||
Storage map[common.Hash]string `json:"storage,omitempty"`
|
Storage map[common.Hash]string `json:"storage,omitempty"`
|
||||||
Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode
|
Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode
|
||||||
SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key
|
AddressHash hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump represents the full dump in a collected format, as one large map.
|
// Dump represents the full dump in a collected format, as one large map.
|
||||||
type Dump struct {
|
type Dump struct {
|
||||||
Root string `json:"root"`
|
Root string `json:"root"`
|
||||||
Accounts map[common.Address]DumpAccount `json:"accounts"`
|
Accounts map[string]DumpAccount `json:"accounts"`
|
||||||
|
// Next can be set to represent that this dump is only partial, and Next
|
||||||
|
// is where an iterator should be positioned in order to continue the dump.
|
||||||
|
Next []byte `json:"next,omitempty"` // nil if no more accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnRoot implements DumpCollector interface
|
// OnRoot implements DumpCollector interface
|
||||||
|
|
@ -73,27 +76,11 @@ func (d *Dump) OnRoot(root common.Hash) {
|
||||||
|
|
||||||
// OnAccount implements DumpCollector interface
|
// OnAccount implements DumpCollector interface
|
||||||
func (d *Dump) OnAccount(addr *common.Address, account DumpAccount) {
|
func (d *Dump) OnAccount(addr *common.Address, account DumpAccount) {
|
||||||
if addr != nil {
|
if addr == nil {
|
||||||
d.Accounts[*addr] = account
|
d.Accounts[fmt.Sprintf("pre(%s)", account.AddressHash)] = account
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// IteratorDump is an implementation for iterating over data.
|
|
||||||
type IteratorDump struct {
|
|
||||||
Root string `json:"root"`
|
|
||||||
Accounts map[common.Address]DumpAccount `json:"accounts"`
|
|
||||||
Next []byte `json:"next,omitempty"` // nil if no more accounts
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnRoot implements DumpCollector interface
|
|
||||||
func (d *IteratorDump) OnRoot(root common.Hash) {
|
|
||||||
d.Root = fmt.Sprintf("%x", root)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnAccount implements DumpCollector interface
|
|
||||||
func (d *IteratorDump) OnAccount(addr *common.Address, account DumpAccount) {
|
|
||||||
if addr != nil {
|
if addr != nil {
|
||||||
d.Accounts[*addr] = account
|
d.Accounts[(*addr).String()] = account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,14 +92,14 @@ type iterativeDump struct {
|
||||||
// OnAccount implements DumpCollector interface
|
// OnAccount implements DumpCollector interface
|
||||||
func (d iterativeDump) OnAccount(addr *common.Address, account DumpAccount) {
|
func (d iterativeDump) OnAccount(addr *common.Address, account DumpAccount) {
|
||||||
dumpAccount := &DumpAccount{
|
dumpAccount := &DumpAccount{
|
||||||
Balance: account.Balance,
|
Balance: account.Balance,
|
||||||
Nonce: account.Nonce,
|
Nonce: account.Nonce,
|
||||||
Root: account.Root,
|
Root: account.Root,
|
||||||
CodeHash: account.CodeHash,
|
CodeHash: account.CodeHash,
|
||||||
Code: account.Code,
|
Code: account.Code,
|
||||||
Storage: account.Storage,
|
Storage: account.Storage,
|
||||||
SecureKey: account.SecureKey,
|
AddressHash: account.AddressHash,
|
||||||
Address: addr,
|
Address: addr,
|
||||||
}
|
}
|
||||||
d.Encode(dumpAccount)
|
d.Encode(dumpAccount)
|
||||||
}
|
}
|
||||||
|
|
@ -150,26 +137,27 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
account := DumpAccount{
|
|
||||||
Balance: data.Balance.String(),
|
|
||||||
Nonce: data.Nonce,
|
|
||||||
Root: data.Root[:],
|
|
||||||
CodeHash: data.CodeHash,
|
|
||||||
SecureKey: it.Key,
|
|
||||||
}
|
|
||||||
var (
|
var (
|
||||||
addrBytes = s.trie.GetKey(it.Key)
|
account = DumpAccount{
|
||||||
addr = common.BytesToAddress(addrBytes)
|
Balance: data.Balance.String(),
|
||||||
|
Nonce: data.Nonce,
|
||||||
|
Root: data.Root[:],
|
||||||
|
CodeHash: data.CodeHash,
|
||||||
|
AddressHash: it.Key,
|
||||||
|
}
|
||||||
address *common.Address
|
address *common.Address
|
||||||
|
addr common.Address
|
||||||
|
addrBytes = s.trie.GetKey(it.Key)
|
||||||
)
|
)
|
||||||
if addrBytes == nil {
|
if addrBytes == nil {
|
||||||
// Preimage missing
|
|
||||||
missingPreimages++
|
missingPreimages++
|
||||||
if conf.OnlyWithAddresses {
|
if conf.OnlyWithAddresses {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
addr = common.BytesToAddress(addrBytes)
|
||||||
address = &addr
|
address = &addr
|
||||||
|
account.Address = address
|
||||||
}
|
}
|
||||||
obj := newObject(s, addr, &data)
|
obj := newObject(s, addr, &data)
|
||||||
if !conf.SkipCode {
|
if !conf.SkipCode {
|
||||||
|
|
@ -220,12 +208,13 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
return nextKey
|
return nextKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawDump returns the entire state an a single large object
|
// RawDump returns the state. If the processing is aborted e.g. due to options
|
||||||
|
// reaching Max, the `Next` key is set on the returned Dump.
|
||||||
func (s *StateDB) RawDump(opts *DumpConfig) Dump {
|
func (s *StateDB) RawDump(opts *DumpConfig) Dump {
|
||||||
dump := &Dump{
|
dump := &Dump{
|
||||||
Accounts: make(map[common.Address]DumpAccount),
|
Accounts: make(map[string]DumpAccount),
|
||||||
}
|
}
|
||||||
s.DumpToCollector(dump, opts)
|
dump.Next = s.DumpToCollector(dump, opts)
|
||||||
return *dump
|
return *dump
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,7 +223,7 @@ func (s *StateDB) Dump(opts *DumpConfig) []byte {
|
||||||
dump := s.RawDump(opts)
|
dump := s.RawDump(opts)
|
||||||
json, err := json.MarshalIndent(dump, "", " ")
|
json, err := json.MarshalIndent(dump, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Dump err", err)
|
log.Error("Error dumping state", "err", err)
|
||||||
}
|
}
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
@ -243,12 +232,3 @@ func (s *StateDB) Dump(opts *DumpConfig) []byte {
|
||||||
func (s *StateDB) IterativeDump(opts *DumpConfig, output *json.Encoder) {
|
func (s *StateDB) IterativeDump(opts *DumpConfig, output *json.Encoder) {
|
||||||
s.DumpToCollector(iterativeDump{output}, opts)
|
s.DumpToCollector(iterativeDump{output}, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IteratorDump dumps out a batch of accounts starts with the given start key
|
|
||||||
func (s *StateDB) IteratorDump(opts *DumpConfig) IteratorDump {
|
|
||||||
iterator := &IteratorDump{
|
|
||||||
Accounts: make(map[common.Address]DumpAccount),
|
|
||||||
}
|
|
||||||
iterator.Next = s.DumpToCollector(iterator, opts)
|
|
||||||
return *iterator
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -601,7 +601,7 @@ func testGenerateWithExtraAccounts(t *testing.T, scheme string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func enableLogging() {
|
func enableLogging() {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that snapshot generation when an extra account with storage exists in the snap state.
|
// Tests that snapshot generation when an extra account with storage exists in the snap state.
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,10 @@ func (s *stateObject) empty() bool {
|
||||||
|
|
||||||
// newObject creates a state object.
|
// newObject creates a state object.
|
||||||
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
|
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
|
||||||
origin := acct
|
var (
|
||||||
|
origin = acct
|
||||||
|
created = acct == nil // true if the account was not existent
|
||||||
|
)
|
||||||
if acct == nil {
|
if acct == nil {
|
||||||
acct = types.NewEmptyStateAccount()
|
acct = types.NewEmptyStateAccount()
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +114,7 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
|
||||||
originStorage: make(Storage),
|
originStorage: make(Storage),
|
||||||
pendingStorage: make(Storage),
|
pendingStorage: make(Storage),
|
||||||
dirtyStorage: make(Storage),
|
dirtyStorage: make(Storage),
|
||||||
|
created: created,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ func TestDump(t *testing.T) {
|
||||||
"nonce": 0,
|
"nonce": 0,
|
||||||
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
|
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
|
||||||
|
"address": "0x0000000000000000000000000000000000000001",
|
||||||
"key": "0x1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d"
|
"key": "0x1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d"
|
||||||
},
|
},
|
||||||
"0x0000000000000000000000000000000000000002": {
|
"0x0000000000000000000000000000000000000002": {
|
||||||
|
|
@ -78,6 +79,7 @@ func TestDump(t *testing.T) {
|
||||||
"nonce": 0,
|
"nonce": 0,
|
||||||
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||||
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
|
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
|
||||||
|
"address": "0x0000000000000000000000000000000000000002",
|
||||||
"key": "0xd52688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf62"
|
"key": "0xd52688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf62"
|
||||||
},
|
},
|
||||||
"0x0000000000000000000000000000000000000102": {
|
"0x0000000000000000000000000000000000000102": {
|
||||||
|
|
@ -86,6 +88,7 @@ func TestDump(t *testing.T) {
|
||||||
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||||
"codeHash": "0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3",
|
"codeHash": "0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3",
|
||||||
"code": "0x03030303030303",
|
"code": "0x03030303030303",
|
||||||
|
"address": "0x0000000000000000000000000000000000000102",
|
||||||
"key": "0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1"
|
"key": "0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -658,9 +658,6 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
|
||||||
delete(s.accountsOrigin, prev.address)
|
delete(s.accountsOrigin, prev.address)
|
||||||
delete(s.storagesOrigin, prev.address)
|
delete(s.storagesOrigin, prev.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
newobj.created = true
|
|
||||||
|
|
||||||
s.setStateObject(newobj)
|
s.setStateObject(newobj)
|
||||||
if prev != nil && !prev.deleted {
|
if prev != nil && !prev.deleted {
|
||||||
return newobj, prev
|
return newobj, prev
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,10 @@ import (
|
||||||
// ExecutionResult includes all output after executing given evm
|
// ExecutionResult includes all output after executing given evm
|
||||||
// message no matter the execution itself is successful or not.
|
// message no matter the execution itself is successful or not.
|
||||||
type ExecutionResult struct {
|
type ExecutionResult struct {
|
||||||
UsedGas uint64 // Total used gas but include the refunded gas
|
UsedGas uint64 // Total used gas, not including the refunded gas
|
||||||
Err error // Any error encountered during the execution(listed in core/vm/errors.go)
|
RefundedGas uint64 // Total gas refunded after execution
|
||||||
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
|
Err error // Any error encountered during the execution(listed in core/vm/errors.go)
|
||||||
|
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the internal evm error which allows us for further
|
// Unwrap returns the internal evm error which allows us for further
|
||||||
|
|
@ -419,12 +420,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||||
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value)
|
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gasRefund uint64
|
||||||
if !rules.IsLondon {
|
if !rules.IsLondon {
|
||||||
// Before EIP-3529: refunds were capped to gasUsed / 2
|
// Before EIP-3529: refunds were capped to gasUsed / 2
|
||||||
st.refundGas(params.RefundQuotient)
|
gasRefund = st.refundGas(params.RefundQuotient)
|
||||||
} else {
|
} else {
|
||||||
// After EIP-3529: refunds are capped to gasUsed / 5
|
// After EIP-3529: refunds are capped to gasUsed / 5
|
||||||
st.refundGas(params.RefundQuotientEIP3529)
|
gasRefund = st.refundGas(params.RefundQuotientEIP3529)
|
||||||
}
|
}
|
||||||
effectiveTip := msg.GasPrice
|
effectiveTip := msg.GasPrice
|
||||||
if rules.IsLondon {
|
if rules.IsLondon {
|
||||||
|
|
@ -442,13 +444,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ExecutionResult{
|
return &ExecutionResult{
|
||||||
UsedGas: st.gasUsed(),
|
UsedGas: st.gasUsed(),
|
||||||
Err: vmerr,
|
RefundedGas: gasRefund,
|
||||||
ReturnData: ret,
|
Err: vmerr,
|
||||||
|
ReturnData: ret,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *StateTransition) refundGas(refundQuotient uint64) {
|
func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
|
||||||
// Apply refund counter, capped to a refund quotient
|
// Apply refund counter, capped to a refund quotient
|
||||||
refund := st.gasUsed() / refundQuotient
|
refund := st.gasUsed() / refundQuotient
|
||||||
if refund > st.state.GetRefund() {
|
if refund > st.state.GetRefund() {
|
||||||
|
|
@ -463,6 +466,8 @@ func (st *StateTransition) refundGas(refundQuotient uint64) {
|
||||||
// Also return remaining gas to the block gas counter so it is
|
// Also return remaining gas to the block gas counter so it is
|
||||||
// available for the next transaction.
|
// available for the next transaction.
|
||||||
st.gp.AddGas(st.gasRemaining)
|
st.gp.AddGas(st.gasRemaining)
|
||||||
|
|
||||||
|
return refund
|
||||||
}
|
}
|
||||||
|
|
||||||
// gasUsed returns the amount of gas used up by the state transition.
|
// gasUsed returns the amount of gas used up by the state transition.
|
||||||
|
|
|
||||||
|
|
@ -319,7 +319,7 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
|
||||||
// - 3. All transactions after a nonce gap must be dropped
|
// - 3. All transactions after a nonce gap must be dropped
|
||||||
// - 4. All transactions after an underpriced one (including it) must be dropped
|
// - 4. All transactions after an underpriced one (including it) must be dropped
|
||||||
func TestOpenDrops(t *testing.T) {
|
func TestOpenDrops(t *testing.T) {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
// Create a temporary folder for the persistent backend
|
// Create a temporary folder for the persistent backend
|
||||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
storage, _ := os.MkdirTemp("", "blobpool-")
|
||||||
|
|
@ -600,7 +600,7 @@ func TestOpenDrops(t *testing.T) {
|
||||||
// - 2. Eviction thresholds are calculated correctly for the sequences
|
// - 2. Eviction thresholds are calculated correctly for the sequences
|
||||||
// - 3. Balance usage of an account is totals across all transactions
|
// - 3. Balance usage of an account is totals across all transactions
|
||||||
func TestOpenIndex(t *testing.T) {
|
func TestOpenIndex(t *testing.T) {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
// Create a temporary folder for the persistent backend
|
// Create a temporary folder for the persistent backend
|
||||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
storage, _ := os.MkdirTemp("", "blobpool-")
|
||||||
|
|
@ -689,7 +689,7 @@ func TestOpenIndex(t *testing.T) {
|
||||||
// Tests that after indexing all the loaded transactions from disk, a price heap
|
// Tests that after indexing all the loaded transactions from disk, a price heap
|
||||||
// is correctly constructed based on the head basefee and blobfee.
|
// is correctly constructed based on the head basefee and blobfee.
|
||||||
func TestOpenHeap(t *testing.T) {
|
func TestOpenHeap(t *testing.T) {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
// Create a temporary folder for the persistent backend
|
// Create a temporary folder for the persistent backend
|
||||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
storage, _ := os.MkdirTemp("", "blobpool-")
|
||||||
|
|
@ -776,7 +776,7 @@ func TestOpenHeap(t *testing.T) {
|
||||||
// Tests that after the pool's previous state is loaded back, any transactions
|
// Tests that after the pool's previous state is loaded back, any transactions
|
||||||
// over the new storage cap will get dropped.
|
// over the new storage cap will get dropped.
|
||||||
func TestOpenCap(t *testing.T) {
|
func TestOpenCap(t *testing.T) {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
// Create a temporary folder for the persistent backend
|
// Create a temporary folder for the persistent backend
|
||||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
storage, _ := os.MkdirTemp("", "blobpool-")
|
||||||
|
|
@ -868,7 +868,7 @@ func TestOpenCap(t *testing.T) {
|
||||||
// specific to the blob pool. It does not do an exhaustive transaction validity
|
// specific to the blob pool. It does not do an exhaustive transaction validity
|
||||||
// check.
|
// check.
|
||||||
func TestAdd(t *testing.T) {
|
func TestAdd(t *testing.T) {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
// seed is a helper tumpe to seed an initial state db and pool
|
// seed is a helper tumpe to seed an initial state db and pool
|
||||||
type seed struct {
|
type seed struct {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
// Use of this source code is governed by a BSD-style license that can be found in
|
// Use of this source code is governed by a BSD-style license that can be found in
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !gofuzz && cgo
|
||||||
|
// +build !gofuzz,cgo
|
||||||
|
|
||||||
package secp256k1
|
package secp256k1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ func (api *DebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error)
|
||||||
const AccountRangeMaxResults = 256
|
const AccountRangeMaxResults = 256
|
||||||
|
|
||||||
// AccountRange enumerates all accounts in the given block and start point in paging request
|
// AccountRange enumerates all accounts in the given block and start point in paging request
|
||||||
func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.IteratorDump, error) {
|
func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.Dump, error) {
|
||||||
var stateDb *state.StateDB
|
var stateDb *state.StateDB
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
@ -144,7 +144,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
|
||||||
// the miner and operate on those
|
// the miner and operate on those
|
||||||
_, stateDb = api.eth.miner.Pending()
|
_, stateDb = api.eth.miner.Pending()
|
||||||
if stateDb == nil {
|
if stateDb == nil {
|
||||||
return state.IteratorDump{}, errors.New("pending state is not available")
|
return state.Dump{}, errors.New("pending state is not available")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var header *types.Header
|
var header *types.Header
|
||||||
|
|
@ -158,29 +158,29 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
|
||||||
default:
|
default:
|
||||||
block := api.eth.blockchain.GetBlockByNumber(uint64(number))
|
block := api.eth.blockchain.GetBlockByNumber(uint64(number))
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
|
return state.Dump{}, fmt.Errorf("block #%d not found", number)
|
||||||
}
|
}
|
||||||
header = block.Header()
|
header = block.Header()
|
||||||
}
|
}
|
||||||
if header == nil {
|
if header == nil {
|
||||||
return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
|
return state.Dump{}, fmt.Errorf("block #%d not found", number)
|
||||||
}
|
}
|
||||||
stateDb, err = api.eth.BlockChain().StateAt(header.Root)
|
stateDb, err = api.eth.BlockChain().StateAt(header.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return state.IteratorDump{}, err
|
return state.Dump{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if hash, ok := blockNrOrHash.Hash(); ok {
|
} else if hash, ok := blockNrOrHash.Hash(); ok {
|
||||||
block := api.eth.blockchain.GetBlockByHash(hash)
|
block := api.eth.blockchain.GetBlockByHash(hash)
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex())
|
return state.Dump{}, fmt.Errorf("block %s not found", hash.Hex())
|
||||||
}
|
}
|
||||||
stateDb, err = api.eth.BlockChain().StateAt(block.Root())
|
stateDb, err = api.eth.BlockChain().StateAt(block.Root())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return state.IteratorDump{}, err
|
return state.Dump{}, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return state.IteratorDump{}, errors.New("either block number or block hash must be specified")
|
return state.Dump{}, errors.New("either block number or block hash must be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := &state.DumpConfig{
|
opts := &state.DumpConfig{
|
||||||
|
|
@ -193,7 +193,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
|
||||||
if maxResults > AccountRangeMaxResults || maxResults <= 0 {
|
if maxResults > AccountRangeMaxResults || maxResults <= 0 {
|
||||||
opts.Max = AccountRangeMaxResults
|
opts.Max = AccountRangeMaxResults
|
||||||
}
|
}
|
||||||
return stateDb.IteratorDump(opts), nil
|
return stateDb.RawDump(opts), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageRangeResult is the result of a debug_storageRangeAt API call.
|
// StorageRangeResult is the result of a debug_storageRangeAt API call.
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
@ -35,8 +36,8 @@ import (
|
||||||
|
|
||||||
var dumper = spew.ConfigState{Indent: " "}
|
var dumper = spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.IteratorDump {
|
func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.Dump {
|
||||||
result := statedb.IteratorDump(&state.DumpConfig{
|
result := statedb.RawDump(&state.DumpConfig{
|
||||||
SkipCode: true,
|
SkipCode: true,
|
||||||
SkipStorage: true,
|
SkipStorage: true,
|
||||||
OnlyWithAddresses: false,
|
OnlyWithAddresses: false,
|
||||||
|
|
@ -47,12 +48,12 @@ func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, st
|
||||||
if len(result.Accounts) != expectedNum {
|
if len(result.Accounts) != expectedNum {
|
||||||
t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts))
|
t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts))
|
||||||
}
|
}
|
||||||
for address := range result.Accounts {
|
for addr, acc := range result.Accounts {
|
||||||
if address == (common.Address{}) {
|
if strings.HasSuffix(addr, "pre") || acc.Address == nil {
|
||||||
t.Fatalf("empty address returned")
|
t.Fatalf("account without prestate (address) returned: %v", addr)
|
||||||
}
|
}
|
||||||
if !statedb.Exist(address) {
|
if !statedb.Exist(*acc.Address) {
|
||||||
t.Fatalf("account not found in state %s", address.Hex())
|
t.Fatalf("account not found in state %s", acc.Address.Hex())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
@ -92,16 +93,16 @@ func TestAccountRange(t *testing.T) {
|
||||||
secondResult := accountRangeTest(t, &trie, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults)
|
secondResult := accountRangeTest(t, &trie, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults)
|
||||||
|
|
||||||
hList := make([]common.Hash, 0)
|
hList := make([]common.Hash, 0)
|
||||||
for addr1 := range firstResult.Accounts {
|
for addr1, acc := range firstResult.Accounts {
|
||||||
// If address is empty, then it makes no sense to compare
|
// If address is non-available, then it makes no sense to compare
|
||||||
// them as they might be two different accounts.
|
// them as they might be two different accounts.
|
||||||
if addr1 == (common.Address{}) {
|
if acc.Address == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, duplicate := secondResult.Accounts[addr1]; duplicate {
|
if _, duplicate := secondResult.Accounts[addr1]; duplicate {
|
||||||
t.Fatalf("pagination test failed: results should not overlap")
|
t.Fatalf("pagination test failed: results should not overlap")
|
||||||
}
|
}
|
||||||
hList = append(hList, crypto.Keccak256Hash(addr1.Bytes()))
|
hList = append(hList, crypto.Keccak256Hash(acc.Address.Bytes()))
|
||||||
}
|
}
|
||||||
// Test to see if it's possible to recover from the middle of the previous
|
// Test to see if it's possible to recover from the middle of the previous
|
||||||
// set and get an even split between the first and second sets.
|
// set and get an even split between the first and second sets.
|
||||||
|
|
@ -140,7 +141,7 @@ func TestEmptyAccountRange(t *testing.T) {
|
||||||
st.Commit(0, true)
|
st.Commit(0, true)
|
||||||
st, _ = state.New(types.EmptyRootHash, statedb, nil)
|
st, _ = state.New(types.EmptyRootHash, statedb, nil)
|
||||||
|
|
||||||
results := st.IteratorDump(&state.DumpConfig{
|
results := st.RawDump(&state.DumpConfig{
|
||||||
SkipCode: true,
|
SkipCode: true,
|
||||||
SkipStorage: true,
|
SkipStorage: true,
|
||||||
OnlyWithAddresses: true,
|
OnlyWithAddresses: true,
|
||||||
|
|
|
||||||
|
|
@ -611,7 +611,8 @@ func (api *ConsensusAPI) delayPayloadImport(block *types.Block) (engine.PayloadS
|
||||||
// Although we don't want to trigger a sync, if there is one already in
|
// Although we don't want to trigger a sync, if there is one already in
|
||||||
// progress, try to extend if with the current payload request to relieve
|
// progress, try to extend if with the current payload request to relieve
|
||||||
// some strain from the forkchoice update.
|
// some strain from the forkchoice update.
|
||||||
if err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()); err == nil {
|
err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header())
|
||||||
|
if err == nil {
|
||||||
log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash())
|
log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash())
|
||||||
return engine.PayloadStatusV1{Status: engine.SYNCING}, nil
|
return engine.PayloadStatusV1{Status: engine.SYNCING}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -623,12 +624,12 @@ func (api *ConsensusAPI) delayPayloadImport(block *types.Block) (engine.PayloadS
|
||||||
// In full sync mode, failure to import a well-formed block can only mean
|
// In full sync mode, failure to import a well-formed block can only mean
|
||||||
// that the parent state is missing and the syncer rejected extending the
|
// that the parent state is missing and the syncer rejected extending the
|
||||||
// current cycle with the new payload.
|
// current cycle with the new payload.
|
||||||
log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash())
|
log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash(), "reason", err)
|
||||||
} else {
|
} else {
|
||||||
// In non-full sync mode (i.e. snap sync) all payloads are rejected until
|
// In non-full sync mode (i.e. snap sync) all payloads are rejected until
|
||||||
// snap sync terminates as snap sync relies on direct database injections
|
// snap sync terminates as snap sync relies on direct database injections
|
||||||
// and cannot afford concurrent out-if-band modifications via imports.
|
// and cannot afford concurrent out-if-band modifications via imports.
|
||||||
log.Warn("Ignoring payload while snap syncing", "number", block.NumberU64(), "hash", block.Hash())
|
log.Warn("Ignoring payload while snap syncing", "number", block.NumberU64(), "hash", block.Hash(), "reason", err)
|
||||||
}
|
}
|
||||||
return engine.PayloadStatusV1{Status: engine.SYNCING}, nil
|
return engine.PayloadStatusV1{Status: engine.SYNCING}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1562,7 +1562,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) {
|
||||||
|
|
||||||
// This checks that beaconRoot is applied to the state from the engine API.
|
// This checks that beaconRoot is applied to the state from the engine API.
|
||||||
func TestParentBeaconBlockRoot(t *testing.T) {
|
func TestParentBeaconBlockRoot(t *testing.T) {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), log.LevelTrace, true)))
|
||||||
|
|
||||||
genesis, blocks := generateMergeChain(10, true)
|
genesis, blocks := generateMergeChain(10, true)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -31,6 +32,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// makeChain creates a chain of n blocks starting at and including parent.
|
// makeChain creates a chain of n blocks starting at and including parent.
|
||||||
|
|
@ -271,7 +273,7 @@ func XTestDelivery(t *testing.T) {
|
||||||
world.chain = blo
|
world.chain = blo
|
||||||
world.progress(10)
|
world.progress(10)
|
||||||
if false {
|
if false {
|
||||||
log.Root().SetHandler(log.StdoutHandler)
|
log.SetDefault(log.NewLogger(slog.NewTextHandler(os.Stdout, nil)))
|
||||||
}
|
}
|
||||||
q := newQueue(10, 10)
|
q := newQueue(10, 10)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,17 @@ var errSyncReorged = errors.New("sync reorged")
|
||||||
// might still be propagating.
|
// might still be propagating.
|
||||||
var errTerminated = errors.New("terminated")
|
var errTerminated = errors.New("terminated")
|
||||||
|
|
||||||
// errReorgDenied is returned if an attempt is made to extend the beacon chain
|
// errChainReorged is an internal helper error to signal that the header chain
|
||||||
// with a new header, but it does not link up to the existing sync.
|
// of the current sync cycle was (partially) reorged.
|
||||||
var errReorgDenied = errors.New("non-forced head reorg denied")
|
var errChainReorged = errors.New("chain reorged")
|
||||||
|
|
||||||
|
// errChainGapped is an internal helper error to signal that the header chain
|
||||||
|
// of the current sync cycle is gaped with the one advertised by consensus client.
|
||||||
|
var errChainGapped = errors.New("chain gapped")
|
||||||
|
|
||||||
|
// errChainForked is an internal helper error to signal that the header chain
|
||||||
|
// of the current sync cycle is forked with the one advertised by consensus client.
|
||||||
|
var errChainForked = errors.New("chain forked")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Tuning parameters is nice, but the scratch space must be assignable in
|
// Tuning parameters is nice, but the scratch space must be assignable in
|
||||||
|
|
@ -271,9 +279,9 @@ func (s *skeleton) startup() {
|
||||||
newhead, err := s.sync(head)
|
newhead, err := s.sync(head)
|
||||||
switch {
|
switch {
|
||||||
case err == errSyncLinked:
|
case err == errSyncLinked:
|
||||||
// Sync cycle linked up to the genesis block. Tear down the loop
|
// Sync cycle linked up to the genesis block, or the existent chain
|
||||||
// and restart it so, it can properly notify the backfiller. Don't
|
// segment. Tear down the loop and restart it so, it can properly
|
||||||
// account a new head.
|
// notify the backfiller. Don't account a new head.
|
||||||
head = nil
|
head = nil
|
||||||
|
|
||||||
case err == errSyncMerged:
|
case err == errSyncMerged:
|
||||||
|
|
@ -457,15 +465,16 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) {
|
||||||
// we don't seamlessly integrate reorgs to keep things simple. If the
|
// we don't seamlessly integrate reorgs to keep things simple. If the
|
||||||
// network starts doing many mini reorgs, it might be worthwhile handling
|
// network starts doing many mini reorgs, it might be worthwhile handling
|
||||||
// a limited depth without an error.
|
// a limited depth without an error.
|
||||||
if reorged := s.processNewHead(event.header, event.final, event.force); reorged {
|
if err := s.processNewHead(event.header, event.final); err != nil {
|
||||||
// If a reorg is needed, and we're forcing the new head, signal
|
// If a reorg is needed, and we're forcing the new head, signal
|
||||||
// the syncer to tear down and start over. Otherwise, drop the
|
// the syncer to tear down and start over. Otherwise, drop the
|
||||||
// non-force reorg.
|
// non-force reorg.
|
||||||
if event.force {
|
if event.force {
|
||||||
event.errc <- nil // forced head reorg accepted
|
event.errc <- nil // forced head reorg accepted
|
||||||
|
log.Info("Restarting sync cycle", "reason", err)
|
||||||
return event.header, errSyncReorged
|
return event.header, errSyncReorged
|
||||||
}
|
}
|
||||||
event.errc <- errReorgDenied
|
event.errc <- err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
event.errc <- nil // head extension accepted
|
event.errc <- nil // head extension accepted
|
||||||
|
|
@ -610,7 +619,7 @@ func (s *skeleton) saveSyncStatus(db ethdb.KeyValueWriter) {
|
||||||
// accepts and integrates it into the skeleton or requests a reorg. Upon reorg,
|
// accepts and integrates it into the skeleton or requests a reorg. Upon reorg,
|
||||||
// the syncer will tear itself down and restart with a fresh head. It is simpler
|
// the syncer will tear itself down and restart with a fresh head. It is simpler
|
||||||
// to reconstruct the sync state than to mutate it and hope for the best.
|
// to reconstruct the sync state than to mutate it and hope for the best.
|
||||||
func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force bool) bool {
|
func (s *skeleton) processNewHead(head *types.Header, final *types.Header) error {
|
||||||
// If a new finalized block was announced, update the sync process independent
|
// If a new finalized block was announced, update the sync process independent
|
||||||
// of what happens with the sync head below
|
// of what happens with the sync head below
|
||||||
if final != nil {
|
if final != nil {
|
||||||
|
|
@ -631,26 +640,17 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force
|
||||||
// once more, ignore it instead of tearing down sync for a noop.
|
// once more, ignore it instead of tearing down sync for a noop.
|
||||||
if lastchain.Head == lastchain.Tail {
|
if lastchain.Head == lastchain.Tail {
|
||||||
if current := rawdb.ReadSkeletonHeader(s.db, number); current.Hash() == head.Hash() {
|
if current := rawdb.ReadSkeletonHeader(s.db, number); current.Hash() == head.Hash() {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Not a noop / double head announce, abort with a reorg
|
// Not a noop / double head announce, abort with a reorg
|
||||||
if force {
|
return fmt.Errorf("%w, tail: %d, head: %d, newHead: %d", errChainReorged, lastchain.Tail, lastchain.Head, number)
|
||||||
log.Warn("Beacon chain reorged", "tail", lastchain.Tail, "head", lastchain.Head, "newHead", number)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
if lastchain.Head+1 < number {
|
if lastchain.Head+1 < number {
|
||||||
if force {
|
return fmt.Errorf("%w, head: %d, newHead: %d", errChainGapped, lastchain.Head, number)
|
||||||
log.Warn("Beacon chain gapped", "head", lastchain.Head, "newHead", number)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
if parent := rawdb.ReadSkeletonHeader(s.db, number-1); parent.Hash() != head.ParentHash {
|
if parent := rawdb.ReadSkeletonHeader(s.db, number-1); parent.Hash() != head.ParentHash {
|
||||||
if force {
|
return fmt.Errorf("%w, ancestor: %d, hash: %s, want: %s", errChainForked, number-1, parent.Hash(), head.ParentHash)
|
||||||
log.Warn("Beacon chain forked", "ancestor", number-1, "hash", parent.Hash(), "want", head.ParentHash)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
// New header seems to be in the last subchain range. Unwind any extra headers
|
// New header seems to be in the last subchain range. Unwind any extra headers
|
||||||
// from the chain tip and insert the new head. We won't delete any trimmed
|
// from the chain tip and insert the new head. We won't delete any trimmed
|
||||||
|
|
@ -666,7 +666,7 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Crit("Failed to write skeleton sync status", "err", err)
|
log.Crit("Failed to write skeleton sync status", "err", err)
|
||||||
}
|
}
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// assignTasks attempts to match idle peers to pending header retrievals.
|
// assignTasks attempts to match idle peers to pending header retrievals.
|
||||||
|
|
|
||||||
|
|
@ -434,7 +434,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
|
||||||
newstate: []*subchain{
|
newstate: []*subchain{
|
||||||
{Head: 49, Tail: 49},
|
{Head: 49, Tail: 49},
|
||||||
},
|
},
|
||||||
err: errReorgDenied,
|
err: errChainReorged,
|
||||||
},
|
},
|
||||||
// Initialize a sync and try to extend it with a number-wise sequential
|
// Initialize a sync and try to extend it with a number-wise sequential
|
||||||
// header, but a hash wise non-linking one.
|
// header, but a hash wise non-linking one.
|
||||||
|
|
@ -444,7 +444,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
|
||||||
newstate: []*subchain{
|
newstate: []*subchain{
|
||||||
{Head: 49, Tail: 49},
|
{Head: 49, Tail: 49},
|
||||||
},
|
},
|
||||||
err: errReorgDenied,
|
err: errChainForked,
|
||||||
},
|
},
|
||||||
// Initialize a sync and try to extend it with a non-linking future block.
|
// Initialize a sync and try to extend it with a non-linking future block.
|
||||||
{
|
{
|
||||||
|
|
@ -453,7 +453,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
|
||||||
newstate: []*subchain{
|
newstate: []*subchain{
|
||||||
{Head: 49, Tail: 49},
|
{Head: 49, Tail: 49},
|
||||||
},
|
},
|
||||||
err: errReorgDenied,
|
err: errChainGapped,
|
||||||
},
|
},
|
||||||
// Initialize a sync and try to extend it with a past canonical block.
|
// Initialize a sync and try to extend it with a past canonical block.
|
||||||
{
|
{
|
||||||
|
|
@ -462,7 +462,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
|
||||||
newstate: []*subchain{
|
newstate: []*subchain{
|
||||||
{Head: 50, Tail: 50},
|
{Head: 50, Tail: 50},
|
||||||
},
|
},
|
||||||
err: errReorgDenied,
|
err: errChainReorged,
|
||||||
},
|
},
|
||||||
// Initialize a sync and try to extend it with a past sidechain block.
|
// Initialize a sync and try to extend it with a past sidechain block.
|
||||||
{
|
{
|
||||||
|
|
@ -471,7 +471,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
|
||||||
newstate: []*subchain{
|
newstate: []*subchain{
|
||||||
{Head: 50, Tail: 50},
|
{Head: 50, Tail: 50},
|
||||||
},
|
},
|
||||||
err: errReorgDenied,
|
err: errChainReorged,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
|
|
@ -487,7 +487,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
|
||||||
skeleton.Sync(tt.head, nil, true)
|
skeleton.Sync(tt.head, nil, true)
|
||||||
|
|
||||||
<-wait
|
<-wait
|
||||||
if err := skeleton.Sync(tt.extend, nil, false); err != tt.err {
|
if err := skeleton.Sync(tt.extend, nil, false); !errors.Is(err, tt.err) {
|
||||||
t.Errorf("test %d: extension failure mismatch: have %v, want %v", i, err, tt.err)
|
t.Errorf("test %d: extension failure mismatch: have %v, want %v", i, err, tt.err)
|
||||||
}
|
}
|
||||||
skeleton.Terminate()
|
skeleton.Terminate()
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ type Options struct {
|
||||||
Chain core.ChainContext // Chain context to access past block hashes
|
Chain core.ChainContext // Chain context to access past block hashes
|
||||||
Header *types.Header // Header defining the block context to execute in
|
Header *types.Header // Header defining the block context to execute in
|
||||||
State *state.StateDB // Pre-state on top of which to estimate the gas
|
State *state.StateDB // Pre-state on top of which to estimate the gas
|
||||||
|
|
||||||
|
ErrorRatio float64 // Allowed overestimation ratio for faster estimation termination
|
||||||
}
|
}
|
||||||
|
|
||||||
// Estimate returns the lowest possible gas limit that allows the transaction to
|
// Estimate returns the lowest possible gas limit that allows the transaction to
|
||||||
|
|
@ -86,16 +88,28 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
||||||
if transfer == nil {
|
if transfer == nil {
|
||||||
transfer = new(big.Int)
|
transfer = new(big.Int)
|
||||||
}
|
}
|
||||||
log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
|
log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance,
|
||||||
"sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)
|
"sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)
|
||||||
hi = allowance.Uint64()
|
hi = allowance.Uint64()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Recap the highest gas allowance with specified gascap.
|
// Recap the highest gas allowance with specified gascap.
|
||||||
if gasCap != 0 && hi > gasCap {
|
if gasCap != 0 && hi > gasCap {
|
||||||
log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
|
log.Debug("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
|
||||||
hi = gasCap
|
hi = gasCap
|
||||||
}
|
}
|
||||||
|
// If the transaction is a plain value transfer, short circuit estimation and
|
||||||
|
// directly try 21000. Returning 21000 without any execution is dangerous as
|
||||||
|
// some tx field combos might bump the price up even for plain transfers (e.g.
|
||||||
|
// unused access list items). Ever so slightly wasteful, but safer overall.
|
||||||
|
if len(call.Data) == 0 {
|
||||||
|
if call.To != nil && opts.State.GetCodeSize(*call.To) == 0 {
|
||||||
|
failed, _, err := execute(ctx, call, opts, params.TxGas)
|
||||||
|
if !failed && err == nil {
|
||||||
|
return params.TxGas, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// We first execute the transaction at the highest allowable gas limit, since if this fails we
|
// We first execute the transaction at the highest allowable gas limit, since if this fails we
|
||||||
// can return error immediately.
|
// can return error immediately.
|
||||||
failed, result, err := execute(ctx, call, opts, hi)
|
failed, result, err := execute(ctx, call, opts, hi)
|
||||||
|
|
@ -115,8 +129,35 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
||||||
// limit for these cases anyway.
|
// limit for these cases anyway.
|
||||||
lo = result.UsedGas - 1
|
lo = result.UsedGas - 1
|
||||||
|
|
||||||
|
// There's a fairly high chance for the transaction to execute successfully
|
||||||
|
// with gasLimit set to the first execution's usedGas + gasRefund. Explicitly
|
||||||
|
// check that gas amount and use as a limit for the binary search.
|
||||||
|
optimisticGasLimit := (result.UsedGas + result.RefundedGas + params.CallStipend) * 64 / 63
|
||||||
|
if optimisticGasLimit < hi {
|
||||||
|
failed, _, err = execute(ctx, call, opts, optimisticGasLimit)
|
||||||
|
if err != nil {
|
||||||
|
// This should not happen under normal conditions since if we make it this far the
|
||||||
|
// transaction had run without error at least once before.
|
||||||
|
log.Error("Execution error in estimate gas", "err", err)
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
if failed {
|
||||||
|
lo = optimisticGasLimit
|
||||||
|
} else {
|
||||||
|
hi = optimisticGasLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
// Binary search for the smallest gas limit that allows the tx to execute successfully.
|
// Binary search for the smallest gas limit that allows the tx to execute successfully.
|
||||||
for lo+1 < hi {
|
for lo+1 < hi {
|
||||||
|
if opts.ErrorRatio > 0 {
|
||||||
|
// It is a bit pointless to return a perfect estimation, as changing
|
||||||
|
// network conditions require the caller to bump it up anyway. Since
|
||||||
|
// wallets tend to use 20-25% bump, allowing a small approximation
|
||||||
|
// error is fine (as long as it's upwards).
|
||||||
|
if float64(hi-lo)/float64(hi) < opts.ErrorRatio {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
mid := (hi + lo) / 2
|
mid := (hi + lo) / 2
|
||||||
if mid > lo*2 {
|
if mid > lo*2 {
|
||||||
// Most txs don't need much higher gas limit than their gas used, and most txs don't
|
// Most txs don't need much higher gas limit than their gas used, and most txs don't
|
||||||
|
|
|
||||||
|
|
@ -142,19 +142,29 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracer
|
||||||
vm: vm,
|
vm: vm,
|
||||||
ctx: make(map[string]goja.Value),
|
ctx: make(map[string]goja.Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.setTypeConverters()
|
||||||
|
t.setBuiltinFunctions()
|
||||||
|
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = new(tracers.Context)
|
ctx = new(tracers.Context)
|
||||||
}
|
}
|
||||||
if ctx.BlockHash != (common.Hash{}) {
|
if ctx.BlockHash != (common.Hash{}) {
|
||||||
t.ctx["blockHash"] = vm.ToValue(ctx.BlockHash.Bytes())
|
blockHash, err := t.toBuf(vm, ctx.BlockHash.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.ctx["blockHash"] = blockHash
|
||||||
if ctx.TxHash != (common.Hash{}) {
|
if ctx.TxHash != (common.Hash{}) {
|
||||||
t.ctx["txIndex"] = vm.ToValue(ctx.TxIndex)
|
t.ctx["txIndex"] = vm.ToValue(ctx.TxIndex)
|
||||||
t.ctx["txHash"] = vm.ToValue(ctx.TxHash.Bytes())
|
txHash, err := t.toBuf(vm, ctx.TxHash.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.ctx["txHash"] = txHash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.setTypeConverters()
|
|
||||||
t.setBuiltinFunctions()
|
|
||||||
ret, err := vm.RunString("(" + code + ")")
|
ret, err := vm.RunString("(" + code + ")")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -224,6 +234,10 @@ func (t *jsTracer) CaptureTxEnd(restGas uint64) {
|
||||||
|
|
||||||
// CaptureStart implements the Tracer interface to initialize the tracing operation.
|
// CaptureStart implements the Tracer interface to initialize the tracing operation.
|
||||||
func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||||
|
cancel := func(err error) {
|
||||||
|
t.err = err
|
||||||
|
t.env.Cancel()
|
||||||
|
}
|
||||||
t.env = env
|
t.env = env
|
||||||
db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf}
|
db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf}
|
||||||
t.dbValue = db.setupObject()
|
t.dbValue = db.setupObject()
|
||||||
|
|
@ -232,19 +246,34 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
|
||||||
} else {
|
} else {
|
||||||
t.ctx["type"] = t.vm.ToValue("CALL")
|
t.ctx["type"] = t.vm.ToValue("CALL")
|
||||||
}
|
}
|
||||||
t.ctx["from"] = t.vm.ToValue(from.Bytes())
|
fromVal, err := t.toBuf(t.vm, from.Bytes())
|
||||||
t.ctx["to"] = t.vm.ToValue(to.Bytes())
|
if err != nil {
|
||||||
t.ctx["input"] = t.vm.ToValue(input)
|
cancel(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.ctx["from"] = fromVal
|
||||||
|
toVal, err := t.toBuf(t.vm, to.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
cancel(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.ctx["to"] = toVal
|
||||||
|
inputVal, err := t.toBuf(t.vm, input)
|
||||||
|
if err != nil {
|
||||||
|
cancel(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.ctx["input"] = inputVal
|
||||||
t.ctx["gas"] = t.vm.ToValue(t.gasLimit)
|
t.ctx["gas"] = t.vm.ToValue(t.gasLimit)
|
||||||
gasPriceBig, err := t.toBig(t.vm, env.TxContext.GasPrice.String())
|
gasPriceBig, err := t.toBig(t.vm, env.TxContext.GasPrice.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.err = err
|
cancel(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.ctx["gasPrice"] = gasPriceBig
|
t.ctx["gasPrice"] = gasPriceBig
|
||||||
valueBig, err := t.toBig(t.vm, value.String())
|
valueBig, err := t.toBig(t.vm, value.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.err = err
|
cancel(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.ctx["value"] = valueBig
|
t.ctx["value"] = valueBig
|
||||||
|
|
@ -293,10 +322,15 @@ func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope
|
||||||
|
|
||||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||||
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||||
t.ctx["output"] = t.vm.ToValue(output)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.ctx["error"] = t.vm.ToValue(err.Error())
|
t.ctx["error"] = t.vm.ToValue(err.Error())
|
||||||
}
|
}
|
||||||
|
outputVal, err := t.toBuf(t.vm, output)
|
||||||
|
if err != nil {
|
||||||
|
t.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.ctx["output"] = outputVal
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||||
|
|
@ -465,13 +499,13 @@ func (t *jsTracer) setBuiltinFunctions() {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
vm.Set("slice", func(slice goja.Value, start, end int) goja.Value {
|
vm.Set("slice", func(slice goja.Value, start, end int64) goja.Value {
|
||||||
b, err := t.fromBuf(vm, slice, false)
|
b, err := t.fromBuf(vm, slice, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vm.Interrupt(err)
|
vm.Interrupt(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if start < 0 || start > end || end > len(b) {
|
if start < 0 || start > end || end > int64(len(b)) {
|
||||||
vm.Interrupt(fmt.Sprintf("Tracer accessed out of bound memory: available %d, offset %d, size %d", len(b), start, end-start))
|
vm.Interrupt(fmt.Sprintf("Tracer accessed out of bound memory: available %d, offset %d, size %d", len(b), start, end-start))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
go.mod
17
go.mod
|
|
@ -29,7 +29,6 @@ require (
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
|
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
|
||||||
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46
|
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46
|
||||||
github.com/go-stack/stack v1.8.1
|
|
||||||
github.com/gofrs/flock v0.8.1
|
github.com/gofrs/flock v0.8.1
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/golang/protobuf v1.5.3
|
github.com/golang/protobuf v1.5.3
|
||||||
|
|
@ -64,17 +63,18 @@ require (
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/supranational/blst v0.3.11
|
github.com/supranational/blst v0.3.11
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||||
|
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834
|
||||||
github.com/tyler-smith/go-bip39 v1.1.0
|
github.com/tyler-smith/go-bip39 v1.1.0
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
github.com/urfave/cli/v2 v2.25.7
|
||||||
go.uber.org/automaxprocs v1.5.2
|
go.uber.org/automaxprocs v1.5.2
|
||||||
go.uber.org/zap v1.26.0
|
go.uber.org/zap v1.26.0
|
||||||
golang.org/x/crypto v0.14.0
|
golang.org/x/crypto v0.15.0
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
|
||||||
golang.org/x/sync v0.4.0
|
golang.org/x/sync v0.5.0
|
||||||
golang.org/x/sys v0.14.0
|
golang.org/x/sys v0.14.0
|
||||||
golang.org/x/text v0.13.0
|
golang.org/x/text v0.14.0
|
||||||
golang.org/x/time v0.3.0
|
golang.org/x/time v0.3.0
|
||||||
golang.org/x/tools v0.14.0
|
golang.org/x/tools v0.15.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
@ -139,13 +139,12 @@ require (
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/mod v0.13.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/net v0.17.0 // indirect
|
golang.org/x/net v0.18.0 // indirect
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
rsc.io/tmplfunc v0.0.3 // indirect
|
rsc.io/tmplfunc v0.0.3 // indirect
|
||||||
|
|
|
||||||
35
go.sum
35
go.sum
|
|
@ -93,7 +93,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.23.2/go.mod h1:Eows6e1uQEsc4ZaHANmsP
|
||||||
github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8=
|
github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8=
|
||||||
github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
|
@ -231,8 +230,6 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
|
||||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
|
||||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||||
|
|
@ -618,8 +615,6 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|
||||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||||
|
|
@ -638,8 +633,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
|
@ -650,8 +645,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
|
@ -673,8 +668,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|
@ -714,8 +709,8 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
|
@ -734,8 +729,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
@ -812,8 +807,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
|
@ -840,7 +835,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|
@ -869,8 +863,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
||||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
@ -979,7 +973,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ func TestGraphQLBlockSerialization(t *testing.T) {
|
||||||
// should return `estimateGas` as decimal
|
// should return `estimateGas` as decimal
|
||||||
{
|
{
|
||||||
body: `{"query": "{block{ estimateGas(data:{}) }}"}`,
|
body: `{"query": "{block{ estimateGas(data:{}) }}"}`,
|
||||||
want: `{"data":{"block":{"estimateGas":"0xcf08"}}}`,
|
want: `{"data":{"block":{"estimateGas":"0xd221"}}}`,
|
||||||
code: 200,
|
code: 200,
|
||||||
},
|
},
|
||||||
// should return `status` as decimal
|
// should return `status` as decimal
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,6 @@ import (
|
||||||
// NotFound is returned by API methods if the requested item does not exist.
|
// NotFound is returned by API methods if the requested item does not exist.
|
||||||
var NotFound = errors.New("not found")
|
var NotFound = errors.New("not found")
|
||||||
|
|
||||||
// TODO: move subscription to package event
|
|
||||||
|
|
||||||
// Subscription represents an event subscription where events are
|
// Subscription represents an event subscription where events are
|
||||||
// delivered on a data channel.
|
// delivered on a data channel.
|
||||||
type Subscription interface {
|
type Subscription interface {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/hashicorp/go-bexpr"
|
"github.com/hashicorp/go-bexpr"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler is the global debugging handler.
|
// Handler is the global debugging handler.
|
||||||
|
|
@ -56,7 +57,7 @@ type HandlerT struct {
|
||||||
// Verbosity sets the log verbosity ceiling. The verbosity of individual packages
|
// Verbosity sets the log verbosity ceiling. The verbosity of individual packages
|
||||||
// and source files can be raised using Vmodule.
|
// and source files can be raised using Vmodule.
|
||||||
func (*HandlerT) Verbosity(level int) {
|
func (*HandlerT) Verbosity(level int) {
|
||||||
glogger.Verbosity(log.Lvl(level))
|
glogger.Verbosity(slog.Level(level))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vmodule sets the log verbosity pattern. See package log for details on the
|
// Vmodule sets the log verbosity pattern. See package log for details on the
|
||||||
|
|
@ -65,12 +66,6 @@ func (*HandlerT) Vmodule(pattern string) error {
|
||||||
return glogger.Vmodule(pattern)
|
return glogger.Vmodule(pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BacktraceAt sets the log backtrace location. See package log for details on
|
|
||||||
// the pattern syntax.
|
|
||||||
func (*HandlerT) BacktraceAt(location string) error {
|
|
||||||
return glogger.BacktraceAt(location)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MemStats returns detailed runtime memory statistics.
|
// MemStats returns detailed runtime memory statistics.
|
||||||
func (*HandlerT) MemStats() *runtime.MemStats {
|
func (*HandlerT) MemStats() *runtime.MemStats {
|
||||||
s := new(runtime.MemStats)
|
s := new(runtime.MemStats)
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
"gopkg.in/natefinch/lumberjack.v2"
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -75,17 +76,6 @@ var (
|
||||||
Usage: "Write logs to a file",
|
Usage: "Write logs to a file",
|
||||||
Category: flags.LoggingCategory,
|
Category: flags.LoggingCategory,
|
||||||
}
|
}
|
||||||
backtraceAtFlag = &cli.StringFlag{
|
|
||||||
Name: "log.backtrace",
|
|
||||||
Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")",
|
|
||||||
Value: "",
|
|
||||||
Category: flags.LoggingCategory,
|
|
||||||
}
|
|
||||||
debugFlag = &cli.BoolFlag{
|
|
||||||
Name: "log.debug",
|
|
||||||
Usage: "Prepends log messages with call-site location (file and line number)",
|
|
||||||
Category: flags.LoggingCategory,
|
|
||||||
}
|
|
||||||
logRotateFlag = &cli.BoolFlag{
|
logRotateFlag = &cli.BoolFlag{
|
||||||
Name: "log.rotate",
|
Name: "log.rotate",
|
||||||
Usage: "Enables log file rotation",
|
Usage: "Enables log file rotation",
|
||||||
|
|
@ -160,8 +150,6 @@ var Flags = []cli.Flag{
|
||||||
verbosityFlag,
|
verbosityFlag,
|
||||||
logVmoduleFlag,
|
logVmoduleFlag,
|
||||||
vmoduleFlag,
|
vmoduleFlag,
|
||||||
backtraceAtFlag,
|
|
||||||
debugFlag,
|
|
||||||
logjsonFlag,
|
logjsonFlag,
|
||||||
logFormatFlag,
|
logFormatFlag,
|
||||||
logFileFlag,
|
logFileFlag,
|
||||||
|
|
@ -180,45 +168,34 @@ var Flags = []cli.Flag{
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
glogger *log.GlogHandler
|
glogger *log.GlogHandler
|
||||||
logOutputStream log.Handler
|
logOutputFile io.WriteCloser
|
||||||
|
defaultTerminalHandler *log.TerminalHandler
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
defaultTerminalHandler = log.NewTerminalHandler(os.Stderr, false)
|
||||||
|
glogger = log.NewGlogHandler(defaultTerminalHandler)
|
||||||
glogger.Verbosity(log.LvlInfo)
|
glogger.Verbosity(log.LvlInfo)
|
||||||
log.Root().SetHandler(glogger)
|
log.SetDefault(log.NewLogger(glogger))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResetLogging() {
|
||||||
|
if defaultTerminalHandler != nil {
|
||||||
|
defaultTerminalHandler.ResetFieldPadding()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup initializes profiling and logging based on the CLI flags.
|
// Setup initializes profiling and logging based on the CLI flags.
|
||||||
// It should be called as early as possible in the program.
|
// It should be called as early as possible in the program.
|
||||||
func Setup(ctx *cli.Context) error {
|
func Setup(ctx *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
logfmt log.Format
|
handler slog.Handler
|
||||||
output = io.Writer(os.Stderr)
|
terminalOutput = io.Writer(os.Stderr)
|
||||||
logFmtFlag = ctx.String(logFormatFlag.Name)
|
output io.Writer
|
||||||
|
logFmtFlag = ctx.String(logFormatFlag.Name)
|
||||||
)
|
)
|
||||||
switch {
|
|
||||||
case ctx.Bool(logjsonFlag.Name):
|
|
||||||
// Retain backwards compatibility with `--log.json` flag if `--log.format` not set
|
|
||||||
defer log.Warn("The flag '--log.json' is deprecated, please use '--log.format=json' instead")
|
|
||||||
logfmt = log.JSONFormat()
|
|
||||||
case logFmtFlag == "json":
|
|
||||||
logfmt = log.JSONFormat()
|
|
||||||
case logFmtFlag == "logfmt":
|
|
||||||
logfmt = log.LogfmtFormat()
|
|
||||||
case logFmtFlag == "", logFmtFlag == "terminal":
|
|
||||||
useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
|
|
||||||
if useColor {
|
|
||||||
output = colorable.NewColorableStderr()
|
|
||||||
}
|
|
||||||
logfmt = log.TerminalFormat(useColor)
|
|
||||||
default:
|
|
||||||
// Unknown log format specified
|
|
||||||
return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name))
|
|
||||||
}
|
|
||||||
var (
|
var (
|
||||||
ostream = log.StreamHandler(output, logfmt)
|
|
||||||
logFile = ctx.String(logFileFlag.Name)
|
logFile = ctx.String(logFileFlag.Name)
|
||||||
rotation = ctx.Bool(logRotateFlag.Name)
|
rotation = ctx.Bool(logRotateFlag.Name)
|
||||||
)
|
)
|
||||||
|
|
@ -241,27 +218,55 @@ func Setup(ctx *cli.Context) error {
|
||||||
} else {
|
} else {
|
||||||
context = append(context, "location", filepath.Join(os.TempDir(), "geth-lumberjack.log"))
|
context = append(context, "location", filepath.Join(os.TempDir(), "geth-lumberjack.log"))
|
||||||
}
|
}
|
||||||
lumberWriter := &lumberjack.Logger{
|
logOutputFile = &lumberjack.Logger{
|
||||||
Filename: logFile,
|
Filename: logFile,
|
||||||
MaxSize: ctx.Int(logMaxSizeMBsFlag.Name),
|
MaxSize: ctx.Int(logMaxSizeMBsFlag.Name),
|
||||||
MaxBackups: ctx.Int(logMaxBackupsFlag.Name),
|
MaxBackups: ctx.Int(logMaxBackupsFlag.Name),
|
||||||
MaxAge: ctx.Int(logMaxAgeFlag.Name),
|
MaxAge: ctx.Int(logMaxAgeFlag.Name),
|
||||||
Compress: ctx.Bool(logCompressFlag.Name),
|
Compress: ctx.Bool(logCompressFlag.Name),
|
||||||
}
|
}
|
||||||
ostream = log.StreamHandler(io.MultiWriter(output, lumberWriter), logfmt)
|
output = io.MultiWriter(terminalOutput, logOutputFile)
|
||||||
} else if logFile != "" {
|
} else if logFile != "" {
|
||||||
f, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
var err error
|
||||||
if err != nil {
|
if logOutputFile, err = os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ostream = log.StreamHandler(io.MultiWriter(output, f), logfmt)
|
output = io.MultiWriter(logOutputFile, terminalOutput)
|
||||||
context = append(context, "location", logFile)
|
context = append(context, "location", logFile)
|
||||||
|
} else {
|
||||||
|
output = terminalOutput
|
||||||
}
|
}
|
||||||
glogger.SetHandler(ostream)
|
|
||||||
|
switch {
|
||||||
|
case ctx.Bool(logjsonFlag.Name):
|
||||||
|
// Retain backwards compatibility with `--log.json` flag if `--log.format` not set
|
||||||
|
defer log.Warn("The flag '--log.json' is deprecated, please use '--log.format=json' instead")
|
||||||
|
handler = log.JSONHandler(output)
|
||||||
|
case logFmtFlag == "json":
|
||||||
|
handler = log.JSONHandler(output)
|
||||||
|
case logFmtFlag == "logfmt":
|
||||||
|
handler = log.LogfmtHandler(output)
|
||||||
|
case logFmtFlag == "", logFmtFlag == "terminal":
|
||||||
|
useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
|
||||||
|
if useColor {
|
||||||
|
terminalOutput = colorable.NewColorableStderr()
|
||||||
|
if logOutputFile != nil {
|
||||||
|
output = io.MultiWriter(logOutputFile, terminalOutput)
|
||||||
|
} else {
|
||||||
|
output = terminalOutput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handler = log.NewTerminalHandler(output, useColor)
|
||||||
|
default:
|
||||||
|
// Unknown log format specified
|
||||||
|
return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
glogger = log.NewGlogHandler(handler)
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
verbosity := ctx.Int(verbosityFlag.Name)
|
verbosity := log.FromLegacyLevel(ctx.Int(verbosityFlag.Name))
|
||||||
glogger.Verbosity(log.Lvl(verbosity))
|
glogger.Verbosity(verbosity)
|
||||||
vmodule := ctx.String(logVmoduleFlag.Name)
|
vmodule := ctx.String(logVmoduleFlag.Name)
|
||||||
if vmodule == "" {
|
if vmodule == "" {
|
||||||
// Retain backwards compatibility with `--vmodule` flag if `--log.vmodule` not set
|
// Retain backwards compatibility with `--vmodule` flag if `--log.vmodule` not set
|
||||||
|
|
@ -272,16 +277,7 @@ func Setup(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
glogger.Vmodule(vmodule)
|
glogger.Vmodule(vmodule)
|
||||||
|
|
||||||
debug := ctx.Bool(debugFlag.Name)
|
log.SetDefault(log.NewLogger(glogger))
|
||||||
if ctx.IsSet(debugFlag.Name) {
|
|
||||||
debug = ctx.Bool(debugFlag.Name)
|
|
||||||
}
|
|
||||||
log.PrintOrigins(debug)
|
|
||||||
|
|
||||||
backtrace := ctx.String(backtraceAtFlag.Name)
|
|
||||||
glogger.BacktraceAt(backtrace)
|
|
||||||
|
|
||||||
log.Root().SetHandler(glogger)
|
|
||||||
|
|
||||||
// profiling, tracing
|
// profiling, tracing
|
||||||
runtime.MemProfileRate = memprofilerateFlag.Value
|
runtime.MemProfileRate = memprofilerateFlag.Value
|
||||||
|
|
@ -341,8 +337,8 @@ func StartPProf(address string, withMetrics bool) {
|
||||||
func Exit() {
|
func Exit() {
|
||||||
Handler.StopCPUProfile()
|
Handler.StopCPUProfile()
|
||||||
Handler.StopGoTrace()
|
Handler.StopGoTrace()
|
||||||
if closer, ok := logOutputStream.(io.Closer); ok {
|
if logOutputFile != nil {
|
||||||
closer.Close()
|
logOutputFile.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,10 @@ import (
|
||||||
"github.com/tyler-smith/go-bip39"
|
"github.com/tyler-smith/go-bip39"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// estimateGasErrorRatio is the amount of overestimation eth_estimateGas is
|
||||||
|
// allowed to produce in order to speed up calculations.
|
||||||
|
const estimateGasErrorRatio = 0.015
|
||||||
|
|
||||||
// EthereumAPI provides an API to access Ethereum related information.
|
// EthereumAPI provides an API to access Ethereum related information.
|
||||||
type EthereumAPI struct {
|
type EthereumAPI struct {
|
||||||
b Backend
|
b Backend
|
||||||
|
|
@ -1189,10 +1193,11 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
|
||||||
}
|
}
|
||||||
// Construct the gas estimator option from the user input
|
// Construct the gas estimator option from the user input
|
||||||
opts := &gasestimator.Options{
|
opts := &gasestimator.Options{
|
||||||
Config: b.ChainConfig(),
|
Config: b.ChainConfig(),
|
||||||
Chain: NewChainContext(ctx, b),
|
Chain: NewChainContext(ctx, b),
|
||||||
Header: header,
|
Header: header,
|
||||||
State: state,
|
State: state,
|
||||||
|
ErrorRatio: estimateGasErrorRatio,
|
||||||
}
|
}
|
||||||
// Run the gas estimation andwrap any revertals into a custom return
|
// Run the gas estimation andwrap any revertals into a custom return
|
||||||
call, err := args.ToMessage(gasCap, header.BaseFee)
|
call, err := args.ToMessage(gasCap, header.BaseFee)
|
||||||
|
|
|
||||||
|
|
@ -735,7 +735,7 @@ func TestEstimateGas(t *testing.T) {
|
||||||
t.Errorf("test %d: want no error, have %v", i, err)
|
t.Errorf("test %d: want no error, have %v", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if uint64(result) != tc.want {
|
if float64(result) > float64(tc.want)*(1+estimateGasErrorRatio) {
|
||||||
t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want)
|
t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,26 +18,19 @@
|
||||||
package testlog
|
package testlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler returns a log handler which logs to the unit test log of t.
|
const (
|
||||||
func Handler(t *testing.T, level log.Lvl) log.Handler {
|
termTimeFormat = "01-02|15:04:05.000"
|
||||||
return log.LvlFilterHandler(level, &handler{t, log.TerminalFormat(false)})
|
)
|
||||||
}
|
|
||||||
|
|
||||||
type handler struct {
|
|
||||||
t *testing.T
|
|
||||||
fmt log.Format
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) Log(r *log.Record) error {
|
|
||||||
h.t.Logf("%s", h.fmt.Format(r))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// logger implements log.Logger such that all output goes to the unit test log via
|
// logger implements log.Logger such that all output goes to the unit test log via
|
||||||
// t.Logf(). All methods in between logger.Trace, logger.Debug, etc. are marked as test
|
// t.Logf(). All methods in between logger.Trace, logger.Debug, etc. are marked as test
|
||||||
|
|
@ -51,27 +44,62 @@ type logger struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type bufHandler struct {
|
type bufHandler struct {
|
||||||
buf []*log.Record
|
buf []slog.Record
|
||||||
fmt log.Format
|
attrs []slog.Attr
|
||||||
|
level slog.Level
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *bufHandler) Log(r *log.Record) error {
|
func (h *bufHandler) Handle(_ context.Context, r slog.Record) error {
|
||||||
h.buf = append(h.buf, r)
|
h.buf = append(h.buf, r)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger returns a logger which logs to the unit test log of t.
|
func (h *bufHandler) Enabled(_ context.Context, lvl slog.Level) bool {
|
||||||
func Logger(t *testing.T, level log.Lvl) log.Logger {
|
return lvl <= h.level
|
||||||
l := &logger{
|
|
||||||
t: t,
|
|
||||||
l: log.New(),
|
|
||||||
mu: new(sync.Mutex),
|
|
||||||
h: &bufHandler{fmt: log.TerminalFormat(false)},
|
|
||||||
}
|
|
||||||
l.l.SetHandler(log.LvlFilterHandler(level, l.h))
|
|
||||||
return l
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *bufHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
records := make([]slog.Record, len(h.buf))
|
||||||
|
copy(records[:], h.buf[:])
|
||||||
|
return &bufHandler{
|
||||||
|
records,
|
||||||
|
append(h.attrs, attrs...),
|
||||||
|
h.level,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *bufHandler) WithGroup(_ string) slog.Handler {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger returns a logger which logs to the unit test log of t.
|
||||||
|
func Logger(t *testing.T, level slog.Level) log.Logger {
|
||||||
|
handler := bufHandler{
|
||||||
|
[]slog.Record{},
|
||||||
|
[]slog.Attr{},
|
||||||
|
level,
|
||||||
|
}
|
||||||
|
return &logger{
|
||||||
|
t: t,
|
||||||
|
l: log.NewLogger(&handler),
|
||||||
|
mu: new(sync.Mutex),
|
||||||
|
h: &handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerWithHandler returns
|
||||||
|
func LoggerWithHandler(t *testing.T, handler slog.Handler) log.Logger {
|
||||||
|
var bh bufHandler
|
||||||
|
return &logger{
|
||||||
|
t: t,
|
||||||
|
l: log.NewLogger(handler),
|
||||||
|
mu: new(sync.Mutex),
|
||||||
|
h: &bh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Write(level slog.Level, msg string, ctx ...interface{}) {}
|
||||||
|
|
||||||
func (l *logger) Trace(msg string, ctx ...interface{}) {
|
func (l *logger) Trace(msg string, ctx ...interface{}) {
|
||||||
l.t.Helper()
|
l.t.Helper()
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
|
|
@ -80,6 +108,14 @@ func (l *logger) Trace(msg string, ctx ...interface{}) {
|
||||||
l.flush()
|
l.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *logger) Log(level slog.Level, msg string, ctx ...interface{}) {
|
||||||
|
l.t.Helper()
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.l.Log(level, msg, ctx...)
|
||||||
|
l.flush()
|
||||||
|
}
|
||||||
|
|
||||||
func (l *logger) Debug(msg string, ctx ...interface{}) {
|
func (l *logger) Debug(msg string, ctx ...interface{}) {
|
||||||
l.t.Helper()
|
l.t.Helper()
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
|
|
@ -120,23 +156,45 @@ func (l *logger) Crit(msg string, ctx ...interface{}) {
|
||||||
l.flush()
|
l.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *logger) With(ctx ...interface{}) log.Logger {
|
||||||
|
return &logger{l.t, l.l.With(ctx...), l.mu, l.h}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *logger) New(ctx ...interface{}) log.Logger {
|
func (l *logger) New(ctx ...interface{}) log.Logger {
|
||||||
return &logger{l.t, l.l.New(ctx...), l.mu, l.h}
|
return l.With(ctx...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logger) GetHandler() log.Handler {
|
// terminalFormat formats a message similarly to the NewTerminalHandler in the log package.
|
||||||
return l.l.GetHandler()
|
// The difference is that terminalFormat does not escape messages/attributes and does not pad attributes.
|
||||||
}
|
func (h *bufHandler) terminalFormat(r slog.Record) string {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
lvl := log.LevelAlignedString(r.Level)
|
||||||
|
attrs := []slog.Attr{}
|
||||||
|
r.Attrs(func(attr slog.Attr) bool {
|
||||||
|
attrs = append(attrs, attr)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
func (l *logger) SetHandler(h log.Handler) {
|
attrs = append(h.attrs, attrs...)
|
||||||
l.l.SetHandler(h)
|
|
||||||
|
fmt.Fprintf(buf, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Message)
|
||||||
|
if length := len(r.Message); length < 40 {
|
||||||
|
buf.Write(bytes.Repeat([]byte{' '}, 40-length))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, attr := range attrs {
|
||||||
|
rawVal := attr.Value.Any()
|
||||||
|
fmt.Fprintf(buf, " %s=%s", attr.Key, log.FormatLogfmtValue(rawVal, true))
|
||||||
|
}
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush writes all buffered messages and clears the buffer.
|
// flush writes all buffered messages and clears the buffer.
|
||||||
func (l *logger) flush() {
|
func (l *logger) flush() {
|
||||||
l.t.Helper()
|
l.t.Helper()
|
||||||
for _, r := range l.h.buf {
|
for _, r := range l.h.buf {
|
||||||
l.t.Logf("%s", l.h.fmt.Format(r))
|
l.t.Logf("%s", l.h.terminalFormat(r))
|
||||||
}
|
}
|
||||||
l.h.buf = nil
|
l.h.buf = nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,531 +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 light implements on-demand retrieval capable state and chain objects
|
|
||||||
// for the Ethereum Light Client.
|
|
||||||
package light
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/lru"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"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/event"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
bodyCacheLimit = 256
|
|
||||||
blockCacheLimit = 256
|
|
||||||
)
|
|
||||||
|
|
||||||
// LightChain represents a canonical chain that by default only handles block
|
|
||||||
// headers, downloading block bodies and receipts on demand through an ODR
|
|
||||||
// interface. It only does header validation during chain insertion.
|
|
||||||
type LightChain struct {
|
|
||||||
hc *core.HeaderChain
|
|
||||||
indexerConfig *IndexerConfig
|
|
||||||
chainDb ethdb.Database
|
|
||||||
engine consensus.Engine
|
|
||||||
odr OdrBackend
|
|
||||||
chainFeed event.Feed
|
|
||||||
chainSideFeed event.Feed
|
|
||||||
chainHeadFeed event.Feed
|
|
||||||
scope event.SubscriptionScope
|
|
||||||
genesisBlock *types.Block
|
|
||||||
forker *core.ForkChoice
|
|
||||||
|
|
||||||
bodyCache *lru.Cache[common.Hash, *types.Body]
|
|
||||||
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
|
|
||||||
blockCache *lru.Cache[common.Hash, *types.Block]
|
|
||||||
|
|
||||||
chainmu sync.RWMutex // protects header inserts
|
|
||||||
quit chan struct{}
|
|
||||||
wg sync.WaitGroup
|
|
||||||
|
|
||||||
// Atomic boolean switches:
|
|
||||||
stopped atomic.Bool // whether LightChain is stopped or running
|
|
||||||
procInterrupt atomic.Bool // interrupts chain insert
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLightChain returns a fully initialised light chain using information
|
|
||||||
// available in the database. It initialises the default Ethereum header
|
|
||||||
// validator.
|
|
||||||
func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine) (*LightChain, error) {
|
|
||||||
bc := &LightChain{
|
|
||||||
chainDb: odr.Database(),
|
|
||||||
indexerConfig: odr.IndexerConfig(),
|
|
||||||
odr: odr,
|
|
||||||
quit: make(chan struct{}),
|
|
||||||
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
|
|
||||||
bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
|
|
||||||
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
|
|
||||||
engine: engine,
|
|
||||||
}
|
|
||||||
bc.forker = core.NewForkChoice(bc, nil)
|
|
||||||
var err error
|
|
||||||
bc.hc, err = core.NewHeaderChain(odr.Database(), config, bc.engine, bc.getProcInterrupt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
bc.genesisBlock, _ = bc.GetBlockByNumber(NoOdr, 0)
|
|
||||||
if bc.genesisBlock == nil {
|
|
||||||
return nil, core.ErrNoGenesis
|
|
||||||
}
|
|
||||||
if err := bc.loadLastState(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
|
|
||||||
for hash := range core.BadHashes {
|
|
||||||
if header := bc.GetHeaderByHash(hash); header != nil {
|
|
||||||
log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash)
|
|
||||||
bc.SetHead(header.Number.Uint64() - 1)
|
|
||||||
log.Info("Chain rewind was successful, resuming normal operation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *LightChain) getProcInterrupt() bool {
|
|
||||||
return lc.procInterrupt.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Odr returns the ODR backend of the chain
|
|
||||||
func (lc *LightChain) Odr() OdrBackend {
|
|
||||||
return lc.odr
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderChain returns the underlying header chain.
|
|
||||||
func (lc *LightChain) HeaderChain() *core.HeaderChain {
|
|
||||||
return lc.hc
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadLastState loads the last known chain state from the database. This method
|
|
||||||
// assumes that the chain manager mutex is held.
|
|
||||||
func (lc *LightChain) loadLastState() error {
|
|
||||||
if head := rawdb.ReadHeadHeaderHash(lc.chainDb); head == (common.Hash{}) {
|
|
||||||
// Corrupt or empty database, init from scratch
|
|
||||||
lc.Reset()
|
|
||||||
} else {
|
|
||||||
header := lc.GetHeaderByHash(head)
|
|
||||||
if header == nil {
|
|
||||||
// Corrupt or empty database, init from scratch
|
|
||||||
lc.Reset()
|
|
||||||
} else {
|
|
||||||
lc.hc.SetCurrentHeader(header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Issue a status log and return
|
|
||||||
header := lc.hc.CurrentHeader()
|
|
||||||
headerTd := lc.GetTd(header.Hash(), header.Number.Uint64())
|
|
||||||
log.Info("Loaded most recent local header", "number", header.Number, "hash", header.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(header.Time), 0)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHead rewinds the local chain to a new head. Everything above the new
|
|
||||||
// head will be deleted and the new one set.
|
|
||||||
func (lc *LightChain) SetHead(head uint64) error {
|
|
||||||
lc.chainmu.Lock()
|
|
||||||
defer lc.chainmu.Unlock()
|
|
||||||
|
|
||||||
lc.hc.SetHead(head, nil, nil)
|
|
||||||
return lc.loadLastState()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeadWithTimestamp rewinds the local chain to a new head that has at max
|
|
||||||
// the given timestamp. Everything above the new head will be deleted and the
|
|
||||||
// new one set.
|
|
||||||
func (lc *LightChain) SetHeadWithTimestamp(timestamp uint64) error {
|
|
||||||
lc.chainmu.Lock()
|
|
||||||
defer lc.chainmu.Unlock()
|
|
||||||
|
|
||||||
lc.hc.SetHeadWithTimestamp(timestamp, nil, nil)
|
|
||||||
return lc.loadLastState()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GasLimit returns the gas limit of the current HEAD block.
|
|
||||||
func (lc *LightChain) GasLimit() uint64 {
|
|
||||||
return lc.hc.CurrentHeader().GasLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset purges the entire blockchain, restoring it to its genesis state.
|
|
||||||
func (lc *LightChain) Reset() {
|
|
||||||
lc.ResetWithGenesisBlock(lc.genesisBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetWithGenesisBlock purges the entire blockchain, restoring it to the
|
|
||||||
// specified genesis state.
|
|
||||||
func (lc *LightChain) ResetWithGenesisBlock(genesis *types.Block) {
|
|
||||||
// Dump the entire block chain and purge the caches
|
|
||||||
lc.SetHead(0)
|
|
||||||
|
|
||||||
lc.chainmu.Lock()
|
|
||||||
defer lc.chainmu.Unlock()
|
|
||||||
|
|
||||||
// Prepare the genesis block and reinitialise the chain
|
|
||||||
batch := lc.chainDb.NewBatch()
|
|
||||||
rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty())
|
|
||||||
rawdb.WriteBlock(batch, genesis)
|
|
||||||
rawdb.WriteHeadHeaderHash(batch, genesis.Hash())
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
log.Crit("Failed to reset genesis block", "err", err)
|
|
||||||
}
|
|
||||||
lc.genesisBlock = genesis
|
|
||||||
lc.hc.SetGenesis(lc.genesisBlock.Header())
|
|
||||||
lc.hc.SetCurrentHeader(lc.genesisBlock.Header())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accessors
|
|
||||||
|
|
||||||
// Engine retrieves the light chain's consensus engine.
|
|
||||||
func (lc *LightChain) Engine() consensus.Engine { return lc.engine }
|
|
||||||
|
|
||||||
// Genesis returns the genesis block
|
|
||||||
func (lc *LightChain) Genesis() *types.Block {
|
|
||||||
return lc.genesisBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *LightChain) StateCache() state.Database {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBody retrieves a block body (transactions and uncles) from the database
|
|
||||||
// or ODR service by hash, caching it if found.
|
|
||||||
func (lc *LightChain) GetBody(ctx context.Context, hash common.Hash) (*types.Body, error) {
|
|
||||||
// Short circuit if the body's already in the cache, retrieve otherwise
|
|
||||||
if cached, ok := lc.bodyCache.Get(hash); ok {
|
|
||||||
return cached, nil
|
|
||||||
}
|
|
||||||
number := lc.hc.GetBlockNumber(hash)
|
|
||||||
if number == nil {
|
|
||||||
return nil, errors.New("unknown block")
|
|
||||||
}
|
|
||||||
body, err := GetBody(ctx, lc.odr, hash, *number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Cache the found body for next time and return
|
|
||||||
lc.bodyCache.Add(hash, body)
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBodyRLP retrieves a block body in RLP encoding from the database or
|
|
||||||
// ODR service by hash, caching it if found.
|
|
||||||
func (lc *LightChain) GetBodyRLP(ctx context.Context, hash common.Hash) (rlp.RawValue, error) {
|
|
||||||
// Short circuit if the body's already in the cache, retrieve otherwise
|
|
||||||
if cached, ok := lc.bodyRLPCache.Get(hash); ok {
|
|
||||||
return cached, nil
|
|
||||||
}
|
|
||||||
number := lc.hc.GetBlockNumber(hash)
|
|
||||||
if number == nil {
|
|
||||||
return nil, errors.New("unknown block")
|
|
||||||
}
|
|
||||||
body, err := GetBodyRLP(ctx, lc.odr, hash, *number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Cache the found body for next time and return
|
|
||||||
lc.bodyRLPCache.Add(hash, body)
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasBlock checks if a block is fully present in the database or not, caching
|
|
||||||
// it if present.
|
|
||||||
func (lc *LightChain) HasBlock(hash common.Hash, number uint64) bool {
|
|
||||||
blk, _ := lc.GetBlock(NoOdr, hash, number)
|
|
||||||
return blk != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBlock retrieves a block from the database or ODR service by hash and number,
|
|
||||||
// caching it if found.
|
|
||||||
func (lc *LightChain) GetBlock(ctx context.Context, hash common.Hash, number uint64) (*types.Block, error) {
|
|
||||||
// Short circuit if the block's already in the cache, retrieve otherwise
|
|
||||||
if block, ok := lc.blockCache.Get(hash); ok {
|
|
||||||
return block, nil
|
|
||||||
}
|
|
||||||
block, err := GetBlock(ctx, lc.odr, hash, number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Cache the found block for next time and return
|
|
||||||
lc.blockCache.Add(block.Hash(), block)
|
|
||||||
return block, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBlockByHash retrieves a block from the database or ODR service by hash,
|
|
||||||
// caching it if found.
|
|
||||||
func (lc *LightChain) GetBlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
|
||||||
number := lc.hc.GetBlockNumber(hash)
|
|
||||||
if number == nil {
|
|
||||||
return nil, errors.New("unknown block")
|
|
||||||
}
|
|
||||||
return lc.GetBlock(ctx, hash, *number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBlockByNumber retrieves a block from the database or ODR service by
|
|
||||||
// number, caching it (associated with its hash) if found.
|
|
||||||
func (lc *LightChain) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) {
|
|
||||||
hash, err := GetCanonicalHash(ctx, lc.odr, number)
|
|
||||||
if hash == (common.Hash{}) || err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return lc.GetBlock(ctx, hash, number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the blockchain service. If any imports are currently in progress
|
|
||||||
// it will abort them using the procInterrupt.
|
|
||||||
func (lc *LightChain) Stop() {
|
|
||||||
if !lc.stopped.CompareAndSwap(false, true) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(lc.quit)
|
|
||||||
lc.StopInsert()
|
|
||||||
lc.wg.Wait()
|
|
||||||
log.Info("Blockchain stopped")
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopInsert interrupts all insertion methods, causing them to return
|
|
||||||
// errInsertionInterrupted as soon as possible. Insertion is permanently disabled after
|
|
||||||
// calling this method.
|
|
||||||
func (lc *LightChain) StopInsert() {
|
|
||||||
lc.procInterrupt.Store(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rollback is designed to remove a chain of links from the database that aren't
|
|
||||||
// certain enough to be valid.
|
|
||||||
func (lc *LightChain) Rollback(chain []common.Hash) {
|
|
||||||
lc.chainmu.Lock()
|
|
||||||
defer lc.chainmu.Unlock()
|
|
||||||
|
|
||||||
batch := lc.chainDb.NewBatch()
|
|
||||||
for i := len(chain) - 1; i >= 0; i-- {
|
|
||||||
hash := chain[i]
|
|
||||||
|
|
||||||
// Degrade the chain markers if they are explicitly reverted.
|
|
||||||
// In theory we should update all in-memory markers in the
|
|
||||||
// last step, however the direction of rollback is from high
|
|
||||||
// to low, so it's safe the update in-memory markers directly.
|
|
||||||
if head := lc.hc.CurrentHeader(); head.Hash() == hash {
|
|
||||||
rawdb.WriteHeadHeaderHash(batch, head.ParentHash)
|
|
||||||
lc.hc.SetCurrentHeader(lc.GetHeader(head.ParentHash, head.Number.Uint64()-1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
log.Crit("Failed to rollback light chain", "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *LightChain) InsertHeader(header *types.Header) error {
|
|
||||||
// Verify the header first before obtaining the lock
|
|
||||||
headers := []*types.Header{header}
|
|
||||||
if _, err := lc.hc.ValidateHeaderChain(headers); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Make sure only one thread manipulates the chain at once
|
|
||||||
lc.chainmu.Lock()
|
|
||||||
defer lc.chainmu.Unlock()
|
|
||||||
|
|
||||||
lc.wg.Add(1)
|
|
||||||
defer lc.wg.Done()
|
|
||||||
|
|
||||||
_, err := lc.hc.WriteHeaders(headers)
|
|
||||||
log.Info("Inserted header", "number", header.Number, "hash", header.Hash())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *LightChain) SetCanonical(header *types.Header) error {
|
|
||||||
lc.chainmu.Lock()
|
|
||||||
defer lc.chainmu.Unlock()
|
|
||||||
|
|
||||||
lc.wg.Add(1)
|
|
||||||
defer lc.wg.Done()
|
|
||||||
|
|
||||||
if err := lc.hc.Reorg([]*types.Header{header}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Emit events
|
|
||||||
block := types.NewBlockWithHeader(header)
|
|
||||||
lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()})
|
|
||||||
lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: block})
|
|
||||||
log.Info("Set the chain head", "number", block.Number(), "hash", block.Hash())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertHeaderChain attempts to insert the given header chain in to the local
|
|
||||||
// chain, possibly creating a reorg. If an error is returned, it will return the
|
|
||||||
// index number of the failing header as well an error describing what went wrong.
|
|
||||||
|
|
||||||
// In the case of a light chain, InsertHeaderChain also creates and posts light
|
|
||||||
// chain events when necessary.
|
|
||||||
func (lc *LightChain) InsertHeaderChain(chain []*types.Header) (int, error) {
|
|
||||||
if len(chain) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
start := time.Now()
|
|
||||||
if i, err := lc.hc.ValidateHeaderChain(chain); err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure only one thread manipulates the chain at once
|
|
||||||
lc.chainmu.Lock()
|
|
||||||
defer lc.chainmu.Unlock()
|
|
||||||
|
|
||||||
lc.wg.Add(1)
|
|
||||||
defer lc.wg.Done()
|
|
||||||
|
|
||||||
status, err := lc.hc.InsertHeaderChain(chain, start, lc.forker)
|
|
||||||
if err != nil || len(chain) == 0 {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create chain event for the new head block of this insertion.
|
|
||||||
var (
|
|
||||||
lastHeader = chain[len(chain)-1]
|
|
||||||
block = types.NewBlockWithHeader(lastHeader)
|
|
||||||
)
|
|
||||||
switch status {
|
|
||||||
case core.CanonStatTy:
|
|
||||||
lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()})
|
|
||||||
lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: block})
|
|
||||||
case core.SideStatTy:
|
|
||||||
lc.chainSideFeed.Send(core.ChainSideEvent{Block: block})
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentHeader retrieves the current head header of the canonical chain. The
|
|
||||||
// header is retrieved from the HeaderChain's internal cache.
|
|
||||||
func (lc *LightChain) CurrentHeader() *types.Header {
|
|
||||||
return lc.hc.CurrentHeader()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTd retrieves a block's total difficulty in the canonical chain from the
|
|
||||||
// database by hash and number, caching it if found.
|
|
||||||
func (lc *LightChain) GetTd(hash common.Hash, number uint64) *big.Int {
|
|
||||||
return lc.hc.GetTd(hash, number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTdOdr retrieves the total difficult from the database or
|
|
||||||
// network by hash and number, caching it (associated with its hash) if found.
|
|
||||||
func (lc *LightChain) GetTdOdr(ctx context.Context, hash common.Hash, number uint64) *big.Int {
|
|
||||||
td := lc.GetTd(hash, number)
|
|
||||||
if td != nil {
|
|
||||||
return td
|
|
||||||
}
|
|
||||||
td, _ = GetTd(ctx, lc.odr, hash, number)
|
|
||||||
return td
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHeader retrieves a block header from the database by hash and number,
|
|
||||||
// caching it if found.
|
|
||||||
func (lc *LightChain) GetHeader(hash common.Hash, number uint64) *types.Header {
|
|
||||||
return lc.hc.GetHeader(hash, number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHeaderByHash retrieves a block header from the database by hash, caching it if
|
|
||||||
// found.
|
|
||||||
func (lc *LightChain) GetHeaderByHash(hash common.Hash) *types.Header {
|
|
||||||
return lc.hc.GetHeaderByHash(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasHeader checks if a block header is present in the database or not, caching
|
|
||||||
// it if present.
|
|
||||||
func (lc *LightChain) HasHeader(hash common.Hash, number uint64) bool {
|
|
||||||
return lc.hc.HasHeader(hash, number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCanonicalHash returns the canonical hash for a given block number
|
|
||||||
func (bc *LightChain) GetCanonicalHash(number uint64) common.Hash {
|
|
||||||
return bc.hc.GetCanonicalHash(number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAncestor retrieves the Nth ancestor of a given block. It assumes that either the given block or
|
|
||||||
// a close ancestor of it is canonical. maxNonCanonical points to a downwards counter limiting the
|
|
||||||
// number of blocks to be individually checked before we reach the canonical chain.
|
|
||||||
//
|
|
||||||
// Note: ancestor == 0 returns the same block, 1 returns its parent and so on.
|
|
||||||
func (lc *LightChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) {
|
|
||||||
return lc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHeaderByNumber retrieves a block header from the database by number,
|
|
||||||
// caching it (associated with its hash) if found.
|
|
||||||
func (lc *LightChain) GetHeaderByNumber(number uint64) *types.Header {
|
|
||||||
return lc.hc.GetHeaderByNumber(number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHeaderByNumberOdr retrieves a block header from the database or network
|
|
||||||
// by number, caching it (associated with its hash) if found.
|
|
||||||
func (lc *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64) (*types.Header, error) {
|
|
||||||
if header := lc.hc.GetHeaderByNumber(number); header != nil {
|
|
||||||
return header, nil
|
|
||||||
}
|
|
||||||
return GetHeaderByNumber(ctx, lc.odr, number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config retrieves the header chain's chain configuration.
|
|
||||||
func (lc *LightChain) Config() *params.ChainConfig { return lc.hc.Config() }
|
|
||||||
|
|
||||||
// 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 (lc *LightChain) LockChain() {
|
|
||||||
lc.chainmu.RLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnlockChain unlocks the chain mutex
|
|
||||||
func (lc *LightChain) UnlockChain() {
|
|
||||||
lc.chainmu.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeChainEvent registers a subscription of ChainEvent.
|
|
||||||
func (lc *LightChain) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
|
||||||
return lc.scope.Track(lc.chainFeed.Subscribe(ch))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeChainHeadEvent registers a subscription of ChainHeadEvent.
|
|
||||||
func (lc *LightChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
|
||||||
return lc.scope.Track(lc.chainHeadFeed.Subscribe(ch))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeChainSideEvent registers a subscription of ChainSideEvent.
|
|
||||||
func (lc *LightChain) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
|
|
||||||
return lc.scope.Track(lc.chainSideFeed.Subscribe(ch))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeLogsEvent implements the interface of filters.Backend
|
|
||||||
// LightChain does not send logs events, so return an empty subscription.
|
|
||||||
func (lc *LightChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
||||||
return lc.scope.Track(new(event.Feed).Subscribe(ch))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeRemovedLogsEvent implements the interface of filters.Backend
|
|
||||||
// LightChain does not send core.RemovedLogsEvent, so return an empty subscription.
|
|
||||||
func (lc *LightChain) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
|
|
||||||
return lc.scope.Track(new(event.Feed).Subscribe(ch))
|
|
||||||
}
|
|
||||||
|
|
@ -1,358 +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 light
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
)
|
|
||||||
|
|
||||||
// So we can deterministically seed different blockchains
|
|
||||||
var (
|
|
||||||
canonicalSeed = 1
|
|
||||||
forkSeed = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// makeHeaderChain creates a deterministic chain of headers rooted at parent.
|
|
||||||
func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) []*types.Header {
|
|
||||||
blocks, _ := core.GenerateChain(params.TestChainConfig, types.NewBlockWithHeader(parent), ethash.NewFaker(), db, n, func(i int, b *core.BlockGen) {
|
|
||||||
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
|
|
||||||
})
|
|
||||||
headers := make([]*types.Header, len(blocks))
|
|
||||||
for i, block := range blocks {
|
|
||||||
headers[i] = block.Header()
|
|
||||||
}
|
|
||||||
return headers
|
|
||||||
}
|
|
||||||
|
|
||||||
// newCanonical creates a chain database, and injects a deterministic canonical
|
|
||||||
// chain. Depending on the full flag, if creates either a full block chain or a
|
|
||||||
// header only chain.
|
|
||||||
func newCanonical(n int) (ethdb.Database, *LightChain, error) {
|
|
||||||
db := rawdb.NewMemoryDatabase()
|
|
||||||
gspec := core.Genesis{Config: params.TestChainConfig}
|
|
||||||
genesis := gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults))
|
|
||||||
blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker())
|
|
||||||
|
|
||||||
// Create and inject the requested chain
|
|
||||||
if n == 0 {
|
|
||||||
return db, blockchain, nil
|
|
||||||
}
|
|
||||||
// Header-only chain requested
|
|
||||||
headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed)
|
|
||||||
_, err := blockchain.InsertHeaderChain(headers)
|
|
||||||
return db, blockchain, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// newTestLightChain creates a LightChain that doesn't validate anything.
|
|
||||||
func newTestLightChain() *LightChain {
|
|
||||||
db := rawdb.NewMemoryDatabase()
|
|
||||||
gspec := &core.Genesis{
|
|
||||||
Difficulty: big.NewInt(1),
|
|
||||||
Config: params.TestChainConfig,
|
|
||||||
}
|
|
||||||
gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults))
|
|
||||||
lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker())
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return lc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test fork of length N starting from block i
|
|
||||||
func testFork(t *testing.T, LightChain *LightChain, i, n int, comparator func(td1, td2 *big.Int)) {
|
|
||||||
// Copy old chain up to #i into a new db
|
|
||||||
db, LightChain2, err := newCanonical(i)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("could not make new canonical in testFork", err)
|
|
||||||
}
|
|
||||||
// Assert the chains have the same header/block at #i
|
|
||||||
var hash1, hash2 common.Hash
|
|
||||||
hash1 = LightChain.GetHeaderByNumber(uint64(i)).Hash()
|
|
||||||
hash2 = LightChain2.GetHeaderByNumber(uint64(i)).Hash()
|
|
||||||
if hash1 != hash2 {
|
|
||||||
t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1)
|
|
||||||
}
|
|
||||||
// Extend the newly created chain
|
|
||||||
headerChainB := makeHeaderChain(LightChain2.CurrentHeader(), n, db, forkSeed)
|
|
||||||
if _, err := LightChain2.InsertHeaderChain(headerChainB); err != nil {
|
|
||||||
t.Fatalf("failed to insert forking chain: %v", err)
|
|
||||||
}
|
|
||||||
// Sanity check that the forked chain can be imported into the original
|
|
||||||
var tdPre, tdPost *big.Int
|
|
||||||
cur := LightChain.CurrentHeader()
|
|
||||||
tdPre = LightChain.GetTd(cur.Hash(), cur.Number.Uint64())
|
|
||||||
if err := testHeaderChainImport(headerChainB, LightChain); err != nil {
|
|
||||||
t.Fatalf("failed to import forked header chain: %v", err)
|
|
||||||
}
|
|
||||||
last := headerChainB[len(headerChainB)-1]
|
|
||||||
tdPost = LightChain.GetTd(last.Hash(), last.Number.Uint64())
|
|
||||||
// Compare the total difficulties of the chains
|
|
||||||
comparator(tdPre, tdPost)
|
|
||||||
}
|
|
||||||
|
|
||||||
// testHeaderChainImport tries to process a chain of header, writing them into
|
|
||||||
// the database if successful.
|
|
||||||
func testHeaderChainImport(chain []*types.Header, lightchain *LightChain) error {
|
|
||||||
for _, header := range chain {
|
|
||||||
// Try and validate the header
|
|
||||||
if err := lightchain.engine.VerifyHeader(lightchain.hc, header); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Manually insert the header into the database, but don't reorganize (allows subsequent testing)
|
|
||||||
lightchain.chainmu.Lock()
|
|
||||||
rawdb.WriteTd(lightchain.chainDb, header.Hash(), header.Number.Uint64(),
|
|
||||||
new(big.Int).Add(header.Difficulty, lightchain.GetTd(header.ParentHash, header.Number.Uint64()-1)))
|
|
||||||
rawdb.WriteHeader(lightchain.chainDb, header)
|
|
||||||
lightchain.chainmu.Unlock()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that given a starting canonical chain of a given size, it can be extended
|
|
||||||
// with various length chains.
|
|
||||||
func TestExtendCanonicalHeaders(t *testing.T) {
|
|
||||||
length := 5
|
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
|
||||||
_, processor, err := newCanonical(length)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
|
||||||
}
|
|
||||||
// Define the difficulty comparator
|
|
||||||
better := func(td1, td2 *big.Int) {
|
|
||||||
if td2.Cmp(td1) <= 0 {
|
|
||||||
t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Start fork from current height
|
|
||||||
testFork(t, processor, length, 1, better)
|
|
||||||
testFork(t, processor, length, 2, better)
|
|
||||||
testFork(t, processor, length, 5, better)
|
|
||||||
testFork(t, processor, length, 10, better)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that given a starting canonical chain of a given size, creating shorter
|
|
||||||
// forks do not take canonical ownership.
|
|
||||||
func TestShorterForkHeaders(t *testing.T) {
|
|
||||||
length := 10
|
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
|
||||||
_, processor, err := newCanonical(length)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
|
||||||
}
|
|
||||||
// Define the difficulty comparator
|
|
||||||
worse := func(td1, td2 *big.Int) {
|
|
||||||
if td2.Cmp(td1) >= 0 {
|
|
||||||
t.Errorf("total difficulty mismatch: have %v, expected less than %v", td2, td1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sum of numbers must be less than `length` for this to be a shorter fork
|
|
||||||
testFork(t, processor, 0, 3, worse)
|
|
||||||
testFork(t, processor, 0, 7, worse)
|
|
||||||
testFork(t, processor, 1, 1, worse)
|
|
||||||
testFork(t, processor, 1, 7, worse)
|
|
||||||
testFork(t, processor, 5, 3, worse)
|
|
||||||
testFork(t, processor, 5, 4, worse)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that given a starting canonical chain of a given size, creating longer
|
|
||||||
// forks do take canonical ownership.
|
|
||||||
func TestLongerForkHeaders(t *testing.T) {
|
|
||||||
length := 10
|
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
|
||||||
_, processor, err := newCanonical(length)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
|
||||||
}
|
|
||||||
// Define the difficulty comparator
|
|
||||||
better := func(td1, td2 *big.Int) {
|
|
||||||
if td2.Cmp(td1) <= 0 {
|
|
||||||
t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sum of numbers must be greater than `length` for this to be a longer fork
|
|
||||||
testFork(t, processor, 0, 11, better)
|
|
||||||
testFork(t, processor, 0, 15, better)
|
|
||||||
testFork(t, processor, 1, 10, better)
|
|
||||||
testFork(t, processor, 1, 12, better)
|
|
||||||
testFork(t, processor, 5, 6, better)
|
|
||||||
testFork(t, processor, 5, 8, better)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that given a starting canonical chain of a given size, creating equal
|
|
||||||
// forks do take canonical ownership.
|
|
||||||
func TestEqualForkHeaders(t *testing.T) {
|
|
||||||
length := 10
|
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
|
||||||
_, processor, err := newCanonical(length)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
|
||||||
}
|
|
||||||
// Define the difficulty comparator
|
|
||||||
equal := func(td1, td2 *big.Int) {
|
|
||||||
if td2.Cmp(td1) != 0 {
|
|
||||||
t.Errorf("total difficulty mismatch: have %v, want %v", td2, td1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sum of numbers must be equal to `length` for this to be an equal fork
|
|
||||||
testFork(t, processor, 0, 10, equal)
|
|
||||||
testFork(t, processor, 1, 9, equal)
|
|
||||||
testFork(t, processor, 2, 8, equal)
|
|
||||||
testFork(t, processor, 5, 5, equal)
|
|
||||||
testFork(t, processor, 6, 4, equal)
|
|
||||||
testFork(t, processor, 9, 1, equal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that chains missing links do not get accepted by the processor.
|
|
||||||
func TestBrokenHeaderChain(t *testing.T) {
|
|
||||||
// Make chain starting from genesis
|
|
||||||
db, LightChain, err := newCanonical(10)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
|
||||||
}
|
|
||||||
// Create a forked chain, and try to insert with a missing link
|
|
||||||
chain := makeHeaderChain(LightChain.CurrentHeader(), 5, db, forkSeed)[1:]
|
|
||||||
if err := testHeaderChainImport(chain, LightChain); err == nil {
|
|
||||||
t.Errorf("broken header chain not reported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header {
|
|
||||||
var chain []*types.Header
|
|
||||||
for i, difficulty := range d {
|
|
||||||
header := &types.Header{
|
|
||||||
Coinbase: common.Address{seed},
|
|
||||||
Number: big.NewInt(int64(i + 1)),
|
|
||||||
Difficulty: big.NewInt(int64(difficulty)),
|
|
||||||
UncleHash: types.EmptyUncleHash,
|
|
||||||
TxHash: types.EmptyTxsHash,
|
|
||||||
ReceiptHash: types.EmptyReceiptsHash,
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
header.ParentHash = genesis.Hash()
|
|
||||||
} else {
|
|
||||||
header.ParentHash = chain[i-1].Hash()
|
|
||||||
}
|
|
||||||
chain = append(chain, types.CopyHeader(header))
|
|
||||||
}
|
|
||||||
return chain
|
|
||||||
}
|
|
||||||
|
|
||||||
type dummyOdr struct {
|
|
||||||
OdrBackend
|
|
||||||
db ethdb.Database
|
|
||||||
indexerConfig *IndexerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (odr *dummyOdr) Database() ethdb.Database {
|
|
||||||
return odr.db
|
|
||||||
}
|
|
||||||
|
|
||||||
func (odr *dummyOdr) Retrieve(ctx context.Context, req OdrRequest) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (odr *dummyOdr) IndexerConfig() *IndexerConfig {
|
|
||||||
return odr.indexerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that reorganizing a long difficult chain after a short easy one
|
|
||||||
// overwrites the canonical numbers and links in the database.
|
|
||||||
func TestReorgLongHeaders(t *testing.T) {
|
|
||||||
testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that reorganizing a short difficult chain after a long easy one
|
|
||||||
// overwrites the canonical numbers and links in the database.
|
|
||||||
func TestReorgShortHeaders(t *testing.T) {
|
|
||||||
testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testReorg(t *testing.T, first, second []int, td int64) {
|
|
||||||
bc := newTestLightChain()
|
|
||||||
|
|
||||||
// Insert an easy and a difficult chain afterwards
|
|
||||||
bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, first, 11))
|
|
||||||
bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, second, 22))
|
|
||||||
// Check that the chain is valid number and link wise
|
|
||||||
prev := bc.CurrentHeader()
|
|
||||||
for header := bc.GetHeaderByNumber(bc.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, bc.GetHeaderByNumber(header.Number.Uint64()-1) {
|
|
||||||
if prev.ParentHash != header.Hash() {
|
|
||||||
t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Make sure the chain total difficulty is the correct one
|
|
||||||
want := new(big.Int).Add(bc.genesisBlock.Difficulty(), big.NewInt(td))
|
|
||||||
if have := bc.GetTd(bc.CurrentHeader().Hash(), bc.CurrentHeader().Number.Uint64()); have.Cmp(want) != 0 {
|
|
||||||
t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that the insertion functions detect banned hashes.
|
|
||||||
func TestBadHeaderHashes(t *testing.T) {
|
|
||||||
bc := newTestLightChain()
|
|
||||||
|
|
||||||
// Create a chain, ban a hash and try to import
|
|
||||||
var err error
|
|
||||||
headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10)
|
|
||||||
core.BadHashes[headers[2].Hash()] = true
|
|
||||||
if _, err = bc.InsertHeaderChain(headers); !errors.Is(err, core.ErrBannedHash) {
|
|
||||||
t.Errorf("error mismatch: have: %v, want %v", err, core.ErrBannedHash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that bad hashes are detected on boot, and the chan rolled back to a
|
|
||||||
// good state prior to the bad hash.
|
|
||||||
func TestReorgBadHeaderHashes(t *testing.T) {
|
|
||||||
bc := newTestLightChain()
|
|
||||||
|
|
||||||
// Create a chain, import and ban afterwards
|
|
||||||
headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 3, 4}, 10)
|
|
||||||
|
|
||||||
if _, err := bc.InsertHeaderChain(headers); err != nil {
|
|
||||||
t.Fatalf("failed to import headers: %v", err)
|
|
||||||
}
|
|
||||||
if bc.CurrentHeader().Hash() != headers[3].Hash() {
|
|
||||||
t.Errorf("last header hash mismatch: have: %x, want %x", bc.CurrentHeader().Hash(), headers[3].Hash())
|
|
||||||
}
|
|
||||||
core.BadHashes[headers[3].Hash()] = true
|
|
||||||
defer func() { delete(core.BadHashes, headers[3].Hash()) }()
|
|
||||||
|
|
||||||
// Create a new LightChain and check that it rolled back the state.
|
|
||||||
ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create new chain manager: %v", err)
|
|
||||||
}
|
|
||||||
if ncm.CurrentHeader().Hash() != headers[2].Hash() {
|
|
||||||
t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
196
light/odr.go
196
light/odr.go
|
|
@ -1,196 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package light
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NoOdr is the default context passed to an ODR capable function when the ODR
|
|
||||||
// service is not required.
|
|
||||||
var NoOdr = context.Background()
|
|
||||||
|
|
||||||
// ErrNoPeers is returned if no peers capable of serving a queued request are available
|
|
||||||
var ErrNoPeers = errors.New("no suitable peers available")
|
|
||||||
|
|
||||||
// OdrBackend is an interface to a backend service that handles ODR retrievals type
|
|
||||||
type OdrBackend interface {
|
|
||||||
Database() ethdb.Database
|
|
||||||
ChtIndexer() *core.ChainIndexer
|
|
||||||
BloomTrieIndexer() *core.ChainIndexer
|
|
||||||
BloomIndexer() *core.ChainIndexer
|
|
||||||
Retrieve(ctx context.Context, req OdrRequest) error
|
|
||||||
RetrieveTxStatus(ctx context.Context, req *TxStatusRequest) error
|
|
||||||
IndexerConfig() *IndexerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// OdrRequest is an interface for retrieval requests
|
|
||||||
type OdrRequest interface {
|
|
||||||
StoreResult(db ethdb.Database)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrieID identifies a state or account storage trie
|
|
||||||
type TrieID struct {
|
|
||||||
BlockHash common.Hash
|
|
||||||
BlockNumber uint64
|
|
||||||
StateRoot common.Hash
|
|
||||||
Root common.Hash
|
|
||||||
AccountAddress []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateTrieID returns a TrieID for a state trie belonging to a certain block
|
|
||||||
// header.
|
|
||||||
func StateTrieID(header *types.Header) *TrieID {
|
|
||||||
return &TrieID{
|
|
||||||
BlockHash: header.Hash(),
|
|
||||||
BlockNumber: header.Number.Uint64(),
|
|
||||||
StateRoot: header.Root,
|
|
||||||
Root: header.Root,
|
|
||||||
AccountAddress: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StorageTrieID returns a TrieID for a contract storage trie at a given account
|
|
||||||
// of a given state trie. It also requires the root hash of the trie for
|
|
||||||
// checking Merkle proofs.
|
|
||||||
func StorageTrieID(state *TrieID, address common.Address, root common.Hash) *TrieID {
|
|
||||||
return &TrieID{
|
|
||||||
BlockHash: state.BlockHash,
|
|
||||||
BlockNumber: state.BlockNumber,
|
|
||||||
StateRoot: state.StateRoot,
|
|
||||||
AccountAddress: address[:],
|
|
||||||
Root: root,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrieRequest is the ODR request type for state/storage trie entries
|
|
||||||
type TrieRequest struct {
|
|
||||||
Id *TrieID
|
|
||||||
Key []byte
|
|
||||||
Proof *trienode.ProofSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResult stores the retrieved data in local database
|
|
||||||
func (req *TrieRequest) StoreResult(db ethdb.Database) {
|
|
||||||
req.Proof.Store(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CodeRequest is the ODR request type for retrieving contract code
|
|
||||||
type CodeRequest struct {
|
|
||||||
Id *TrieID // references storage trie of the account
|
|
||||||
Hash common.Hash
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResult stores the retrieved data in local database
|
|
||||||
func (req *CodeRequest) StoreResult(db ethdb.Database) {
|
|
||||||
rawdb.WriteCode(db, req.Hash, req.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockRequest is the ODR request type for retrieving block bodies
|
|
||||||
type BlockRequest struct {
|
|
||||||
Hash common.Hash
|
|
||||||
Number uint64
|
|
||||||
Header *types.Header
|
|
||||||
Rlp []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResult stores the retrieved data in local database
|
|
||||||
func (req *BlockRequest) StoreResult(db ethdb.Database) {
|
|
||||||
rawdb.WriteBodyRLP(db, req.Hash, req.Number, req.Rlp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiptsRequest is the ODR request type for retrieving receipts.
|
|
||||||
type ReceiptsRequest struct {
|
|
||||||
Hash common.Hash
|
|
||||||
Number uint64
|
|
||||||
Header *types.Header
|
|
||||||
Receipts types.Receipts
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResult stores the retrieved data in local database
|
|
||||||
func (req *ReceiptsRequest) StoreResult(db ethdb.Database) {
|
|
||||||
rawdb.WriteReceipts(db, req.Hash, req.Number, req.Receipts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie
|
|
||||||
type ChtRequest struct {
|
|
||||||
Config *IndexerConfig
|
|
||||||
ChtNum, BlockNum uint64
|
|
||||||
ChtRoot common.Hash
|
|
||||||
Header *types.Header
|
|
||||||
Td *big.Int
|
|
||||||
Proof *trienode.ProofSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResult stores the retrieved data in local database
|
|
||||||
func (req *ChtRequest) StoreResult(db ethdb.Database) {
|
|
||||||
hash, num := req.Header.Hash(), req.Header.Number.Uint64()
|
|
||||||
rawdb.WriteHeader(db, req.Header)
|
|
||||||
rawdb.WriteTd(db, hash, num, req.Td)
|
|
||||||
rawdb.WriteCanonicalHash(db, hash, num)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure
|
|
||||||
type BloomRequest struct {
|
|
||||||
OdrRequest
|
|
||||||
Config *IndexerConfig
|
|
||||||
BloomTrieNum uint64
|
|
||||||
BitIdx uint
|
|
||||||
SectionIndexList []uint64
|
|
||||||
BloomTrieRoot common.Hash
|
|
||||||
BloomBits [][]byte
|
|
||||||
Proofs *trienode.ProofSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResult stores the retrieved data in local database
|
|
||||||
func (req *BloomRequest) StoreResult(db ethdb.Database) {
|
|
||||||
for i, sectionIdx := range req.SectionIndexList {
|
|
||||||
sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*req.Config.BloomTrieSize-1)
|
|
||||||
// if we don't have the canonical hash stored for this section head number, we'll still store it under
|
|
||||||
// a key with a zero sectionHead. GetBloomBits will look there too if we still don't have the canonical
|
|
||||||
// hash. In the unlikely case we've retrieved the section head hash since then, we'll just retrieve the
|
|
||||||
// bit vector again from the network.
|
|
||||||
rawdb.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxStatus describes the status of a transaction
|
|
||||||
type TxStatus struct {
|
|
||||||
Status txpool.TxStatus
|
|
||||||
Lookup *rawdb.LegacyTxLookupEntry `rlp:"nil"`
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxStatusRequest is the ODR request type for retrieving transaction status
|
|
||||||
type TxStatusRequest struct {
|
|
||||||
Hashes []common.Hash
|
|
||||||
Status []TxStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreResult stores the retrieved data in local database
|
|
||||||
func (req *TxStatusRequest) StoreResult(db ethdb.Database) {}
|
|
||||||
|
|
@ -1,339 +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 light
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
||||||
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
|
||||||
testBankFunds = big.NewInt(1_000_000_000_000_000_000)
|
|
||||||
|
|
||||||
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
|
||||||
acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
|
||||||
acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey)
|
|
||||||
acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey)
|
|
||||||
|
|
||||||
testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
|
|
||||||
testContractAddr common.Address
|
|
||||||
)
|
|
||||||
|
|
||||||
type testOdr struct {
|
|
||||||
OdrBackend
|
|
||||||
indexerConfig *IndexerConfig
|
|
||||||
sdb, ldb ethdb.Database
|
|
||||||
serverState state.Database
|
|
||||||
disable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (odr *testOdr) Database() ethdb.Database {
|
|
||||||
return odr.ldb
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrOdrDisabled = errors.New("ODR disabled")
|
|
||||||
|
|
||||||
func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error {
|
|
||||||
if odr.disable {
|
|
||||||
return ErrOdrDisabled
|
|
||||||
}
|
|
||||||
switch req := req.(type) {
|
|
||||||
case *BlockRequest:
|
|
||||||
number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash)
|
|
||||||
if number != nil {
|
|
||||||
req.Rlp = rawdb.ReadBodyRLP(odr.sdb, req.Hash, *number)
|
|
||||||
}
|
|
||||||
case *ReceiptsRequest:
|
|
||||||
number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash)
|
|
||||||
if number != nil {
|
|
||||||
req.Receipts = rawdb.ReadRawReceipts(odr.sdb, req.Hash, *number)
|
|
||||||
}
|
|
||||||
case *TrieRequest:
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
t state.Trie
|
|
||||||
)
|
|
||||||
if len(req.Id.AccountAddress) > 0 {
|
|
||||||
t, err = odr.serverState.OpenStorageTrie(req.Id.StateRoot, common.BytesToAddress(req.Id.AccountAddress), req.Id.Root, nil)
|
|
||||||
} else {
|
|
||||||
t, err = odr.serverState.OpenTrie(req.Id.Root)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
nodes := trienode.NewProofSet()
|
|
||||||
t.Prove(req.Key, nodes)
|
|
||||||
req.Proof = nodes
|
|
||||||
case *CodeRequest:
|
|
||||||
req.Data = rawdb.ReadCode(odr.sdb, req.Hash)
|
|
||||||
}
|
|
||||||
req.StoreResult(odr.ldb)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (odr *testOdr) IndexerConfig() *IndexerConfig {
|
|
||||||
return odr.indexerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type odrTestFn func(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error)
|
|
||||||
|
|
||||||
func TestOdrGetBlockLes2(t *testing.T) { testChainOdr(t, 1, odrGetBlock) }
|
|
||||||
|
|
||||||
func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
|
|
||||||
var block *types.Block
|
|
||||||
if bc != nil {
|
|
||||||
block = bc.GetBlockByHash(bhash)
|
|
||||||
} else {
|
|
||||||
block, _ = lc.GetBlockByHash(ctx, bhash)
|
|
||||||
}
|
|
||||||
if block == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
rlp, _ := rlp.EncodeToBytes(block)
|
|
||||||
return rlp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOdrGetReceiptsLes2(t *testing.T) { testChainOdr(t, 1, odrGetReceipts) }
|
|
||||||
|
|
||||||
func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
|
|
||||||
var receipts types.Receipts
|
|
||||||
if bc != nil {
|
|
||||||
if number := rawdb.ReadHeaderNumber(db, bhash); number != nil {
|
|
||||||
if header := rawdb.ReadHeader(db, bhash, *number); header != nil {
|
|
||||||
receipts = rawdb.ReadReceipts(db, bhash, *number, header.Time, bc.Config())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
number := rawdb.ReadHeaderNumber(db, bhash)
|
|
||||||
if number != nil {
|
|
||||||
receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash, *number)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if receipts == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
rlp, _ := rlp.EncodeToBytes(receipts)
|
|
||||||
return rlp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOdrAccountsLes2(t *testing.T) { testChainOdr(t, 1, odrAccounts) }
|
|
||||||
|
|
||||||
func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
|
|
||||||
dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678")
|
|
||||||
acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr}
|
|
||||||
|
|
||||||
var st *state.StateDB
|
|
||||||
if bc == nil {
|
|
||||||
header := lc.GetHeaderByHash(bhash)
|
|
||||||
st = NewState(ctx, header, lc.Odr())
|
|
||||||
} else {
|
|
||||||
header := bc.GetHeaderByHash(bhash)
|
|
||||||
st, _ = state.New(header.Root, bc.StateCache(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
var res []byte
|
|
||||||
for _, addr := range acc {
|
|
||||||
bal := st.GetBalance(addr)
|
|
||||||
rlp, _ := rlp.EncodeToBytes(bal)
|
|
||||||
res = append(res, rlp...)
|
|
||||||
}
|
|
||||||
return res, st.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOdrContractCallLes2(t *testing.T) { testChainOdr(t, 1, odrContractCall) }
|
|
||||||
|
|
||||||
func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
|
|
||||||
data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000")
|
|
||||||
config := params.TestChainConfig
|
|
||||||
|
|
||||||
var res []byte
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
data[35] = byte(i)
|
|
||||||
|
|
||||||
var (
|
|
||||||
st *state.StateDB
|
|
||||||
header *types.Header
|
|
||||||
chain core.ChainContext
|
|
||||||
)
|
|
||||||
if bc == nil {
|
|
||||||
chain = lc
|
|
||||||
header = lc.GetHeaderByHash(bhash)
|
|
||||||
st = NewState(ctx, header, lc.Odr())
|
|
||||||
} else {
|
|
||||||
chain = bc
|
|
||||||
header = bc.GetHeaderByHash(bhash)
|
|
||||||
st, _ = state.New(header.Root, bc.StateCache(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform read-only call.
|
|
||||||
st.SetBalance(testBankAddress, math.MaxBig256)
|
|
||||||
msg := &core.Message{
|
|
||||||
From: testBankAddress,
|
|
||||||
To: &testContractAddr,
|
|
||||||
Value: new(big.Int),
|
|
||||||
GasLimit: 1000000,
|
|
||||||
GasPrice: big.NewInt(params.InitialBaseFee),
|
|
||||||
GasFeeCap: big.NewInt(params.InitialBaseFee),
|
|
||||||
GasTipCap: new(big.Int),
|
|
||||||
Data: data,
|
|
||||||
SkipAccountChecks: true,
|
|
||||||
}
|
|
||||||
txContext := core.NewEVMTxContext(msg)
|
|
||||||
context := core.NewEVMBlockContext(header, chain, nil)
|
|
||||||
vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true})
|
|
||||||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
|
||||||
result, _ := core.ApplyMessage(vmenv, msg, gp)
|
|
||||||
res = append(res, result.Return()...)
|
|
||||||
if st.Error() != nil {
|
|
||||||
return res, st.Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func testChainGen(i int, block *core.BlockGen) {
|
|
||||||
signer := types.HomesteadSigner{}
|
|
||||||
switch i {
|
|
||||||
case 0:
|
|
||||||
// In block 1, the test bank sends account #1 some ether.
|
|
||||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testBankKey)
|
|
||||||
block.AddTx(tx)
|
|
||||||
case 1:
|
|
||||||
// In block 2, the test bank sends some more ether to account #1.
|
|
||||||
// acc1Addr passes it on to account #2.
|
|
||||||
// acc1Addr creates a test contract.
|
|
||||||
tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testBankKey)
|
|
||||||
nonce := block.TxNonce(acc1Addr)
|
|
||||||
tx2, _ := types.SignTx(types.NewTransaction(nonce, acc2Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, acc1Key)
|
|
||||||
nonce++
|
|
||||||
tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, block.BaseFee(), testContractCode), signer, acc1Key)
|
|
||||||
testContractAddr = crypto.CreateAddress(acc1Addr, nonce)
|
|
||||||
block.AddTx(tx1)
|
|
||||||
block.AddTx(tx2)
|
|
||||||
block.AddTx(tx3)
|
|
||||||
case 2:
|
|
||||||
// Block 3 is empty but was mined by account #2.
|
|
||||||
block.SetCoinbase(acc2Addr)
|
|
||||||
block.SetExtra([]byte("yeehaw"))
|
|
||||||
data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001")
|
|
||||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, block.BaseFee(), data), signer, testBankKey)
|
|
||||||
block.AddTx(tx)
|
|
||||||
case 3:
|
|
||||||
// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
|
|
||||||
b2 := block.PrevBlock(1).Header()
|
|
||||||
b2.Extra = []byte("foo")
|
|
||||||
block.AddUncle(b2)
|
|
||||||
b3 := block.PrevBlock(2).Header()
|
|
||||||
b3.Extra = []byte("foo")
|
|
||||||
block.AddUncle(b3)
|
|
||||||
data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002")
|
|
||||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, block.BaseFee(), data), signer, testBankKey)
|
|
||||||
block.AddTx(tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testChainOdr(t *testing.T, protocol int, fn odrTestFn) {
|
|
||||||
var (
|
|
||||||
sdb = rawdb.NewMemoryDatabase()
|
|
||||||
ldb = rawdb.NewMemoryDatabase()
|
|
||||||
gspec = &core.Genesis{
|
|
||||||
Config: params.TestChainConfig,
|
|
||||||
Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
|
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
// Assemble the test environment
|
|
||||||
blockchain, _ := core.NewBlockChain(sdb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil)
|
|
||||||
_, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, testChainGen)
|
|
||||||
if _, err := blockchain.InsertChain(gchain); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gspec.MustCommit(ldb, trie.NewDatabase(ldb, trie.HashDefaults))
|
|
||||||
odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig}
|
|
||||||
lightchain, err := NewLightChain(odr, gspec.Config, ethash.NewFullFaker())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
headers := make([]*types.Header, len(gchain))
|
|
||||||
for i, block := range gchain {
|
|
||||||
headers[i] = block.Header()
|
|
||||||
}
|
|
||||||
if _, err := lightchain.InsertHeaderChain(headers); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
test := func(expFail int) {
|
|
||||||
for i := uint64(0); i <= blockchain.CurrentHeader().Number.Uint64(); i++ {
|
|
||||||
bhash := rawdb.ReadCanonicalHash(sdb, i)
|
|
||||||
b1, err := fn(NoOdr, sdb, blockchain, nil, bhash)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error in full-node test for block %d: %v", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
exp := i < uint64(expFail)
|
|
||||||
b2, err := fn(ctx, ldb, nil, lightchain, bhash)
|
|
||||||
if err != nil && exp {
|
|
||||||
t.Errorf("error in ODR test for block %d: %v", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
eq := bytes.Equal(b1, b2)
|
|
||||||
if exp && !eq {
|
|
||||||
t.Errorf("ODR test output for block %d doesn't match full node", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expect retrievals to fail (except genesis block) without a les peer
|
|
||||||
t.Log("checking without ODR")
|
|
||||||
odr.disable = true
|
|
||||||
test(1)
|
|
||||||
|
|
||||||
// expect all retrievals to pass with ODR enabled
|
|
||||||
t.Log("checking with ODR")
|
|
||||||
odr.disable = false
|
|
||||||
test(len(gchain))
|
|
||||||
|
|
||||||
// still expect all retrievals to pass, now data should be cached locally
|
|
||||||
t.Log("checking without ODR, should be cached")
|
|
||||||
odr.disable = true
|
|
||||||
test(len(gchain))
|
|
||||||
}
|
|
||||||
|
|
@ -1,275 +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 light
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// errNonCanonicalHash is returned if the requested chain data doesn't belong
|
|
||||||
// to the canonical chain. ODR can only retrieve the canonical chain data covered
|
|
||||||
// by the CHT or Bloom trie for verification.
|
|
||||||
var errNonCanonicalHash = errors.New("hash is not currently canonical")
|
|
||||||
|
|
||||||
// GetHeaderByNumber retrieves the canonical block header corresponding to the
|
|
||||||
// given number. The returned header is proven by local CHT.
|
|
||||||
func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) {
|
|
||||||
// Try to find it in the local database first.
|
|
||||||
db := odr.Database()
|
|
||||||
hash := rawdb.ReadCanonicalHash(db, number)
|
|
||||||
|
|
||||||
// If there is a canonical hash, there should have a header too.
|
|
||||||
// But if it's pruned, re-fetch from network again.
|
|
||||||
if (hash != common.Hash{}) {
|
|
||||||
if header := rawdb.ReadHeader(db, hash, number); header != nil {
|
|
||||||
return header, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Retrieve the header via ODR, ensure the requested header is covered
|
|
||||||
// by local trusted CHT.
|
|
||||||
chts, _, chtHead := odr.ChtIndexer().Sections()
|
|
||||||
if number >= chts*odr.IndexerConfig().ChtSize {
|
|
||||||
return nil, errNoTrustedCht
|
|
||||||
}
|
|
||||||
r := &ChtRequest{
|
|
||||||
ChtRoot: GetChtRoot(db, chts-1, chtHead),
|
|
||||||
ChtNum: chts - 1,
|
|
||||||
BlockNum: number,
|
|
||||||
Config: odr.IndexerConfig(),
|
|
||||||
}
|
|
||||||
if err := odr.Retrieve(ctx, r); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return r.Header, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCanonicalHash retrieves the canonical block hash corresponding to the number.
|
|
||||||
func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) {
|
|
||||||
hash := rawdb.ReadCanonicalHash(odr.Database(), number)
|
|
||||||
if hash != (common.Hash{}) {
|
|
||||||
return hash, nil
|
|
||||||
}
|
|
||||||
header, err := GetHeaderByNumber(ctx, odr, number)
|
|
||||||
if err != nil {
|
|
||||||
return common.Hash{}, err
|
|
||||||
}
|
|
||||||
// number -> canonical mapping already be stored in db, get it.
|
|
||||||
return header.Hash(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTd retrieves the total difficulty corresponding to the number and hash.
|
|
||||||
func GetTd(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*big.Int, error) {
|
|
||||||
td := rawdb.ReadTd(odr.Database(), hash, number)
|
|
||||||
if td != nil {
|
|
||||||
return td, nil
|
|
||||||
}
|
|
||||||
header, err := GetHeaderByNumber(ctx, odr, number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if header.Hash() != hash {
|
|
||||||
return nil, errNonCanonicalHash
|
|
||||||
}
|
|
||||||
// <hash, number> -> td mapping already be stored in db, get it.
|
|
||||||
return rawdb.ReadTd(odr.Database(), hash, number), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding.
|
|
||||||
func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (rlp.RawValue, error) {
|
|
||||||
if data := rawdb.ReadBodyRLP(odr.Database(), hash, number); data != nil {
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
// Retrieve the block header first and pass it for verification.
|
|
||||||
header, err := GetHeaderByNumber(ctx, odr, number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errNoHeader
|
|
||||||
}
|
|
||||||
if header.Hash() != hash {
|
|
||||||
return nil, errNonCanonicalHash
|
|
||||||
}
|
|
||||||
r := &BlockRequest{Hash: hash, Number: number, Header: header}
|
|
||||||
if err := odr.Retrieve(ctx, r); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return r.Rlp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBody retrieves the block body (transactions, uncles) corresponding to the
|
|
||||||
// hash.
|
|
||||||
func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Body, error) {
|
|
||||||
data, err := GetBodyRLP(ctx, odr, hash, number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body := new(types.Body)
|
|
||||||
if err := rlp.DecodeBytes(data, body); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBlock retrieves an entire block corresponding to the hash, assembling it
|
|
||||||
// back from the stored header and body.
|
|
||||||
func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Block, error) {
|
|
||||||
// Retrieve the block header and body contents
|
|
||||||
header, err := GetHeaderByNumber(ctx, odr, number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errNoHeader
|
|
||||||
}
|
|
||||||
body, err := GetBody(ctx, odr, hash, number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Reassemble the block and return
|
|
||||||
return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBlockReceipts retrieves the receipts generated by the transactions included
|
|
||||||
// in a block given by its hash. Receipts will be filled in with context data.
|
|
||||||
func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (types.Receipts, error) {
|
|
||||||
// Assume receipts are already stored locally and attempt to retrieve.
|
|
||||||
receipts := rawdb.ReadRawReceipts(odr.Database(), hash, number)
|
|
||||||
if receipts == nil {
|
|
||||||
header, err := GetHeaderByNumber(ctx, odr, number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errNoHeader
|
|
||||||
}
|
|
||||||
if header.Hash() != hash {
|
|
||||||
return nil, errNonCanonicalHash
|
|
||||||
}
|
|
||||||
r := &ReceiptsRequest{Hash: hash, Number: number, Header: header}
|
|
||||||
if err := odr.Retrieve(ctx, r); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
receipts = r.Receipts
|
|
||||||
}
|
|
||||||
// If the receipts are incomplete, fill the derived fields
|
|
||||||
if len(receipts) > 0 && receipts[0].TxHash == (common.Hash{}) {
|
|
||||||
block, err := GetBlock(ctx, odr, hash, number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
genesis := rawdb.ReadCanonicalHash(odr.Database(), 0)
|
|
||||||
config := rawdb.ReadChainConfig(odr.Database(), genesis)
|
|
||||||
|
|
||||||
var blobGasPrice *big.Int
|
|
||||||
excessBlobGas := block.ExcessBlobGas()
|
|
||||||
if excessBlobGas != nil {
|
|
||||||
blobGasPrice = eip4844.CalcBlobFee(*excessBlobGas)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, block.Transactions()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawdb.WriteReceipts(odr.Database(), hash, number, receipts)
|
|
||||||
}
|
|
||||||
return receipts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBlockLogs retrieves the logs generated by the transactions included in a
|
|
||||||
// block given by its hash. Logs will be filled in with context data.
|
|
||||||
func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) ([][]*types.Log, error) {
|
|
||||||
receipts, err := GetBlockReceipts(ctx, odr, hash, number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
logs := make([][]*types.Log, len(receipts))
|
|
||||||
for i, receipt := range receipts {
|
|
||||||
logs[i] = receipt.Logs
|
|
||||||
}
|
|
||||||
return logs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to
|
|
||||||
// the given bit index and section indexes.
|
|
||||||
func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint64) ([][]byte, error) {
|
|
||||||
var (
|
|
||||||
reqIndex []int
|
|
||||||
reqSections []uint64
|
|
||||||
db = odr.Database()
|
|
||||||
result = make([][]byte, len(sections))
|
|
||||||
)
|
|
||||||
blooms, _, sectionHead := odr.BloomTrieIndexer().Sections()
|
|
||||||
for i, section := range sections {
|
|
||||||
sectionHead := rawdb.ReadCanonicalHash(db, (section+1)*odr.IndexerConfig().BloomSize-1)
|
|
||||||
// If we don't have the canonical hash stored for this section head number,
|
|
||||||
// we'll still look for an entry with a zero sectionHead (we store it with
|
|
||||||
// zero section head too if we don't know it at the time of the retrieval)
|
|
||||||
if bloomBits, _ := rawdb.ReadBloomBits(db, bit, section, sectionHead); len(bloomBits) != 0 {
|
|
||||||
result[i] = bloomBits
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// TODO(rjl493456442) Convert sectionIndex to BloomTrie relative index
|
|
||||||
if section >= blooms {
|
|
||||||
return nil, errNoTrustedBloomTrie
|
|
||||||
}
|
|
||||||
reqSections = append(reqSections, section)
|
|
||||||
reqIndex = append(reqIndex, i)
|
|
||||||
}
|
|
||||||
// Find all bloombits in database, nothing to query via odr, return.
|
|
||||||
if reqSections == nil {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
// Send odr request to retrieve missing bloombits.
|
|
||||||
r := &BloomRequest{
|
|
||||||
BloomTrieRoot: GetBloomTrieRoot(db, blooms-1, sectionHead),
|
|
||||||
BloomTrieNum: blooms - 1,
|
|
||||||
BitIdx: bit,
|
|
||||||
SectionIndexList: reqSections,
|
|
||||||
Config: odr.IndexerConfig(),
|
|
||||||
}
|
|
||||||
if err := odr.Retrieve(ctx, r); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i, idx := range reqIndex {
|
|
||||||
result[idx] = r.BloomBits[i]
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTransaction retrieves a canonical transaction by hash and also returns
|
|
||||||
// its position in the chain. There is no guarantee in the LES protocol that
|
|
||||||
// the mined transaction will be retrieved back for sure because of different
|
|
||||||
// reasons(the transaction is unindexed, the malicious server doesn't reply it
|
|
||||||
// deliberately, etc). Therefore, unretrieved transactions will receive a certain
|
|
||||||
// number of retries, thus giving a weak guarantee.
|
|
||||||
func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
|
|
||||||
r := &TxStatusRequest{Hashes: []common.Hash{txHash}}
|
|
||||||
if err := odr.RetrieveTxStatus(ctx, r); err != nil || r.Status[0].Status != txpool.TxStatusIncluded {
|
|
||||||
return nil, common.Hash{}, 0, 0, err
|
|
||||||
}
|
|
||||||
pos := r.Status[0].Lookup
|
|
||||||
// first ensure that we have the header, otherwise block body retrieval will fail
|
|
||||||
// also verify if this is a canonical block by getting the header by number and checking its hash
|
|
||||||
if header, err := GetHeaderByNumber(ctx, odr, pos.BlockIndex); err != nil || header.Hash() != pos.BlockHash {
|
|
||||||
return nil, common.Hash{}, 0, 0, err
|
|
||||||
}
|
|
||||||
body, err := GetBody(ctx, odr, pos.BlockHash, pos.BlockIndex)
|
|
||||||
if err != nil || uint64(len(body.Transactions)) <= pos.Index || body.Transactions[pos.Index].Hash() != txHash {
|
|
||||||
return nil, common.Hash{}, 0, 0, err
|
|
||||||
}
|
|
||||||
return body.Transactions[pos.Index], pos.BlockHash, pos.BlockIndex, pos.Index, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,538 +0,0 @@
|
||||||
// Copyright 2017 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 light
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/bitutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IndexerConfig includes a set of configs for chain indexers.
|
|
||||||
type IndexerConfig struct {
|
|
||||||
// The block frequency for creating CHTs.
|
|
||||||
ChtSize uint64
|
|
||||||
|
|
||||||
// The number of confirmations needed to generate/accept a canonical hash help trie.
|
|
||||||
ChtConfirms uint64
|
|
||||||
|
|
||||||
// The block frequency for creating new bloom bits.
|
|
||||||
BloomSize uint64
|
|
||||||
|
|
||||||
// The number of confirmation needed before a bloom section is considered probably final and its rotated bits
|
|
||||||
// are calculated.
|
|
||||||
BloomConfirms uint64
|
|
||||||
|
|
||||||
// The block frequency for creating BloomTrie.
|
|
||||||
BloomTrieSize uint64
|
|
||||||
|
|
||||||
// The number of confirmations needed to generate/accept a bloom trie.
|
|
||||||
BloomTrieConfirms uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultServerIndexerConfig wraps a set of configs as a default indexer config for server side.
|
|
||||||
DefaultServerIndexerConfig = &IndexerConfig{
|
|
||||||
ChtSize: params.CHTFrequency,
|
|
||||||
ChtConfirms: params.HelperTrieProcessConfirmations,
|
|
||||||
BloomSize: params.BloomBitsBlocks,
|
|
||||||
BloomConfirms: params.BloomConfirms,
|
|
||||||
BloomTrieSize: params.BloomTrieFrequency,
|
|
||||||
BloomTrieConfirms: params.HelperTrieProcessConfirmations,
|
|
||||||
}
|
|
||||||
// DefaultClientIndexerConfig wraps a set of configs as a default indexer config for client side.
|
|
||||||
DefaultClientIndexerConfig = &IndexerConfig{
|
|
||||||
ChtSize: params.CHTFrequency,
|
|
||||||
ChtConfirms: params.HelperTrieConfirmations,
|
|
||||||
BloomSize: params.BloomBitsBlocksClient,
|
|
||||||
BloomConfirms: params.HelperTrieConfirmations,
|
|
||||||
BloomTrieSize: params.BloomTrieFrequency,
|
|
||||||
BloomTrieConfirms: params.HelperTrieConfirmations,
|
|
||||||
}
|
|
||||||
// TestServerIndexerConfig wraps a set of configs as a test indexer config for server side.
|
|
||||||
TestServerIndexerConfig = &IndexerConfig{
|
|
||||||
ChtSize: 128,
|
|
||||||
ChtConfirms: 1,
|
|
||||||
BloomSize: 16,
|
|
||||||
BloomConfirms: 1,
|
|
||||||
BloomTrieSize: 128,
|
|
||||||
BloomTrieConfirms: 1,
|
|
||||||
}
|
|
||||||
// TestClientIndexerConfig wraps a set of configs as a test indexer config for client side.
|
|
||||||
TestClientIndexerConfig = &IndexerConfig{
|
|
||||||
ChtSize: 128,
|
|
||||||
ChtConfirms: 8,
|
|
||||||
BloomSize: 128,
|
|
||||||
BloomConfirms: 8,
|
|
||||||
BloomTrieSize: 128,
|
|
||||||
BloomTrieConfirms: 8,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errNoTrustedCht = errors.New("no trusted canonical hash trie")
|
|
||||||
errNoTrustedBloomTrie = errors.New("no trusted bloom trie")
|
|
||||||
errNoHeader = errors.New("header not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format
|
|
||||||
type ChtNode struct {
|
|
||||||
Hash common.Hash
|
|
||||||
Td *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetChtRoot reads the CHT root associated to the given section from the database
|
|
||||||
func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
|
|
||||||
var encNumber [8]byte
|
|
||||||
binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
|
|
||||||
data, _ := db.Get(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...))
|
|
||||||
return common.BytesToHash(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreChtRoot writes the CHT root associated to the given section into the database
|
|
||||||
func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
|
|
||||||
var encNumber [8]byte
|
|
||||||
binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
|
|
||||||
db.Put(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChtIndexerBackend implements core.ChainIndexerBackend.
|
|
||||||
type ChtIndexerBackend struct {
|
|
||||||
disablePruning bool
|
|
||||||
diskdb, trieTable ethdb.Database
|
|
||||||
odr OdrBackend
|
|
||||||
triedb *trie.Database
|
|
||||||
section, sectionSize uint64
|
|
||||||
lastHash common.Hash
|
|
||||||
trie *trie.Trie
|
|
||||||
originRoot common.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewChtIndexer creates a Cht chain indexer
|
|
||||||
func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, disablePruning bool) *core.ChainIndexer {
|
|
||||||
trieTable := rawdb.NewTable(db, string(rawdb.ChtTablePrefix))
|
|
||||||
backend := &ChtIndexerBackend{
|
|
||||||
diskdb: db,
|
|
||||||
odr: odr,
|
|
||||||
trieTable: trieTable,
|
|
||||||
triedb: trie.NewDatabase(trieTable, trie.HashDefaults),
|
|
||||||
sectionSize: size,
|
|
||||||
disablePruning: disablePruning,
|
|
||||||
}
|
|
||||||
return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.ChtIndexTablePrefix)), backend, size, confirms, time.Millisecond*100, "cht")
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchMissingNodes tries to retrieve the last entry of the latest trusted CHT from the
|
|
||||||
// ODR backend in order to be able to add new entries and calculate subsequent root hashes
|
|
||||||
func (c *ChtIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error {
|
|
||||||
batch := c.trieTable.NewBatch()
|
|
||||||
r := &ChtRequest{ChtRoot: root, ChtNum: section - 1, BlockNum: section*c.sectionSize - 1, Config: c.odr.IndexerConfig()}
|
|
||||||
for {
|
|
||||||
err := c.odr.Retrieve(ctx, r)
|
|
||||||
switch err {
|
|
||||||
case nil:
|
|
||||||
r.Proof.Store(batch)
|
|
||||||
return batch.Write()
|
|
||||||
case ErrNoPeers:
|
|
||||||
// if there are no peers to serve, retry later
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
case <-time.After(time.Second * 10):
|
|
||||||
// stay in the loop and try again
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset implements core.ChainIndexerBackend
|
|
||||||
func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
|
|
||||||
root := types.EmptyRootHash
|
|
||||||
if section > 0 {
|
|
||||||
root = GetChtRoot(c.diskdb, section-1, lastSectionHead)
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
c.trie, err = trie.New(trie.TrieID(root), c.triedb)
|
|
||||||
|
|
||||||
if err != nil && c.odr != nil {
|
|
||||||
err = c.fetchMissingNodes(ctx, section, root)
|
|
||||||
if err == nil {
|
|
||||||
c.trie, err = trie.New(trie.TrieID(root), c.triedb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.section = section
|
|
||||||
c.originRoot = root
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process implements core.ChainIndexerBackend
|
|
||||||
func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) error {
|
|
||||||
hash, num := header.Hash(), header.Number.Uint64()
|
|
||||||
c.lastHash = hash
|
|
||||||
|
|
||||||
td := rawdb.ReadTd(c.diskdb, hash, num)
|
|
||||||
if td == nil {
|
|
||||||
panic(nil)
|
|
||||||
}
|
|
||||||
var encNumber [8]byte
|
|
||||||
binary.BigEndian.PutUint64(encNumber[:], num)
|
|
||||||
data, _ := rlp.EncodeToBytes(ChtNode{hash, td})
|
|
||||||
return c.trie.Update(encNumber[:], data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit implements core.ChainIndexerBackend
|
|
||||||
func (c *ChtIndexerBackend) Commit() error {
|
|
||||||
root, nodes, err := c.trie.Commit(false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Commit trie changes into trie database in case it's not nil.
|
|
||||||
if nodes != nil {
|
|
||||||
if err := c.triedb.Update(root, c.originRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := c.triedb.Commit(root, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Re-create trie with newly generated root and updated database.
|
|
||||||
c.trie, err = trie.New(trie.TrieID(root), c.triedb)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Pruning historical trie nodes if necessary.
|
|
||||||
if !c.disablePruning {
|
|
||||||
it := c.trieTable.NewIterator(nil, nil)
|
|
||||||
defer it.Release()
|
|
||||||
|
|
||||||
var (
|
|
||||||
deleted int
|
|
||||||
batch = c.trieTable.NewBatch()
|
|
||||||
t = time.Now()
|
|
||||||
)
|
|
||||||
hashes := make(map[common.Hash]struct{})
|
|
||||||
if nodes != nil {
|
|
||||||
for _, hash := range nodes.Hashes() {
|
|
||||||
hashes[hash] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for it.Next() {
|
|
||||||
trimmed := bytes.TrimPrefix(it.Key(), rawdb.ChtTablePrefix)
|
|
||||||
if len(trimmed) == common.HashLength {
|
|
||||||
if _, ok := hashes[common.BytesToHash(trimmed)]; !ok {
|
|
||||||
batch.Delete(trimmed)
|
|
||||||
deleted += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug("Prune historical CHT trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t)))
|
|
||||||
}
|
|
||||||
log.Info("Storing CHT", "section", c.section, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root))
|
|
||||||
StoreChtRoot(c.diskdb, c.section, c.lastHash, root)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prune implements core.ChainIndexerBackend which deletes all chain data
|
|
||||||
// (except hash<->number mappings) older than the specified threshold.
|
|
||||||
func (c *ChtIndexerBackend) Prune(threshold uint64) error {
|
|
||||||
// Short circuit if the light pruning is disabled.
|
|
||||||
if c.disablePruning {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t := time.Now()
|
|
||||||
// Always keep genesis header in database.
|
|
||||||
start, end := uint64(1), (threshold+1)*c.sectionSize
|
|
||||||
|
|
||||||
var batch = c.diskdb.NewBatch()
|
|
||||||
for {
|
|
||||||
numbers, hashes := rawdb.ReadAllCanonicalHashes(c.diskdb, start, end, 10240)
|
|
||||||
if len(numbers) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for i := 0; i < len(numbers); i++ {
|
|
||||||
// Keep hash<->number mapping in database otherwise the hash based
|
|
||||||
// API(e.g. GetReceipt, GetLogs) will be broken.
|
|
||||||
//
|
|
||||||
// Storage size wise, the size of a mapping is ~41bytes. For one
|
|
||||||
// section is about 1.3MB which is acceptable.
|
|
||||||
//
|
|
||||||
// In order to totally get rid of this index, we need an additional
|
|
||||||
// flag to specify how many historical data light client can serve.
|
|
||||||
rawdb.DeleteCanonicalHash(batch, numbers[i])
|
|
||||||
rawdb.DeleteBlockWithoutNumber(batch, hashes[i], numbers[i])
|
|
||||||
}
|
|
||||||
if batch.ValueSize() > ethdb.IdealBatchSize {
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
batch.Reset()
|
|
||||||
}
|
|
||||||
start = numbers[len(numbers)-1] + 1
|
|
||||||
}
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug("Prune history headers", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(t)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBloomTrieRoot reads the BloomTrie root associated to the given section from the database
|
|
||||||
func GetBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
|
|
||||||
var encNumber [8]byte
|
|
||||||
binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
|
|
||||||
data, _ := db.Get(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...))
|
|
||||||
return common.BytesToHash(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreBloomTrieRoot writes the BloomTrie root associated to the given section into the database
|
|
||||||
func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
|
|
||||||
var encNumber [8]byte
|
|
||||||
binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
|
|
||||||
db.Put(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// BloomTrieIndexerBackend implements core.ChainIndexerBackend
|
|
||||||
type BloomTrieIndexerBackend struct {
|
|
||||||
disablePruning bool
|
|
||||||
diskdb, trieTable ethdb.Database
|
|
||||||
triedb *trie.Database
|
|
||||||
odr OdrBackend
|
|
||||||
section uint64
|
|
||||||
parentSize uint64
|
|
||||||
size uint64
|
|
||||||
bloomTrieRatio uint64
|
|
||||||
trie *trie.Trie
|
|
||||||
originRoot common.Hash
|
|
||||||
sectionHeads []common.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBloomTrieIndexer creates a BloomTrie chain indexer
|
|
||||||
func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64, disablePruning bool) *core.ChainIndexer {
|
|
||||||
trieTable := rawdb.NewTable(db, string(rawdb.BloomTrieTablePrefix))
|
|
||||||
backend := &BloomTrieIndexerBackend{
|
|
||||||
diskdb: db,
|
|
||||||
odr: odr,
|
|
||||||
trieTable: trieTable,
|
|
||||||
triedb: trie.NewDatabase(trieTable, trie.HashDefaults),
|
|
||||||
parentSize: parentSize,
|
|
||||||
size: size,
|
|
||||||
disablePruning: disablePruning,
|
|
||||||
}
|
|
||||||
backend.bloomTrieRatio = size / parentSize
|
|
||||||
backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio)
|
|
||||||
return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.BloomTrieIndexPrefix)), backend, size, 0, time.Millisecond*100, "bloomtrie")
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchMissingNodes tries to retrieve the last entries of the latest trusted bloom trie from the
|
|
||||||
// ODR backend in order to be able to add new entries and calculate subsequent root hashes
|
|
||||||
func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error {
|
|
||||||
indexCh := make(chan uint, types.BloomBitLength)
|
|
||||||
type res struct {
|
|
||||||
nodes *trienode.ProofSet
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
resCh := make(chan res, types.BloomBitLength)
|
|
||||||
for i := 0; i < 20; i++ {
|
|
||||||
go func() {
|
|
||||||
for bitIndex := range indexCh {
|
|
||||||
r := &BloomRequest{BloomTrieRoot: root, BloomTrieNum: section - 1, BitIdx: bitIndex, SectionIndexList: []uint64{section - 1}, Config: b.odr.IndexerConfig()}
|
|
||||||
for {
|
|
||||||
if err := b.odr.Retrieve(ctx, r); err == ErrNoPeers {
|
|
||||||
// if there are no peers to serve, retry later
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
resCh <- res{nil, ctx.Err()}
|
|
||||||
return
|
|
||||||
case <-time.After(time.Second * 10):
|
|
||||||
// stay in the loop and try again
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resCh <- res{r.Proofs, err}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
for i := uint(0); i < types.BloomBitLength; i++ {
|
|
||||||
indexCh <- i
|
|
||||||
}
|
|
||||||
close(indexCh)
|
|
||||||
batch := b.trieTable.NewBatch()
|
|
||||||
for i := uint(0); i < types.BloomBitLength; i++ {
|
|
||||||
res := <-resCh
|
|
||||||
if res.err != nil {
|
|
||||||
return res.err
|
|
||||||
}
|
|
||||||
res.nodes.Store(batch)
|
|
||||||
}
|
|
||||||
return batch.Write()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset implements core.ChainIndexerBackend
|
|
||||||
func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
|
|
||||||
root := types.EmptyRootHash
|
|
||||||
if section > 0 {
|
|
||||||
root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead)
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
b.trie, err = trie.New(trie.TrieID(root), b.triedb)
|
|
||||||
if err != nil && b.odr != nil {
|
|
||||||
err = b.fetchMissingNodes(ctx, section, root)
|
|
||||||
if err == nil {
|
|
||||||
b.trie, err = trie.New(trie.TrieID(root), b.triedb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.section = section
|
|
||||||
b.originRoot = root
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process implements core.ChainIndexerBackend
|
|
||||||
func (b *BloomTrieIndexerBackend) Process(ctx context.Context, header *types.Header) error {
|
|
||||||
num := header.Number.Uint64() - b.section*b.size
|
|
||||||
if (num+1)%b.parentSize == 0 {
|
|
||||||
b.sectionHeads[num/b.parentSize] = header.Hash()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit implements core.ChainIndexerBackend
|
|
||||||
func (b *BloomTrieIndexerBackend) Commit() error {
|
|
||||||
var compSize, decompSize uint64
|
|
||||||
|
|
||||||
for i := uint(0); i < types.BloomBitLength; i++ {
|
|
||||||
var encKey [10]byte
|
|
||||||
binary.BigEndian.PutUint16(encKey[0:2], uint16(i))
|
|
||||||
binary.BigEndian.PutUint64(encKey[2:10], b.section)
|
|
||||||
var decomp []byte
|
|
||||||
for j := uint64(0); j < b.bloomTrieRatio; j++ {
|
|
||||||
data, err := rawdb.ReadBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
decompData, err2 := bitutil.DecompressBytes(data, int(b.parentSize/8))
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
decomp = append(decomp, decompData...)
|
|
||||||
}
|
|
||||||
comp := bitutil.CompressBytes(decomp)
|
|
||||||
|
|
||||||
decompSize += uint64(len(decomp))
|
|
||||||
compSize += uint64(len(comp))
|
|
||||||
|
|
||||||
var terr error
|
|
||||||
if len(comp) > 0 {
|
|
||||||
terr = b.trie.Update(encKey[:], comp)
|
|
||||||
} else {
|
|
||||||
terr = b.trie.Delete(encKey[:])
|
|
||||||
}
|
|
||||||
if terr != nil {
|
|
||||||
return terr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
root, nodes, err := b.trie.Commit(false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Commit trie changes into trie database in case it's not nil.
|
|
||||||
if nodes != nil {
|
|
||||||
if err := b.triedb.Update(root, b.originRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := b.triedb.Commit(root, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Re-create trie with newly generated root and updated database.
|
|
||||||
b.trie, err = trie.New(trie.TrieID(root), b.triedb)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Pruning historical trie nodes if necessary.
|
|
||||||
if !b.disablePruning {
|
|
||||||
it := b.trieTable.NewIterator(nil, nil)
|
|
||||||
defer it.Release()
|
|
||||||
|
|
||||||
var (
|
|
||||||
deleted int
|
|
||||||
batch = b.trieTable.NewBatch()
|
|
||||||
t = time.Now()
|
|
||||||
)
|
|
||||||
hashes := make(map[common.Hash]struct{})
|
|
||||||
if nodes != nil {
|
|
||||||
for _, hash := range nodes.Hashes() {
|
|
||||||
hashes[hash] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for it.Next() {
|
|
||||||
trimmed := bytes.TrimPrefix(it.Key(), rawdb.BloomTrieTablePrefix)
|
|
||||||
if len(trimmed) == common.HashLength {
|
|
||||||
if _, ok := hashes[common.BytesToHash(trimmed)]; !ok {
|
|
||||||
batch.Delete(trimmed)
|
|
||||||
deleted += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug("Prune historical bloom trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t)))
|
|
||||||
}
|
|
||||||
sectionHead := b.sectionHeads[b.bloomTrieRatio-1]
|
|
||||||
StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root)
|
|
||||||
log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prune implements core.ChainIndexerBackend which deletes all
|
|
||||||
// bloombits which older than the specified threshold.
|
|
||||||
func (b *BloomTrieIndexerBackend) Prune(threshold uint64) error {
|
|
||||||
// Short circuit if the light pruning is disabled.
|
|
||||||
if b.disablePruning {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
start := time.Now()
|
|
||||||
for i := uint(0); i < types.BloomBitLength; i++ {
|
|
||||||
rawdb.DeleteBloombits(b.diskdb, i, 0, threshold*b.bloomTrieRatio+b.bloomTrieRatio)
|
|
||||||
}
|
|
||||||
log.Debug("Prune history bloombits", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
319
light/trie.go
319
light/trie.go
|
|
@ -1,319 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package light
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
sha3Nil = crypto.Keccak256Hash(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB {
|
|
||||||
state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr), nil)
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStateDatabase(ctx context.Context, head *types.Header, odr OdrBackend) state.Database {
|
|
||||||
return &odrDatabase{ctx, StateTrieID(head), odr}
|
|
||||||
}
|
|
||||||
|
|
||||||
type odrDatabase struct {
|
|
||||||
ctx context.Context
|
|
||||||
id *TrieID
|
|
||||||
backend OdrBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *odrDatabase) OpenTrie(root common.Hash) (state.Trie, error) {
|
|
||||||
return &odrTrie{db: db, id: db.id}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *odrDatabase) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, _ state.Trie) (state.Trie, error) {
|
|
||||||
return &odrTrie{db: db, id: StorageTrieID(db.id, address, root)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *odrDatabase) CopyTrie(t state.Trie) state.Trie {
|
|
||||||
switch t := t.(type) {
|
|
||||||
case *odrTrie:
|
|
||||||
cpy := &odrTrie{db: t.db, id: t.id}
|
|
||||||
if t.trie != nil {
|
|
||||||
cpy.trie = t.trie.Copy()
|
|
||||||
}
|
|
||||||
return cpy
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("unknown trie type %T", t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *odrDatabase) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) {
|
|
||||||
if codeHash == sha3Nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
code := rawdb.ReadCode(db.backend.Database(), codeHash)
|
|
||||||
if len(code) != 0 {
|
|
||||||
return code, nil
|
|
||||||
}
|
|
||||||
id := *db.id
|
|
||||||
id.AccountAddress = addr[:]
|
|
||||||
req := &CodeRequest{Id: &id, Hash: codeHash}
|
|
||||||
err := db.backend.Retrieve(db.ctx, req)
|
|
||||||
return req.Data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *odrDatabase) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) {
|
|
||||||
code, err := db.ContractCode(addr, codeHash)
|
|
||||||
return len(code), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *odrDatabase) TrieDB() *trie.Database {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *odrDatabase) DiskDB() ethdb.KeyValueStore {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
type odrTrie struct {
|
|
||||||
db *odrDatabase
|
|
||||||
id *TrieID
|
|
||||||
trie *trie.Trie
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) {
|
|
||||||
key = crypto.Keccak256(key)
|
|
||||||
var enc []byte
|
|
||||||
err := t.do(key, func() (err error) {
|
|
||||||
enc, err = t.trie.Get(key)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil || len(enc) == 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, content, _, err := rlp.Split(enc)
|
|
||||||
return content, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) GetAccount(address common.Address) (*types.StateAccount, error) {
|
|
||||||
var (
|
|
||||||
enc []byte
|
|
||||||
key = crypto.Keccak256(address.Bytes())
|
|
||||||
)
|
|
||||||
err := t.do(key, func() (err error) {
|
|
||||||
enc, err = t.trie.Get(key)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil || len(enc) == 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
acct := new(types.StateAccount)
|
|
||||||
if err := rlp.DecodeBytes(enc, acct); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return acct, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) UpdateAccount(address common.Address, acc *types.StateAccount) error {
|
|
||||||
key := crypto.Keccak256(address.Bytes())
|
|
||||||
value, err := rlp.EncodeToBytes(acc)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("decoding error in account update: %w", err)
|
|
||||||
}
|
|
||||||
return t.do(key, func() error {
|
|
||||||
return t.trie.Update(key, value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) UpdateStorage(_ common.Address, key, value []byte) error {
|
|
||||||
key = crypto.Keccak256(key)
|
|
||||||
v, _ := rlp.EncodeToBytes(value)
|
|
||||||
return t.do(key, func() error {
|
|
||||||
return t.trie.Update(key, v)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) DeleteStorage(_ common.Address, key []byte) error {
|
|
||||||
key = crypto.Keccak256(key)
|
|
||||||
return t.do(key, func() error {
|
|
||||||
return t.trie.Delete(key)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteAccount abstracts an account deletion from the trie.
|
|
||||||
func (t *odrTrie) DeleteAccount(address common.Address) error {
|
|
||||||
key := crypto.Keccak256(address.Bytes())
|
|
||||||
return t.do(key, func() error {
|
|
||||||
return t.trie.Delete(key)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
|
|
||||||
if t.trie == nil {
|
|
||||||
return t.id.Root, nil, nil
|
|
||||||
}
|
|
||||||
return t.trie.Commit(collectLeaf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) Hash() common.Hash {
|
|
||||||
if t.trie == nil {
|
|
||||||
return t.id.Root
|
|
||||||
}
|
|
||||||
return t.trie.Hash()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) NodeIterator(startkey []byte) (trie.NodeIterator, error) {
|
|
||||||
return newNodeIterator(t, startkey), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) GetKey(sha []byte) []byte {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *odrTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
|
|
||||||
return errors.New("not implemented, needs client/server interface split")
|
|
||||||
}
|
|
||||||
|
|
||||||
// do tries and retries to execute a function until it returns with no error or
|
|
||||||
// an error type other than MissingNodeError
|
|
||||||
func (t *odrTrie) do(key []byte, fn func() error) error {
|
|
||||||
for {
|
|
||||||
var err error
|
|
||||||
if t.trie == nil {
|
|
||||||
var id *trie.ID
|
|
||||||
if len(t.id.AccountAddress) > 0 {
|
|
||||||
id = trie.StorageTrieID(t.id.StateRoot, crypto.Keccak256Hash(t.id.AccountAddress), t.id.Root)
|
|
||||||
} else {
|
|
||||||
id = trie.StateTrieID(t.id.StateRoot)
|
|
||||||
}
|
|
||||||
triedb := trie.NewDatabase(t.db.backend.Database(), trie.HashDefaults)
|
|
||||||
t.trie, err = trie.New(id, triedb)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
err = fn()
|
|
||||||
}
|
|
||||||
if _, ok := err.(*trie.MissingNodeError); !ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r := &TrieRequest{Id: t.id, Key: key}
|
|
||||||
if err := t.db.backend.Retrieve(t.db.ctx, r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type nodeIterator struct {
|
|
||||||
trie.NodeIterator
|
|
||||||
t *odrTrie
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator {
|
|
||||||
it := &nodeIterator{t: t}
|
|
||||||
// Open the actual non-ODR trie if that hasn't happened yet.
|
|
||||||
if t.trie == nil {
|
|
||||||
it.do(func() error {
|
|
||||||
var id *trie.ID
|
|
||||||
if len(t.id.AccountAddress) > 0 {
|
|
||||||
id = trie.StorageTrieID(t.id.StateRoot, crypto.Keccak256Hash(t.id.AccountAddress), t.id.Root)
|
|
||||||
} else {
|
|
||||||
id = trie.StateTrieID(t.id.StateRoot)
|
|
||||||
}
|
|
||||||
triedb := trie.NewDatabase(t.db.backend.Database(), trie.HashDefaults)
|
|
||||||
t, err := trie.New(id, triedb)
|
|
||||||
if err == nil {
|
|
||||||
it.t.trie = t
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
it.do(func() error {
|
|
||||||
var err error
|
|
||||||
it.NodeIterator, err = it.t.trie.NodeIterator(startkey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return it.NodeIterator.Error()
|
|
||||||
})
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *nodeIterator) Next(descend bool) bool {
|
|
||||||
var ok bool
|
|
||||||
it.do(func() error {
|
|
||||||
ok = it.NodeIterator.Next(descend)
|
|
||||||
return it.NodeIterator.Error()
|
|
||||||
})
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// do runs fn and attempts to fill in missing nodes by retrieving.
|
|
||||||
func (it *nodeIterator) do(fn func() error) {
|
|
||||||
var lasthash common.Hash
|
|
||||||
for {
|
|
||||||
it.err = fn()
|
|
||||||
missing, ok := it.err.(*trie.MissingNodeError)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if missing.NodeHash == lasthash {
|
|
||||||
it.err = fmt.Errorf("retrieve loop for trie node %x", missing.NodeHash)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lasthash = missing.NodeHash
|
|
||||||
r := &TrieRequest{Id: it.t.id, Key: nibblesToKey(missing.Path)}
|
|
||||||
if it.err = it.t.db.backend.Retrieve(it.t.db.ctx, r); it.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *nodeIterator) Error() error {
|
|
||||||
if it.err != nil {
|
|
||||||
return it.err
|
|
||||||
}
|
|
||||||
return it.NodeIterator.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func nibblesToKey(nib []byte) []byte {
|
|
||||||
if len(nib) > 0 && nib[len(nib)-1] == 0x10 {
|
|
||||||
nib = nib[:len(nib)-1] // drop terminator
|
|
||||||
}
|
|
||||||
if len(nib)&1 == 1 {
|
|
||||||
nib = append(nib, 0) // make even
|
|
||||||
}
|
|
||||||
key := make([]byte, len(nib)/2)
|
|
||||||
for bi, ni := 0, 0; ni < len(nib); bi, ni = bi+1, ni+2 {
|
|
||||||
key[bi] = nib[ni]<<4 | nib[ni+1]
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
// Copyright 2017 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 light
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNodeIterator(t *testing.T) {
|
|
||||||
var (
|
|
||||||
fulldb = rawdb.NewMemoryDatabase()
|
|
||||||
lightdb = rawdb.NewMemoryDatabase()
|
|
||||||
gspec = &core.Genesis{
|
|
||||||
Config: params.TestChainConfig,
|
|
||||||
Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
|
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
blockchain, _ := core.NewBlockChain(fulldb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil)
|
|
||||||
_, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, testChainGen)
|
|
||||||
if _, err := blockchain.InsertChain(gchain); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gspec.MustCommit(lightdb, trie.NewDatabase(lightdb, trie.HashDefaults))
|
|
||||||
ctx := context.Background()
|
|
||||||
odr := &testOdr{sdb: fulldb, ldb: lightdb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig}
|
|
||||||
head := blockchain.CurrentHeader()
|
|
||||||
lightTrie, _ := NewStateDatabase(ctx, head, odr).OpenTrie(head.Root)
|
|
||||||
fullTrie, _ := blockchain.StateCache().OpenTrie(head.Root)
|
|
||||||
if err := diffTries(fullTrie, lightTrie); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func diffTries(t1, t2 state.Trie) error {
|
|
||||||
trieIt1, err := t1.NodeIterator(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
trieIt2, err := t2.NodeIterator(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i1 := trie.NewIterator(trieIt1)
|
|
||||||
i2 := trie.NewIterator(trieIt2)
|
|
||||||
for i1.Next() && i2.Next() {
|
|
||||||
if !bytes.Equal(i1.Key, i2.Key) {
|
|
||||||
spew.Dump(i2)
|
|
||||||
return fmt.Errorf("tries have different keys %x, %x", i1.Key, i2.Key)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(i1.Value, i2.Value) {
|
|
||||||
return fmt.Errorf("tries differ at key %x", i1.Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case i1.Err != nil:
|
|
||||||
return fmt.Errorf("full trie iterator error: %v", i1.Err)
|
|
||||||
case i2.Err != nil:
|
|
||||||
return fmt.Errorf("light trie iterator error: %v", i2.Err)
|
|
||||||
case i1.Next():
|
|
||||||
return errors.New("full trie iterator has more k/v pairs")
|
|
||||||
case i2.Next():
|
|
||||||
return errors.New("light trie iterator has more k/v pairs")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
556
light/txpool.go
556
light/txpool.go
|
|
@ -1,556 +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 light
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// chainHeadChanSize is the size of channel listening to ChainHeadEvent.
|
|
||||||
chainHeadChanSize = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
// txPermanent is the number of mined blocks after a mined transaction is
|
|
||||||
// considered permanent and no rollback is expected
|
|
||||||
var txPermanent = uint64(500)
|
|
||||||
|
|
||||||
// TxPool implements the transaction pool for light clients, which keeps track
|
|
||||||
// of the status of locally created transactions, detecting if they are included
|
|
||||||
// in a block (mined) or rolled back. There are no queued transactions since we
|
|
||||||
// always receive all locally signed transactions in the same order as they are
|
|
||||||
// created.
|
|
||||||
type TxPool struct {
|
|
||||||
config *params.ChainConfig
|
|
||||||
signer types.Signer
|
|
||||||
quit chan bool
|
|
||||||
txFeed event.Feed
|
|
||||||
scope event.SubscriptionScope
|
|
||||||
chainHeadCh chan core.ChainHeadEvent
|
|
||||||
chainHeadSub event.Subscription
|
|
||||||
mu sync.RWMutex
|
|
||||||
chain *LightChain
|
|
||||||
odr OdrBackend
|
|
||||||
chainDb ethdb.Database
|
|
||||||
relay TxRelayBackend
|
|
||||||
head common.Hash
|
|
||||||
nonce map[common.Address]uint64 // "pending" nonce
|
|
||||||
pending map[common.Hash]*types.Transaction // pending transactions by tx hash
|
|
||||||
mined map[common.Hash][]*types.Transaction // mined transactions by block hash
|
|
||||||
clearIdx uint64 // earliest block nr that can contain mined tx info
|
|
||||||
|
|
||||||
istanbul bool // Fork indicator whether we are in the istanbul stage.
|
|
||||||
eip2718 bool // Fork indicator whether we are in the eip2718 stage.
|
|
||||||
shanghai bool // Fork indicator whether we are in the shanghai stage.
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxRelayBackend provides an interface to the mechanism that forwards transactions to the
|
|
||||||
// ETH network. The implementations of the functions should be non-blocking.
|
|
||||||
//
|
|
||||||
// Send instructs backend to forward new transactions NewHead notifies backend about a new
|
|
||||||
// head after processed by the tx pool, including mined and rolled back transactions since
|
|
||||||
// the last event.
|
|
||||||
//
|
|
||||||
// Discard notifies backend about transactions that should be discarded either because
|
|
||||||
// they have been replaced by a re-send or because they have been mined long ago and no
|
|
||||||
// rollback is expected.
|
|
||||||
type TxRelayBackend interface {
|
|
||||||
Send(txs types.Transactions)
|
|
||||||
NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash)
|
|
||||||
Discard(hashes []common.Hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTxPool creates a new light transaction pool
|
|
||||||
func NewTxPool(config *params.ChainConfig, chain *LightChain, relay TxRelayBackend) *TxPool {
|
|
||||||
pool := &TxPool{
|
|
||||||
config: config,
|
|
||||||
signer: types.LatestSigner(config),
|
|
||||||
nonce: make(map[common.Address]uint64),
|
|
||||||
pending: make(map[common.Hash]*types.Transaction),
|
|
||||||
mined: make(map[common.Hash][]*types.Transaction),
|
|
||||||
quit: make(chan bool),
|
|
||||||
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
|
|
||||||
chain: chain,
|
|
||||||
relay: relay,
|
|
||||||
odr: chain.Odr(),
|
|
||||||
chainDb: chain.Odr().Database(),
|
|
||||||
head: chain.CurrentHeader().Hash(),
|
|
||||||
clearIdx: chain.CurrentHeader().Number.Uint64(),
|
|
||||||
}
|
|
||||||
// Subscribe events from blockchain
|
|
||||||
pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
|
|
||||||
go pool.eventLoop()
|
|
||||||
|
|
||||||
return pool
|
|
||||||
}
|
|
||||||
|
|
||||||
// currentState returns the light state of the current head header
|
|
||||||
func (pool *TxPool) currentState(ctx context.Context) *state.StateDB {
|
|
||||||
return NewState(ctx, pool.chain.CurrentHeader(), pool.odr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNonce returns the "pending" nonce of a given address. It always queries
|
|
||||||
// the nonce belonging to the latest header too in order to detect if another
|
|
||||||
// client using the same key sent a transaction.
|
|
||||||
func (pool *TxPool) GetNonce(ctx context.Context, addr common.Address) (uint64, error) {
|
|
||||||
state := pool.currentState(ctx)
|
|
||||||
nonce := state.GetNonce(addr)
|
|
||||||
if state.Error() != nil {
|
|
||||||
return 0, state.Error()
|
|
||||||
}
|
|
||||||
sn, ok := pool.nonce[addr]
|
|
||||||
if ok && sn > nonce {
|
|
||||||
nonce = sn
|
|
||||||
}
|
|
||||||
if !ok || sn < nonce {
|
|
||||||
pool.nonce[addr] = nonce
|
|
||||||
}
|
|
||||||
return nonce, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// txStateChanges stores the recent changes between pending/mined states of
|
|
||||||
// transactions. True means mined, false means rolled back, no entry means no change
|
|
||||||
type txStateChanges map[common.Hash]bool
|
|
||||||
|
|
||||||
// setState sets the status of a tx to either recently mined or recently rolled back
|
|
||||||
func (txc txStateChanges) setState(txHash common.Hash, mined bool) {
|
|
||||||
val, ent := txc[txHash]
|
|
||||||
if ent && (val != mined) {
|
|
||||||
delete(txc, txHash)
|
|
||||||
} else {
|
|
||||||
txc[txHash] = mined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLists creates lists of mined and rolled back tx hashes
|
|
||||||
func (txc txStateChanges) getLists() (mined []common.Hash, rollback []common.Hash) {
|
|
||||||
for hash, val := range txc {
|
|
||||||
if val {
|
|
||||||
mined = append(mined, hash)
|
|
||||||
} else {
|
|
||||||
rollback = append(rollback, hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkMinedTxs checks newly added blocks for the currently pending transactions
|
|
||||||
// and marks them as mined if necessary. It also stores block position in the db
|
|
||||||
// and adds them to the received txStateChanges map.
|
|
||||||
func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number uint64, txc txStateChanges) error {
|
|
||||||
// If no transactions are pending, we don't care about anything
|
|
||||||
if len(pool.pending) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
block, err := GetBlock(ctx, pool.odr, hash, number)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Gather all the local transaction mined in this block
|
|
||||||
list := pool.mined[hash]
|
|
||||||
for _, tx := range block.Transactions() {
|
|
||||||
if _, ok := pool.pending[tx.Hash()]; ok {
|
|
||||||
list = append(list, tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If some transactions have been mined, write the needed data to disk and update
|
|
||||||
if list != nil {
|
|
||||||
// Retrieve all the receipts belonging to this block and write the lookup table
|
|
||||||
if _, err := GetBlockReceipts(ctx, pool.odr, hash, number); err != nil { // ODR caches, ignore results
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rawdb.WriteTxLookupEntriesByBlock(pool.chainDb, block)
|
|
||||||
|
|
||||||
// Update the transaction pool's state
|
|
||||||
for _, tx := range list {
|
|
||||||
delete(pool.pending, tx.Hash())
|
|
||||||
txc.setState(tx.Hash(), true)
|
|
||||||
}
|
|
||||||
pool.mined[hash] = list
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// rollbackTxs marks the transactions contained in recently rolled back blocks
|
|
||||||
// as rolled back. It also removes any positional lookup entries.
|
|
||||||
func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) {
|
|
||||||
batch := pool.chainDb.NewBatch()
|
|
||||||
if list, ok := pool.mined[hash]; ok {
|
|
||||||
for _, tx := range list {
|
|
||||||
txHash := tx.Hash()
|
|
||||||
rawdb.DeleteTxLookupEntry(batch, txHash)
|
|
||||||
pool.pending[txHash] = tx
|
|
||||||
txc.setState(txHash, false)
|
|
||||||
}
|
|
||||||
delete(pool.mined, hash)
|
|
||||||
}
|
|
||||||
batch.Write()
|
|
||||||
}
|
|
||||||
|
|
||||||
// reorgOnNewHead sets a new head header, processing (and rolling back if necessary)
|
|
||||||
// the blocks since the last known head and returns a txStateChanges map containing
|
|
||||||
// the recently mined and rolled back transaction hashes. If an error (context
|
|
||||||
// timeout) occurs during checking new blocks, it leaves the locally known head
|
|
||||||
// at the latest checked block and still returns a valid txStateChanges, making it
|
|
||||||
// possible to continue checking the missing blocks at the next chain head event
|
|
||||||
func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) (txStateChanges, error) {
|
|
||||||
txc := make(txStateChanges)
|
|
||||||
oldh := pool.chain.GetHeaderByHash(pool.head)
|
|
||||||
newh := newHeader
|
|
||||||
// find common ancestor, create list of rolled back and new block hashes
|
|
||||||
var oldHashes, newHashes []common.Hash
|
|
||||||
for oldh.Hash() != newh.Hash() {
|
|
||||||
if oldh.Number.Uint64() >= newh.Number.Uint64() {
|
|
||||||
oldHashes = append(oldHashes, oldh.Hash())
|
|
||||||
oldh = pool.chain.GetHeader(oldh.ParentHash, oldh.Number.Uint64()-1)
|
|
||||||
}
|
|
||||||
if oldh.Number.Uint64() < newh.Number.Uint64() {
|
|
||||||
newHashes = append(newHashes, newh.Hash())
|
|
||||||
newh = pool.chain.GetHeader(newh.ParentHash, newh.Number.Uint64()-1)
|
|
||||||
if newh == nil {
|
|
||||||
// happens when CHT syncing, nothing to do
|
|
||||||
newh = oldh
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if oldh.Number.Uint64() < pool.clearIdx {
|
|
||||||
pool.clearIdx = oldh.Number.Uint64()
|
|
||||||
}
|
|
||||||
// roll back old blocks
|
|
||||||
for _, hash := range oldHashes {
|
|
||||||
pool.rollbackTxs(hash, txc)
|
|
||||||
}
|
|
||||||
pool.head = oldh.Hash()
|
|
||||||
// check mined txs of new blocks (array is in reversed order)
|
|
||||||
for i := len(newHashes) - 1; i >= 0; i-- {
|
|
||||||
hash := newHashes[i]
|
|
||||||
if err := pool.checkMinedTxs(ctx, hash, newHeader.Number.Uint64()-uint64(i), txc); err != nil {
|
|
||||||
return txc, err
|
|
||||||
}
|
|
||||||
pool.head = hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear old mined tx entries of old blocks
|
|
||||||
if idx := newHeader.Number.Uint64(); idx > pool.clearIdx+txPermanent {
|
|
||||||
idx2 := idx - txPermanent
|
|
||||||
if len(pool.mined) > 0 {
|
|
||||||
for i := pool.clearIdx; i < idx2; i++ {
|
|
||||||
hash := rawdb.ReadCanonicalHash(pool.chainDb, i)
|
|
||||||
if list, ok := pool.mined[hash]; ok {
|
|
||||||
hashes := make([]common.Hash, len(list))
|
|
||||||
for i, tx := range list {
|
|
||||||
hashes[i] = tx.Hash()
|
|
||||||
}
|
|
||||||
pool.relay.Discard(hashes)
|
|
||||||
delete(pool.mined, hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pool.clearIdx = idx2
|
|
||||||
}
|
|
||||||
|
|
||||||
return txc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockCheckTimeout is the time limit for checking new blocks for mined
|
|
||||||
// transactions. Checking resumes at the next chain head event if timed out.
|
|
||||||
const blockCheckTimeout = time.Second * 3
|
|
||||||
|
|
||||||
// eventLoop processes chain head events and also notifies the tx relay backend
|
|
||||||
// about the new head hash and tx state changes
|
|
||||||
func (pool *TxPool) eventLoop() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-pool.chainHeadCh:
|
|
||||||
pool.setNewHead(ev.Block.Header())
|
|
||||||
// hack in order to avoid hogging the lock; this part will
|
|
||||||
// be replaced by a subsequent PR.
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
|
|
||||||
// System stopped
|
|
||||||
case <-pool.chainHeadSub.Err():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pool *TxPool) setNewHead(head *types.Header) {
|
|
||||||
pool.mu.Lock()
|
|
||||||
defer pool.mu.Unlock()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), blockCheckTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
txc, _ := pool.reorgOnNewHead(ctx, head)
|
|
||||||
m, r := txc.getLists()
|
|
||||||
pool.relay.NewHead(pool.head, m, r)
|
|
||||||
|
|
||||||
// Update fork indicator by next pending block number
|
|
||||||
next := new(big.Int).Add(head.Number, big.NewInt(1))
|
|
||||||
pool.istanbul = pool.config.IsIstanbul(next)
|
|
||||||
pool.eip2718 = pool.config.IsBerlin(next)
|
|
||||||
pool.shanghai = pool.config.IsShanghai(next, uint64(time.Now().Unix()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the light transaction pool
|
|
||||||
func (pool *TxPool) Stop() {
|
|
||||||
// Unsubscribe all subscriptions registered from txpool
|
|
||||||
pool.scope.Close()
|
|
||||||
// Unsubscribe subscriptions registered from blockchain
|
|
||||||
pool.chainHeadSub.Unsubscribe()
|
|
||||||
close(pool.quit)
|
|
||||||
log.Info("Transaction pool stopped")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeNewTxsEvent registers a subscription of core.NewTxsEvent and
|
|
||||||
// starts sending event to the given channel.
|
|
||||||
func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
|
||||||
return pool.scope.Track(pool.txFeed.Subscribe(ch))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stats returns the number of currently pending (locally created) transactions
|
|
||||||
func (pool *TxPool) Stats() (pending int) {
|
|
||||||
pool.mu.RLock()
|
|
||||||
defer pool.mu.RUnlock()
|
|
||||||
|
|
||||||
pending = len(pool.pending)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateTx checks whether a transaction is valid according to the consensus rules.
|
|
||||||
func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error {
|
|
||||||
// Validate sender
|
|
||||||
var (
|
|
||||||
from common.Address
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// Validate the transaction sender and it's sig. Throw
|
|
||||||
// if the from fields is invalid.
|
|
||||||
if from, err = types.Sender(pool.signer, tx); err != nil {
|
|
||||||
return txpool.ErrInvalidSender
|
|
||||||
}
|
|
||||||
// Last but not least check for nonce errors
|
|
||||||
currentState := pool.currentState(ctx)
|
|
||||||
if n := currentState.GetNonce(from); n > tx.Nonce() {
|
|
||||||
return core.ErrNonceTooLow
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the transaction doesn't exceed the current
|
|
||||||
// block limit gas.
|
|
||||||
header := pool.chain.GetHeaderByHash(pool.head)
|
|
||||||
if header.GasLimit < tx.Gas() {
|
|
||||||
return txpool.ErrGasLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transactions can't be negative. This may never happen
|
|
||||||
// using RLP decoded transactions but may occur if you create
|
|
||||||
// a transaction using the RPC for example.
|
|
||||||
if tx.Value().Sign() < 0 {
|
|
||||||
return txpool.ErrNegativeValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transactor should have enough funds to cover the costs
|
|
||||||
// cost == V + GP * GL
|
|
||||||
if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 {
|
|
||||||
return core.ErrInsufficientFunds
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should supply enough intrinsic gas
|
|
||||||
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if tx.Gas() < gas {
|
|
||||||
return core.ErrIntrinsicGas
|
|
||||||
}
|
|
||||||
return currentState.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// add validates a new transaction and sets its state pending if processable.
|
|
||||||
// It also updates the locally stored nonce if necessary.
|
|
||||||
func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error {
|
|
||||||
hash := tx.Hash()
|
|
||||||
|
|
||||||
if pool.pending[hash] != nil {
|
|
||||||
return fmt.Errorf("known transaction (%x)", hash[:4])
|
|
||||||
}
|
|
||||||
err := pool.validateTx(ctx, tx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := pool.pending[hash]; !ok {
|
|
||||||
pool.pending[hash] = tx
|
|
||||||
|
|
||||||
nonce := tx.Nonce() + 1
|
|
||||||
|
|
||||||
addr, _ := types.Sender(pool.signer, tx)
|
|
||||||
if nonce > pool.nonce[addr] {
|
|
||||||
pool.nonce[addr] = nonce
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify the subscribers. This event is posted in a goroutine
|
|
||||||
// because it's possible that somewhere during the post "Remove transaction"
|
|
||||||
// gets called which will then wait for the global tx pool lock and deadlock.
|
|
||||||
go pool.txFeed.Send(core.NewTxsEvent{Txs: types.Transactions{tx}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print a log message if low enough level is set
|
|
||||||
log.Debug("Pooled new transaction", "hash", hash, "from", log.Lazy{Fn: func() common.Address { from, _ := types.Sender(pool.signer, tx); return from }}, "to", tx.To())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a transaction to the pool if valid and passes it to the tx relay
|
|
||||||
// backend
|
|
||||||
func (pool *TxPool) Add(ctx context.Context, tx *types.Transaction) error {
|
|
||||||
pool.mu.Lock()
|
|
||||||
defer pool.mu.Unlock()
|
|
||||||
data, err := tx.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pool.add(ctx, tx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//fmt.Println("Send", tx.Hash())
|
|
||||||
pool.relay.Send(types.Transactions{tx})
|
|
||||||
|
|
||||||
pool.chainDb.Put(tx.Hash().Bytes(), data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddBatch adds all valid transactions to the pool and passes them to
|
|
||||||
// the tx relay backend
|
|
||||||
func (pool *TxPool) AddBatch(ctx context.Context, txs []*types.Transaction) {
|
|
||||||
pool.mu.Lock()
|
|
||||||
defer pool.mu.Unlock()
|
|
||||||
var sendTx types.Transactions
|
|
||||||
|
|
||||||
for _, tx := range txs {
|
|
||||||
if err := pool.add(ctx, tx); err == nil {
|
|
||||||
sendTx = append(sendTx, tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(sendTx) > 0 {
|
|
||||||
pool.relay.Send(sendTx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTransaction returns a transaction if it is contained in the pool
|
|
||||||
// and nil otherwise.
|
|
||||||
func (pool *TxPool) GetTransaction(hash common.Hash) *types.Transaction {
|
|
||||||
// check the txs first
|
|
||||||
if tx, ok := pool.pending[hash]; ok {
|
|
||||||
return tx
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTransactions returns all currently processable transactions.
|
|
||||||
// The returned slice may be modified by the caller.
|
|
||||||
func (pool *TxPool) GetTransactions() (txs types.Transactions, err error) {
|
|
||||||
pool.mu.RLock()
|
|
||||||
defer pool.mu.RUnlock()
|
|
||||||
|
|
||||||
txs = make(types.Transactions, len(pool.pending))
|
|
||||||
i := 0
|
|
||||||
for _, tx := range pool.pending {
|
|
||||||
txs[i] = tx
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return txs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content retrieves the data content of the transaction pool, returning all the
|
|
||||||
// pending as well as queued transactions, grouped by account and nonce.
|
|
||||||
func (pool *TxPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
|
|
||||||
pool.mu.RLock()
|
|
||||||
defer pool.mu.RUnlock()
|
|
||||||
|
|
||||||
// Retrieve all the pending transactions and sort by account and by nonce
|
|
||||||
pending := make(map[common.Address][]*types.Transaction)
|
|
||||||
for _, tx := range pool.pending {
|
|
||||||
account, _ := types.Sender(pool.signer, tx)
|
|
||||||
pending[account] = append(pending[account], tx)
|
|
||||||
}
|
|
||||||
// There are no queued transactions in a light pool, just return an empty map
|
|
||||||
queued := make(map[common.Address][]*types.Transaction)
|
|
||||||
return pending, queued
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContentFrom retrieves the data content of the transaction pool, returning the
|
|
||||||
// pending as well as queued transactions of this address, grouped by nonce.
|
|
||||||
func (pool *TxPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
|
|
||||||
pool.mu.RLock()
|
|
||||||
defer pool.mu.RUnlock()
|
|
||||||
|
|
||||||
// Retrieve the pending transactions and sort by nonce
|
|
||||||
var pending []*types.Transaction
|
|
||||||
for _, tx := range pool.pending {
|
|
||||||
account, _ := types.Sender(pool.signer, tx)
|
|
||||||
if account != addr {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pending = append(pending, tx)
|
|
||||||
}
|
|
||||||
// There are no queued transactions in a light pool, just return an empty map
|
|
||||||
return pending, []*types.Transaction{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveTransactions removes all given transactions from the pool.
|
|
||||||
func (pool *TxPool) RemoveTransactions(txs types.Transactions) {
|
|
||||||
pool.mu.Lock()
|
|
||||||
defer pool.mu.Unlock()
|
|
||||||
|
|
||||||
var hashes []common.Hash
|
|
||||||
batch := pool.chainDb.NewBatch()
|
|
||||||
for _, tx := range txs {
|
|
||||||
hash := tx.Hash()
|
|
||||||
delete(pool.pending, hash)
|
|
||||||
batch.Delete(hash.Bytes())
|
|
||||||
hashes = append(hashes, hash)
|
|
||||||
}
|
|
||||||
batch.Write()
|
|
||||||
pool.relay.Discard(hashes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveTx removes the transaction with the given hash from the pool.
|
|
||||||
func (pool *TxPool) RemoveTx(hash common.Hash) {
|
|
||||||
pool.mu.Lock()
|
|
||||||
defer pool.mu.Unlock()
|
|
||||||
// delete from pending pool
|
|
||||||
delete(pool.pending, hash)
|
|
||||||
pool.chainDb.Delete(hash[:])
|
|
||||||
pool.relay.Discard([]common.Hash{hash})
|
|
||||||
}
|
|
||||||
|
|
@ -1,147 +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 light
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testTxRelay struct {
|
|
||||||
send, discard, mined chan int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *testTxRelay) Send(txs types.Transactions) {
|
|
||||||
r.send <- len(txs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *testTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) {
|
|
||||||
m := len(mined)
|
|
||||||
if m != 0 {
|
|
||||||
r.mined <- m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *testTxRelay) Discard(hashes []common.Hash) {
|
|
||||||
r.discard <- len(hashes)
|
|
||||||
}
|
|
||||||
|
|
||||||
const poolTestTxs = 1000
|
|
||||||
const poolTestBlocks = 100
|
|
||||||
|
|
||||||
// test tx 0..n-1
|
|
||||||
var testTx [poolTestTxs]*types.Transaction
|
|
||||||
|
|
||||||
// txs sent before block i
|
|
||||||
func sentTx(i int) int {
|
|
||||||
return int(math.Pow(float64(i)/float64(poolTestBlocks), 0.9) * poolTestTxs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// txs included in block i or before that (minedTx(i) <= sentTx(i))
|
|
||||||
func minedTx(i int) int {
|
|
||||||
return int(math.Pow(float64(i)/float64(poolTestBlocks), 1.1) * poolTestTxs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func txPoolTestChainGen(i int, block *core.BlockGen) {
|
|
||||||
s := minedTx(i)
|
|
||||||
e := minedTx(i + 1)
|
|
||||||
for i := s; i < e; i++ {
|
|
||||||
block.AddTx(testTx[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTxPool(t *testing.T) {
|
|
||||||
for i := range testTx {
|
|
||||||
testTx[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
sdb = rawdb.NewMemoryDatabase()
|
|
||||||
ldb = rawdb.NewMemoryDatabase()
|
|
||||||
gspec = &core.Genesis{
|
|
||||||
Config: params.TestChainConfig,
|
|
||||||
Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
|
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
// Assemble the test environment
|
|
||||||
blockchain, _ := core.NewBlockChain(sdb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil)
|
|
||||||
_, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), poolTestBlocks, txPoolTestChainGen)
|
|
||||||
if _, err := blockchain.InsertChain(gchain); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gspec.MustCommit(ldb, trie.NewDatabase(ldb, trie.HashDefaults))
|
|
||||||
odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig}
|
|
||||||
relay := &testTxRelay{
|
|
||||||
send: make(chan int, 1),
|
|
||||||
discard: make(chan int, 1),
|
|
||||||
mined: make(chan int, 1),
|
|
||||||
}
|
|
||||||
lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker())
|
|
||||||
txPermanent = 50
|
|
||||||
pool := NewTxPool(params.TestChainConfig, lightchain, relay)
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
for ii, block := range gchain {
|
|
||||||
i := ii + 1
|
|
||||||
s := sentTx(i - 1)
|
|
||||||
e := sentTx(i)
|
|
||||||
for i := s; i < e; i++ {
|
|
||||||
pool.Add(ctx, testTx[i])
|
|
||||||
got := <-relay.send
|
|
||||||
exp := 1
|
|
||||||
if got != exp {
|
|
||||||
t.Errorf("relay.Send expected len = %d, got %d", exp, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := lightchain.InsertHeaderChain([]*types.Header{block.Header()}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := <-relay.mined
|
|
||||||
exp := minedTx(i) - minedTx(i-1)
|
|
||||||
if got != exp {
|
|
||||||
t.Errorf("relay.NewHead expected len(mined) = %d, got %d", exp, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
exp = 0
|
|
||||||
if i > int(txPermanent)+1 {
|
|
||||||
exp = minedTx(i-int(txPermanent)-1) - minedTx(i-int(txPermanent)-2)
|
|
||||||
}
|
|
||||||
if exp != 0 {
|
|
||||||
got = <-relay.discard
|
|
||||||
if got != exp {
|
|
||||||
t.Errorf("relay.Discard expected len = %d, got %d", exp, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
Contributors to log15:
|
|
||||||
|
|
||||||
- Aaron L
|
|
||||||
- Alan Shreve
|
|
||||||
- Chris Hines
|
|
||||||
- Ciaran Downey
|
|
||||||
- Dmitry Chestnykh
|
|
||||||
- Evan Shaw
|
|
||||||
- Péter Szilágyi
|
|
||||||
- Trevor Gattis
|
|
||||||
- Vincent Vanackere
|
|
||||||
13
log/LICENSE
13
log/LICENSE
|
|
@ -1,13 +0,0 @@
|
||||||
Copyright 2014 Alan Shreve
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||

|
|
||||||
|
|
||||||
# log15 [](https://godoc.org/github.com/inconshreveable/log15) [](https://travis-ci.org/inconshreveable/log15)
|
|
||||||
|
|
||||||
Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is modeled after the Go standard library's [`io`](https://golang.org/pkg/io/) and [`net/http`](https://golang.org/pkg/net/http/) packages and is an alternative to the standard library's [`log`](https://golang.org/pkg/log/) package.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
- A simple, easy-to-understand API
|
|
||||||
- Promotes structured logging by encouraging use of key/value pairs
|
|
||||||
- Child loggers which inherit and add their own private context
|
|
||||||
- Lazy evaluation of expensive operations
|
|
||||||
- Simple Handler interface allowing for construction of flexible, custom logging configurations with a tiny API.
|
|
||||||
- Color terminal support
|
|
||||||
- Built-in support for logging to files, streams, syslog, and the network
|
|
||||||
- Support for forking records to multiple handlers, buffering records for output, failing over from failed handler writes, + more
|
|
||||||
|
|
||||||
## Versioning
|
|
||||||
The API of the master branch of log15 should always be considered unstable. If you want to rely on a stable API,
|
|
||||||
you must vendor the library.
|
|
||||||
|
|
||||||
## Importing
|
|
||||||
|
|
||||||
```go
|
|
||||||
import log "github.com/inconshreveable/log15"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
```go
|
|
||||||
// all loggers can have key/value context
|
|
||||||
srvlog := log.New("module", "app/server")
|
|
||||||
|
|
||||||
// all log messages can have key/value context
|
|
||||||
srvlog.Warn("abnormal conn rate", "rate", curRate, "low", lowRate, "high", highRate)
|
|
||||||
|
|
||||||
// child loggers with inherited context
|
|
||||||
connlog := srvlog.New("raddr", c.RemoteAddr())
|
|
||||||
connlog.Info("connection open")
|
|
||||||
|
|
||||||
// lazy evaluation
|
|
||||||
connlog.Debug("ping remote", "latency", log.Lazy{pingRemote})
|
|
||||||
|
|
||||||
// flexible configuration
|
|
||||||
srvlog.SetHandler(log.MultiHandler(
|
|
||||||
log.StreamHandler(os.Stderr, log.LogfmtFormat()),
|
|
||||||
log.LvlFilterHandler(
|
|
||||||
log.LvlError,
|
|
||||||
log.Must.FileHandler("errors.json", log.JSONFormat()))))
|
|
||||||
```
|
|
||||||
|
|
||||||
Will result in output that looks like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
WARN[06-17|21:58:10] abnormal conn rate module=app/server rate=0.500 low=0.100 high=0.800
|
|
||||||
INFO[06-17|21:58:10] connection open module=app/server raddr=10.0.0.1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Breaking API Changes
|
|
||||||
The following commits broke API stability. This reference is intended to help you understand the consequences of updating to a newer version
|
|
||||||
of log15.
|
|
||||||
|
|
||||||
- 57a084d014d4150152b19e4e531399a7145d1540 - Added a `Get()` method to the `Logger` interface to retrieve the current handler
|
|
||||||
- 93404652ee366648fa622b64d1e2b67d75a3094a - `Record` field `Call` changed to `stack.Call` with switch to `github.com/go-stack/stack`
|
|
||||||
- a5e7613673c73281f58e15a87d2cf0cf111e8152 - Restored `syslog.Priority` argument to the `SyslogXxx` handler constructors
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
### The varargs style is brittle and error prone! Can I have type safety please?
|
|
||||||
Yes. Use `log.Ctx`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
srvlog := log.New(log.Ctx{"module": "app/server"})
|
|
||||||
srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate})
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
Apache
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
This package is a fork of https://github.com/inconshreveable/log15, with some
|
|
||||||
minor modifications required by the go-ethereum codebase:
|
|
||||||
|
|
||||||
* Support for log level `trace`
|
|
||||||
* Modified behavior to exit on `critical` failure
|
|
||||||
327
log/doc.go
327
log/doc.go
|
|
@ -1,327 +0,0 @@
|
||||||
/*
|
|
||||||
Package log15 provides an opinionated, simple toolkit for best-practice logging that is
|
|
||||||
both human and machine readable. It is modeled after the standard library's io and net/http
|
|
||||||
packages.
|
|
||||||
|
|
||||||
This package enforces you to only log key/value pairs. Keys must be strings. Values may be
|
|
||||||
any type that you like. The default output format is logfmt, but you may also choose to use
|
|
||||||
JSON instead if that suits you. Here's how you log:
|
|
||||||
|
|
||||||
log.Info("page accessed", "path", r.URL.Path, "user_id", user.id)
|
|
||||||
|
|
||||||
This will output a line that looks like:
|
|
||||||
|
|
||||||
lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9
|
|
||||||
|
|
||||||
# Getting Started
|
|
||||||
|
|
||||||
To get started, you'll want to import the library:
|
|
||||||
|
|
||||||
import log "github.com/inconshreveable/log15"
|
|
||||||
|
|
||||||
Now you're ready to start logging:
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.Info("Program starting", "args", os.Args())
|
|
||||||
}
|
|
||||||
|
|
||||||
# Convention
|
|
||||||
|
|
||||||
Because recording a human-meaningful message is common and good practice, the first argument to every
|
|
||||||
logging method is the value to the *implicit* key 'msg'.
|
|
||||||
|
|
||||||
Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so
|
|
||||||
will the current timestamp with key 't'.
|
|
||||||
|
|
||||||
You may supply any additional context as a set of key/value pairs to the logging function. log15 allows
|
|
||||||
you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for
|
|
||||||
logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate
|
|
||||||
in the variadic argument list:
|
|
||||||
|
|
||||||
log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val)
|
|
||||||
|
|
||||||
If you really do favor your type-safety, you may choose to pass a log.Ctx instead:
|
|
||||||
|
|
||||||
log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val})
|
|
||||||
|
|
||||||
# Context loggers
|
|
||||||
|
|
||||||
Frequently, you want to add context to a logger so that you can track actions associated with it. An http
|
|
||||||
request is a good example. You can easily create new loggers that have context that is automatically included
|
|
||||||
with each log line:
|
|
||||||
|
|
||||||
requestlogger := log.New("path", r.URL.Path)
|
|
||||||
|
|
||||||
// later
|
|
||||||
requestlogger.Debug("db txn commit", "duration", txnTimer.Finish())
|
|
||||||
|
|
||||||
This will output a log line that includes the path context that is attached to the logger:
|
|
||||||
|
|
||||||
lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12
|
|
||||||
|
|
||||||
# Handlers
|
|
||||||
|
|
||||||
The Handler interface defines where log lines are printed to and how they are formatted. Handler is a
|
|
||||||
single interface that is inspired by net/http's handler interface:
|
|
||||||
|
|
||||||
type Handler interface {
|
|
||||||
Log(r *Record) error
|
|
||||||
}
|
|
||||||
|
|
||||||
Handlers can filter records, format them, or dispatch to multiple other Handlers.
|
|
||||||
This package implements a number of Handlers for common logging patterns that are
|
|
||||||
easily composed to create flexible, custom logging structures.
|
|
||||||
|
|
||||||
Here's an example handler that prints logfmt output to Stdout:
|
|
||||||
|
|
||||||
handler := log.StreamHandler(os.Stdout, log.LogfmtFormat())
|
|
||||||
|
|
||||||
Here's an example handler that defers to two other handlers. One handler only prints records
|
|
||||||
from the rpc package in logfmt to standard out. The other prints records at Error level
|
|
||||||
or above in JSON formatted output to the file /var/log/service.json
|
|
||||||
|
|
||||||
handler := log.MultiHandler(
|
|
||||||
log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JSONFormat())),
|
|
||||||
log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler())
|
|
||||||
)
|
|
||||||
|
|
||||||
# Logging File Names and Line Numbers
|
|
||||||
|
|
||||||
This package implements three Handlers that add debugging information to the
|
|
||||||
context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's
|
|
||||||
an example that adds the source file and line number of each logging call to
|
|
||||||
the context.
|
|
||||||
|
|
||||||
h := log.CallerFileHandler(log.StdoutHandler)
|
|
||||||
log.Root().SetHandler(h)
|
|
||||||
...
|
|
||||||
log.Error("open file", "err", err)
|
|
||||||
|
|
||||||
This will output a line that looks like:
|
|
||||||
|
|
||||||
lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42
|
|
||||||
|
|
||||||
Here's an example that logs the call stack rather than just the call site.
|
|
||||||
|
|
||||||
h := log.CallerStackHandler("%+v", log.StdoutHandler)
|
|
||||||
log.Root().SetHandler(h)
|
|
||||||
...
|
|
||||||
log.Error("open file", "err", err)
|
|
||||||
|
|
||||||
This will output a line that looks like:
|
|
||||||
|
|
||||||
lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" stack="[pkg/data.go:42 pkg/cmd/main.go]"
|
|
||||||
|
|
||||||
The "%+v" format instructs the handler to include the path of the source file
|
|
||||||
relative to the compile time GOPATH. The github.com/go-stack/stack package
|
|
||||||
documents the full list of formatting verbs and modifiers available.
|
|
||||||
|
|
||||||
# Custom Handlers
|
|
||||||
|
|
||||||
The Handler interface is so simple that it's also trivial to write your own. Let's create an
|
|
||||||
example handler which tries to write to one handler, but if that fails it falls back to
|
|
||||||
writing to another handler and includes the error that it encountered when trying to write
|
|
||||||
to the primary. This might be useful when trying to log over a network socket, but if that
|
|
||||||
fails you want to log those records to a file on disk.
|
|
||||||
|
|
||||||
type BackupHandler struct {
|
|
||||||
Primary Handler
|
|
||||||
Secondary Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *BackupHandler) Log (r *Record) error {
|
|
||||||
err := h.Primary.Log(r)
|
|
||||||
if err != nil {
|
|
||||||
r.Ctx = append(ctx, "primary_err", err)
|
|
||||||
return h.Secondary.Log(r)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
This pattern is so useful that a generic version that handles an arbitrary number of Handlers
|
|
||||||
is included as part of this library called FailoverHandler.
|
|
||||||
|
|
||||||
# Logging Expensive Operations
|
|
||||||
|
|
||||||
Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay
|
|
||||||
the price of computing them if you haven't turned up your logging level to a high level of detail.
|
|
||||||
|
|
||||||
This package provides a simple type to annotate a logging operation that you want to be evaluated
|
|
||||||
lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler
|
|
||||||
filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example:
|
|
||||||
|
|
||||||
func factorRSAKey() (factors []int) {
|
|
||||||
// return the factors of a very large number
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("factors", log.Lazy{factorRSAKey})
|
|
||||||
|
|
||||||
If this message is not logged for any reason (like logging at the Error level), then
|
|
||||||
factorRSAKey is never evaluated.
|
|
||||||
|
|
||||||
# Dynamic context values
|
|
||||||
|
|
||||||
The same log.Lazy mechanism can be used to attach context to a logger which you want to be
|
|
||||||
evaluated when the message is logged, but not when the logger is created. For example, let's imagine
|
|
||||||
a game where you have Player objects:
|
|
||||||
|
|
||||||
type Player struct {
|
|
||||||
name string
|
|
||||||
alive bool
|
|
||||||
log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
You always want to log a player's name and whether they're alive or dead, so when you create the player
|
|
||||||
object, you might do:
|
|
||||||
|
|
||||||
p := &Player{name: name, alive: true}
|
|
||||||
p.Logger = log.New("name", p.name, "alive", p.alive)
|
|
||||||
|
|
||||||
Only now, even after a player has died, the logger will still report they are alive because the logging
|
|
||||||
context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation
|
|
||||||
of whether the player is alive or not to each log message, so that the log records will reflect the player's
|
|
||||||
current state no matter when the log message is written:
|
|
||||||
|
|
||||||
p := &Player{name: name, alive: true}
|
|
||||||
isAlive := func() bool { return p.alive }
|
|
||||||
player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive})
|
|
||||||
|
|
||||||
# Terminal Format
|
|
||||||
|
|
||||||
If log15 detects that stdout is a terminal, it will configure the default
|
|
||||||
handler for it (which is log.StdoutHandler) to use TerminalFormat. This format
|
|
||||||
logs records nicely for your terminal, including color-coded output based
|
|
||||||
on log level.
|
|
||||||
|
|
||||||
# Error Handling
|
|
||||||
|
|
||||||
Becasuse log15 allows you to step around the type system, there are a few ways you can specify
|
|
||||||
invalid arguments to the logging functions. You could, for example, wrap something that is not
|
|
||||||
a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries
|
|
||||||
are typically the mechanism by which errors are reported, it would be onerous for the logging functions
|
|
||||||
to return errors. Instead, log15 handles errors by making these guarantees to you:
|
|
||||||
|
|
||||||
- Any log record containing an error will still be printed with the error explained to you as part of the log record.
|
|
||||||
|
|
||||||
- Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily
|
|
||||||
(and if you like, automatically) detect if any of your logging calls are passing bad values.
|
|
||||||
|
|
||||||
Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers
|
|
||||||
are encouraged to return errors only if they fail to write their log records out to an external source like if the
|
|
||||||
syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures
|
|
||||||
like the FailoverHandler.
|
|
||||||
|
|
||||||
# Library Use
|
|
||||||
|
|
||||||
log15 is intended to be useful for library authors as a way to provide configurable logging to
|
|
||||||
users of their library. Best practice for use in a library is to always disable all output for your logger
|
|
||||||
by default and to provide a public Logger instance that consumers of your library can configure. Like so:
|
|
||||||
|
|
||||||
package yourlib
|
|
||||||
|
|
||||||
import "github.com/inconshreveable/log15"
|
|
||||||
|
|
||||||
var Log = log.New()
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Log.SetHandler(log.DiscardHandler())
|
|
||||||
}
|
|
||||||
|
|
||||||
Users of your library may then enable it if they like:
|
|
||||||
|
|
||||||
import "github.com/inconshreveable/log15"
|
|
||||||
import "example.com/yourlib"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
handler := // custom handler setup
|
|
||||||
yourlib.Log.SetHandler(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Best practices attaching logger context
|
|
||||||
|
|
||||||
The ability to attach context to a logger is a powerful one. Where should you do it and why?
|
|
||||||
I favor embedding a Logger directly into any persistent object in my application and adding
|
|
||||||
unique, tracing context keys to it. For instance, imagine I am writing a web browser:
|
|
||||||
|
|
||||||
type Tab struct {
|
|
||||||
url string
|
|
||||||
render *RenderingContext
|
|
||||||
// ...
|
|
||||||
|
|
||||||
Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTab(url string) *Tab {
|
|
||||||
return &Tab {
|
|
||||||
// ...
|
|
||||||
url: url,
|
|
||||||
|
|
||||||
Logger: log.New("url", url),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
When a new tab is created, I assign a logger to it with the url of
|
|
||||||
the tab as context so it can easily be traced through the logs.
|
|
||||||
Now, whenever we perform any operation with the tab, we'll log with its
|
|
||||||
embedded logger and it will include the tab title automatically:
|
|
||||||
|
|
||||||
tab.Debug("moved position", "idx", tab.idx)
|
|
||||||
|
|
||||||
There's only one problem. What if the tab url changes? We could
|
|
||||||
use log.Lazy to make sure the current url is always written, but that
|
|
||||||
would mean that we couldn't trace a tab's full lifetime through our
|
|
||||||
logs after the user navigate to a new URL.
|
|
||||||
|
|
||||||
Instead, think about what values to attach to your loggers the
|
|
||||||
same way you think about what to use as a key in a SQL database schema.
|
|
||||||
If it's possible to use a natural key that is unique for the lifetime of the
|
|
||||||
object, do so. But otherwise, log15's ext package has a handy RandId
|
|
||||||
function to let you generate what you might call "surrogate keys"
|
|
||||||
They're just random hex identifiers to use for tracing. Back to our
|
|
||||||
Tab example, we would prefer to set up our Logger like so:
|
|
||||||
|
|
||||||
import logext "github.com/inconshreveable/log15/ext"
|
|
||||||
|
|
||||||
t := &Tab {
|
|
||||||
// ...
|
|
||||||
url: url,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl})
|
|
||||||
return t
|
|
||||||
|
|
||||||
Now we'll have a unique traceable identifier even across loading new urls, but
|
|
||||||
we'll still be able to see the tab's current url in the log messages.
|
|
||||||
|
|
||||||
# Must
|
|
||||||
|
|
||||||
For all Handler functions which can return an error, there is a version of that
|
|
||||||
function which will return no error but panics on failure. They are all available
|
|
||||||
on the Must object. For example:
|
|
||||||
|
|
||||||
log.Must.FileHandler("/path", log.JSONFormat)
|
|
||||||
log.Must.NetHandler("tcp", ":1234", log.JSONFormat)
|
|
||||||
|
|
||||||
# Inspiration and Credit
|
|
||||||
|
|
||||||
All of the following excellent projects inspired the design of this library:
|
|
||||||
|
|
||||||
code.google.com/p/log4go
|
|
||||||
|
|
||||||
github.com/op/go-logging
|
|
||||||
|
|
||||||
github.com/technoweenie/grohl
|
|
||||||
|
|
||||||
github.com/Sirupsen/logrus
|
|
||||||
|
|
||||||
github.com/kr/logfmt
|
|
||||||
|
|
||||||
github.com/spacemonkeygo/spacelog
|
|
||||||
|
|
||||||
golang's stdlib, notably io and net/http
|
|
||||||
|
|
||||||
# The Name
|
|
||||||
|
|
||||||
https://xkcd.com/927/
|
|
||||||
*/
|
|
||||||
package log
|
|
||||||
337
log/format.go
337
log/format.go
|
|
@ -2,18 +2,15 @@ package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -24,61 +21,19 @@ const (
|
||||||
termCtxMaxPadding = 40
|
termCtxMaxPadding = 40
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResetGlobalState resets the fieldPadding, which is useful for producing
|
|
||||||
// predictable output.
|
|
||||||
func ResetGlobalState() {
|
|
||||||
fieldPaddingLock.Lock()
|
|
||||||
fieldPadding = make(map[string]int)
|
|
||||||
fieldPaddingLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// locationTrims are trimmed for display to avoid unwieldy log lines.
|
|
||||||
var locationTrims = []string{
|
|
||||||
"github.com/ethereum/go-ethereum/",
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintOrigins sets or unsets log location (file:line) printing for terminal
|
|
||||||
// format output.
|
|
||||||
func PrintOrigins(print bool) {
|
|
||||||
locationEnabled.Store(print)
|
|
||||||
if print {
|
|
||||||
stackEnabled.Store(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stackEnabled is an atomic flag controlling whether the log handler needs
|
|
||||||
// to store the callsite stack. This is needed in case any handler wants to
|
|
||||||
// print locations (locationEnabled), use vmodule, or print full stacks (BacktraceAt).
|
|
||||||
var stackEnabled atomic.Bool
|
|
||||||
|
|
||||||
// locationEnabled is an atomic flag controlling whether the terminal formatter
|
|
||||||
// should append the log locations too when printing entries.
|
|
||||||
var locationEnabled atomic.Bool
|
|
||||||
|
|
||||||
// locationLength is the maxmimum path length encountered, which all logs are
|
|
||||||
// padded to to aid in alignment.
|
|
||||||
var locationLength atomic.Uint32
|
|
||||||
|
|
||||||
// fieldPadding is a global map with maximum field value lengths seen until now
|
|
||||||
// to allow padding log contexts in a bit smarter way.
|
|
||||||
var fieldPadding = make(map[string]int)
|
|
||||||
|
|
||||||
// fieldPaddingLock is a global mutex protecting the field padding map.
|
|
||||||
var fieldPaddingLock sync.RWMutex
|
|
||||||
|
|
||||||
type Format interface {
|
type Format interface {
|
||||||
Format(r *Record) []byte
|
Format(r slog.Record) []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatFunc returns a new Format object which uses
|
// FormatFunc returns a new Format object which uses
|
||||||
// the given function to perform record formatting.
|
// the given function to perform record formatting.
|
||||||
func FormatFunc(f func(*Record) []byte) Format {
|
func FormatFunc(f func(slog.Record) []byte) Format {
|
||||||
return formatFunc(f)
|
return formatFunc(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
type formatFunc func(*Record) []byte
|
type formatFunc func(slog.Record) []byte
|
||||||
|
|
||||||
func (f formatFunc) Format(r *Record) []byte {
|
func (f formatFunc) Format(r slog.Record) []byte {
|
||||||
return f(r)
|
return f(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,263 +44,100 @@ type TerminalStringer interface {
|
||||||
TerminalString() string
|
TerminalString() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TerminalFormat formats log records optimized for human readability on
|
func (h *TerminalHandler) TerminalFormat(r slog.Record, usecolor bool) []byte {
|
||||||
// a terminal with color-coded level output and terser human friendly timestamp.
|
msg := escapeMessage(r.Message)
|
||||||
// This format should only be used for interactive programs or while developing.
|
var color = 0
|
||||||
//
|
if usecolor {
|
||||||
// [LEVEL] [TIME] MESSAGE key=value key=value ...
|
switch r.Level {
|
||||||
//
|
case LevelCrit:
|
||||||
// Example:
|
color = 35
|
||||||
//
|
case slog.LevelError:
|
||||||
// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002
|
color = 31
|
||||||
func TerminalFormat(usecolor bool) Format {
|
case slog.LevelWarn:
|
||||||
return FormatFunc(func(r *Record) []byte {
|
color = 33
|
||||||
msg := escapeMessage(r.Msg)
|
case slog.LevelInfo:
|
||||||
var color = 0
|
color = 32
|
||||||
if usecolor {
|
case slog.LevelDebug:
|
||||||
switch r.Lvl {
|
color = 36
|
||||||
case LvlCrit:
|
case LevelTrace:
|
||||||
color = 35
|
color = 34
|
||||||
case LvlError:
|
|
||||||
color = 31
|
|
||||||
case LvlWarn:
|
|
||||||
color = 33
|
|
||||||
case LvlInfo:
|
|
||||||
color = 32
|
|
||||||
case LvlDebug:
|
|
||||||
color = 36
|
|
||||||
case LvlTrace:
|
|
||||||
color = 34
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
lvl := r.Lvl.AlignedString()
|
lvl := LevelAlignedString(r.Level)
|
||||||
if locationEnabled.Load() {
|
if color > 0 {
|
||||||
// Log origin printing was requested, format the location path and line number
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), msg)
|
||||||
location := fmt.Sprintf("%+v", r.Call)
|
} else {
|
||||||
for _, prefix := range locationTrims {
|
fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), msg)
|
||||||
location = strings.TrimPrefix(location, prefix)
|
}
|
||||||
}
|
// try to justify the log output for short messages
|
||||||
// Maintain the maximum location length for fancyer alignment
|
length := utf8.RuneCountInString(msg)
|
||||||
align := int(locationLength.Load())
|
if r.NumAttrs() > 0 && length < termMsgJust {
|
||||||
if align < len(location) {
|
b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length))
|
||||||
align = len(location)
|
}
|
||||||
locationLength.Store(uint32(align))
|
// print the keys logfmt style
|
||||||
}
|
h.logfmt(b, r, color)
|
||||||
padding := strings.Repeat(" ", align-len(location))
|
|
||||||
|
|
||||||
// Assemble and print the log heading
|
return b.Bytes()
|
||||||
if color > 0 {
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, msg)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, msg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if color > 0 {
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), msg)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// try to justify the log output for short messages
|
|
||||||
length := utf8.RuneCountInString(msg)
|
|
||||||
if len(r.Ctx) > 0 && length < termMsgJust {
|
|
||||||
b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length))
|
|
||||||
}
|
|
||||||
// print the keys logfmt style
|
|
||||||
logfmt(b, r.Ctx, color, true)
|
|
||||||
return b.Bytes()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable
|
func (h *TerminalHandler) logfmt(buf *bytes.Buffer, r slog.Record, color int) {
|
||||||
// format for key/value pairs.
|
attrs := []slog.Attr{}
|
||||||
//
|
r.Attrs(func(attr slog.Attr) bool {
|
||||||
// For more details see: http://godoc.org/github.com/kr/logfmt
|
attrs = append(attrs, attr)
|
||||||
func LogfmtFormat() Format {
|
return true
|
||||||
return FormatFunc(func(r *Record) []byte {
|
|
||||||
common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg}
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
logfmt(buf, append(common, r.Ctx...), 0, false)
|
|
||||||
return buf.Bytes()
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) {
|
attrs = append(h.attrs, attrs...)
|
||||||
for i := 0; i < len(ctx); i += 2 {
|
|
||||||
|
for i, attr := range attrs {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
buf.WriteByte(' ')
|
buf.WriteByte(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
k, ok := ctx[i].(string)
|
key := escapeString(attr.Key)
|
||||||
v := formatLogfmtValue(ctx[i+1], term)
|
rawVal := attr.Value.Any()
|
||||||
if !ok {
|
val := FormatLogfmtValue(rawVal, true)
|
||||||
k, v = errorKey, fmt.Sprintf("%+T is not a string key", ctx[i])
|
|
||||||
} else {
|
|
||||||
k = escapeString(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: we should probably check that all of your key bytes aren't invalid
|
// XXX: we should probably check that all of your key bytes aren't invalid
|
||||||
fieldPaddingLock.RLock()
|
// TODO (jwasinger) above comment was from log15 code. what does it mean? check that key bytes are ascii characters?
|
||||||
padding := fieldPadding[k]
|
padding := h.fieldPadding[key]
|
||||||
fieldPaddingLock.RUnlock()
|
|
||||||
|
|
||||||
length := utf8.RuneCountInString(v)
|
length := utf8.RuneCountInString(val)
|
||||||
if padding < length && length <= termCtxMaxPadding {
|
if padding < length && length <= termCtxMaxPadding {
|
||||||
padding = length
|
padding = length
|
||||||
|
h.fieldPadding[key] = padding
|
||||||
fieldPaddingLock.Lock()
|
|
||||||
fieldPadding[k] = padding
|
|
||||||
fieldPaddingLock.Unlock()
|
|
||||||
}
|
}
|
||||||
if color > 0 {
|
if color > 0 {
|
||||||
fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=", color, k)
|
fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=", color, key)
|
||||||
} else {
|
} else {
|
||||||
buf.WriteString(k)
|
buf.WriteString(key)
|
||||||
buf.WriteByte('=')
|
buf.WriteByte('=')
|
||||||
}
|
}
|
||||||
buf.WriteString(v)
|
buf.WriteString(val)
|
||||||
if i < len(ctx)-2 && padding > length {
|
if i < r.NumAttrs()-1 && padding > length {
|
||||||
buf.Write(bytes.Repeat([]byte{' '}, padding-length))
|
buf.Write(bytes.Repeat([]byte{' '}, padding-length))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf.WriteByte('\n')
|
buf.WriteByte('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONFormat formats log records as JSON objects separated by newlines.
|
// formatValue formats a value for serialization
|
||||||
// It is the equivalent of JSONFormatEx(false, true).
|
func FormatLogfmtValue(value interface{}, term bool) (result string) {
|
||||||
func JSONFormat() Format {
|
if value == nil {
|
||||||
return JSONFormatEx(false, true)
|
return "<nil>"
|
||||||
}
|
|
||||||
|
|
||||||
// JSONFormatOrderedEx formats log records as JSON arrays. If pretty is true,
|
|
||||||
// records will be pretty-printed. If lineSeparated is true, records
|
|
||||||
// will be logged with a new line between each record.
|
|
||||||
func JSONFormatOrderedEx(pretty, lineSeparated bool) Format {
|
|
||||||
jsonMarshal := json.Marshal
|
|
||||||
if pretty {
|
|
||||||
jsonMarshal = func(v interface{}) ([]byte, error) {
|
|
||||||
return json.MarshalIndent(v, "", " ")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return FormatFunc(func(r *Record) []byte {
|
|
||||||
props := map[string]interface{}{
|
|
||||||
r.KeyNames.Time: r.Time,
|
|
||||||
r.KeyNames.Lvl: r.Lvl.String(),
|
|
||||||
r.KeyNames.Msg: r.Msg,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := make([]string, len(r.Ctx))
|
|
||||||
for i := 0; i < len(r.Ctx); i += 2 {
|
|
||||||
if k, ok := r.Ctx[i].(string); ok {
|
|
||||||
ctx[i] = k
|
|
||||||
ctx[i+1] = formatLogfmtValue(r.Ctx[i+1], true)
|
|
||||||
} else {
|
|
||||||
props[errorKey] = fmt.Sprintf("%+T is not a string key,", r.Ctx[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
props[r.KeyNames.Ctx] = ctx
|
|
||||||
|
|
||||||
b, err := jsonMarshal(props)
|
|
||||||
if err != nil {
|
|
||||||
b, _ = jsonMarshal(map[string]string{
|
|
||||||
errorKey: err.Error(),
|
|
||||||
})
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
if lineSeparated {
|
|
||||||
b = append(b, '\n')
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONFormatEx formats log records as JSON objects. If pretty is true,
|
|
||||||
// records will be pretty-printed. If lineSeparated is true, records
|
|
||||||
// will be logged with a new line between each record.
|
|
||||||
func JSONFormatEx(pretty, lineSeparated bool) Format {
|
|
||||||
jsonMarshal := json.Marshal
|
|
||||||
if pretty {
|
|
||||||
jsonMarshal = func(v interface{}) ([]byte, error) {
|
|
||||||
return json.MarshalIndent(v, "", " ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormatFunc(func(r *Record) []byte {
|
|
||||||
props := map[string]interface{}{
|
|
||||||
r.KeyNames.Time: r.Time,
|
|
||||||
r.KeyNames.Lvl: r.Lvl.String(),
|
|
||||||
r.KeyNames.Msg: r.Msg,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(r.Ctx); i += 2 {
|
|
||||||
k, ok := r.Ctx[i].(string)
|
|
||||||
if !ok {
|
|
||||||
props[errorKey] = fmt.Sprintf("%+T is not a string key", r.Ctx[i])
|
|
||||||
} else {
|
|
||||||
props[k] = formatJSONValue(r.Ctx[i+1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := jsonMarshal(props)
|
|
||||||
if err != nil {
|
|
||||||
b, _ = jsonMarshal(map[string]string{
|
|
||||||
errorKey: err.Error(),
|
|
||||||
})
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
if lineSeparated {
|
|
||||||
b = append(b, '\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
return b
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatShared(value interface{}) (result interface{}) {
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
|
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||||
result = "nil"
|
result = "<nil>"
|
||||||
} else {
|
} else {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
switch v := value.(type) {
|
|
||||||
case time.Time:
|
|
||||||
return v.Format(timeFormat)
|
|
||||||
|
|
||||||
case error:
|
|
||||||
return v.Error()
|
|
||||||
|
|
||||||
case fmt.Stringer:
|
|
||||||
return v.String()
|
|
||||||
|
|
||||||
default:
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatJSONValue(value interface{}) interface{} {
|
|
||||||
value = formatShared(value)
|
|
||||||
switch value.(type) {
|
|
||||||
case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:
|
|
||||||
return value
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%+v", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatValue formats a value for serialization
|
|
||||||
func formatLogfmtValue(value interface{}, term bool) string {
|
|
||||||
if value == nil {
|
|
||||||
return "nil"
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case time.Time:
|
case time.Time:
|
||||||
// Performance optimization: No need for escaping since the provided
|
// Performance optimization: No need for escaping since the provided
|
||||||
|
|
@ -375,8 +167,11 @@ func formatLogfmtValue(value interface{}, term bool) string {
|
||||||
return escapeString(s.TerminalString())
|
return escapeString(s.TerminalString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value = formatShared(value)
|
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
|
case error:
|
||||||
|
return escapeString(v.Error())
|
||||||
|
case fmt.Stringer:
|
||||||
|
return escapeString(v.String())
|
||||||
case bool:
|
case bool:
|
||||||
return strconv.FormatBool(v)
|
return strconv.FormatBool(v)
|
||||||
case float32:
|
case float32:
|
||||||
|
|
|
||||||
504
log/handler.go
504
log/handler.go
|
|
@ -1,301 +1,25 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"math/big"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"time"
|
||||||
|
|
||||||
"github.com/go-stack/stack"
|
"github.com/holiman/uint256"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler defines where and how log records are written.
|
// Lazy allows you to defer calculation of a logged value that is expensive
|
||||||
// A Logger prints its log records by writing to a Handler.
|
// to compute until it is certain that it must be evaluated with the given filters.
|
||||||
// Handlers are composable, providing you great flexibility in combining
|
|
||||||
// them to achieve the logging structure that suits your applications.
|
|
||||||
type Handler interface {
|
|
||||||
Log(r *Record) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// FuncHandler returns a Handler that logs records with the given
|
|
||||||
// function.
|
|
||||||
func FuncHandler(fn func(r *Record) error) Handler {
|
|
||||||
return funcHandler(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
type funcHandler func(r *Record) error
|
|
||||||
|
|
||||||
func (h funcHandler) Log(r *Record) error {
|
|
||||||
return h(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamHandler writes log records to an io.Writer
|
|
||||||
// with the given format. StreamHandler can be used
|
|
||||||
// to easily begin writing log records to other
|
|
||||||
// outputs.
|
|
||||||
//
|
//
|
||||||
// StreamHandler wraps itself with LazyHandler and SyncHandler
|
// You may wrap any function which takes no arguments to Lazy. It may return any
|
||||||
// to evaluate Lazy objects and perform safe concurrent writes.
|
// number of values of any type.
|
||||||
func StreamHandler(wr io.Writer, fmtr Format) Handler {
|
type Lazy struct {
|
||||||
h := FuncHandler(func(r *Record) error {
|
Fn interface{}
|
||||||
_, err := wr.Write(fmtr.Format(r))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return LazyHandler(SyncHandler(h))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncHandler can be wrapped around a handler to guarantee that
|
|
||||||
// only a single Log operation can proceed at a time. It's necessary
|
|
||||||
// for thread-safe concurrent writes.
|
|
||||||
func SyncHandler(h Handler) Handler {
|
|
||||||
var mu sync.Mutex
|
|
||||||
return FuncHandler(func(r *Record) error {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
|
|
||||||
return h.Log(r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileHandler returns a handler which writes log records to the give file
|
|
||||||
// using the given format. If the path
|
|
||||||
// already exists, FileHandler will append to the given file. If it does not,
|
|
||||||
// FileHandler will create the file with mode 0644.
|
|
||||||
func FileHandler(path string, fmtr Format) (Handler, error) {
|
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return closingHandler{f, StreamHandler(f, fmtr)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetHandler opens a socket to the given address and writes records
|
|
||||||
// over the connection.
|
|
||||||
func NetHandler(network, addr string, fmtr Format) (Handler, error) {
|
|
||||||
conn, err := net.Dial(network, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return closingHandler{conn, StreamHandler(conn, fmtr)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: closingHandler is essentially unused at the moment
|
|
||||||
// it's meant for a future time when the Handler interface supports
|
|
||||||
// a possible Close() operation
|
|
||||||
type closingHandler struct {
|
|
||||||
io.WriteCloser
|
|
||||||
Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *closingHandler) Close() error {
|
|
||||||
return h.WriteCloser.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CallerFileHandler returns a Handler that adds the line number and file of
|
|
||||||
// the calling function to the context with key "caller".
|
|
||||||
func CallerFileHandler(h Handler) Handler {
|
|
||||||
return FuncHandler(func(r *Record) error {
|
|
||||||
r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call))
|
|
||||||
return h.Log(r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CallerFuncHandler returns a Handler that adds the calling function name to
|
|
||||||
// the context with key "fn".
|
|
||||||
func CallerFuncHandler(h Handler) Handler {
|
|
||||||
return FuncHandler(func(r *Record) error {
|
|
||||||
r.Ctx = append(r.Ctx, "fn", formatCall("%+n", r.Call))
|
|
||||||
return h.Log(r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function is here to please go vet on Go < 1.8.
|
|
||||||
func formatCall(format string, c stack.Call) string {
|
|
||||||
return fmt.Sprintf(format, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CallerStackHandler returns a Handler that adds a stack trace to the context
|
|
||||||
// with key "stack". The stack trace is formatted as a space separated list of
|
|
||||||
// call sites inside matching []'s. The most recent call site is listed first.
|
|
||||||
// Each call site is formatted according to format. See the documentation of
|
|
||||||
// package github.com/go-stack/stack for the list of supported formats.
|
|
||||||
func CallerStackHandler(format string, h Handler) Handler {
|
|
||||||
return FuncHandler(func(r *Record) error {
|
|
||||||
s := stack.Trace().TrimBelow(r.Call).TrimRuntime()
|
|
||||||
if len(s) > 0 {
|
|
||||||
r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s))
|
|
||||||
}
|
|
||||||
return h.Log(r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterHandler returns a Handler that only writes records to the
|
|
||||||
// wrapped Handler if the given function evaluates true. For example,
|
|
||||||
// to only log records where the 'err' key is not nil:
|
|
||||||
//
|
|
||||||
// logger.SetHandler(FilterHandler(func(r *Record) bool {
|
|
||||||
// for i := 0; i < len(r.Ctx); i += 2 {
|
|
||||||
// if r.Ctx[i] == "err" {
|
|
||||||
// return r.Ctx[i+1] != nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return false
|
|
||||||
// }, h))
|
|
||||||
func FilterHandler(fn func(r *Record) bool, h Handler) Handler {
|
|
||||||
return FuncHandler(func(r *Record) error {
|
|
||||||
if fn(r) {
|
|
||||||
return h.Log(r)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchFilterHandler returns a Handler that only writes records
|
|
||||||
// to the wrapped Handler if the given key in the logged
|
|
||||||
// context matches the value. For example, to only log records
|
|
||||||
// from your ui package:
|
|
||||||
//
|
|
||||||
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
|
|
||||||
func MatchFilterHandler(key string, value interface{}, h Handler) Handler {
|
|
||||||
return FilterHandler(func(r *Record) (pass bool) {
|
|
||||||
switch key {
|
|
||||||
case r.KeyNames.Lvl:
|
|
||||||
return r.Lvl == value
|
|
||||||
case r.KeyNames.Time:
|
|
||||||
return r.Time == value
|
|
||||||
case r.KeyNames.Msg:
|
|
||||||
return r.Msg == value
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(r.Ctx); i += 2 {
|
|
||||||
if r.Ctx[i] == key {
|
|
||||||
return r.Ctx[i+1] == value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LvlFilterHandler returns a Handler that only writes
|
|
||||||
// records which are less than the given verbosity
|
|
||||||
// level to the wrapped Handler. For example, to only
|
|
||||||
// log Error/Crit records:
|
|
||||||
//
|
|
||||||
// log.LvlFilterHandler(log.LvlError, log.StdoutHandler)
|
|
||||||
func LvlFilterHandler(maxLvl Lvl, h Handler) Handler {
|
|
||||||
return FilterHandler(func(r *Record) (pass bool) {
|
|
||||||
return r.Lvl <= maxLvl
|
|
||||||
}, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiHandler dispatches any write to each of its handlers.
|
|
||||||
// This is useful for writing different types of log information
|
|
||||||
// to different locations. For example, to log to a file and
|
|
||||||
// standard error:
|
|
||||||
//
|
|
||||||
// log.MultiHandler(
|
|
||||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
|
||||||
// log.StderrHandler)
|
|
||||||
func MultiHandler(hs ...Handler) Handler {
|
|
||||||
return FuncHandler(func(r *Record) error {
|
|
||||||
for _, h := range hs {
|
|
||||||
// what to do about failures?
|
|
||||||
h.Log(r)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// FailoverHandler writes all log records to the first handler
|
|
||||||
// specified, but will failover and write to the second handler if
|
|
||||||
// the first handler has failed, and so on for all handlers specified.
|
|
||||||
// For example you might want to log to a network socket, but failover
|
|
||||||
// to writing to a file if the network fails, and then to
|
|
||||||
// standard out if the file write fails:
|
|
||||||
//
|
|
||||||
// log.FailoverHandler(
|
|
||||||
// log.Must.NetHandler("tcp", ":9090", log.JSONFormat()),
|
|
||||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
|
||||||
// log.StdoutHandler)
|
|
||||||
//
|
|
||||||
// All writes that do not go to the first handler will add context with keys of
|
|
||||||
// the form "failover_err_{idx}" which explain the error encountered while
|
|
||||||
// trying to write to the handlers before them in the list.
|
|
||||||
func FailoverHandler(hs ...Handler) Handler {
|
|
||||||
return FuncHandler(func(r *Record) error {
|
|
||||||
var err error
|
|
||||||
for i, h := range hs {
|
|
||||||
err = h.Log(r)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChannelHandler writes all records to the given channel.
|
|
||||||
// It blocks if the channel is full. Useful for async processing
|
|
||||||
// of log messages, it's used by BufferedHandler.
|
|
||||||
func ChannelHandler(recs chan<- *Record) Handler {
|
|
||||||
return FuncHandler(func(r *Record) error {
|
|
||||||
recs <- r
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufferedHandler writes all records to a buffered
|
|
||||||
// channel of the given size which flushes into the wrapped
|
|
||||||
// handler whenever it is available for writing. Since these
|
|
||||||
// writes happen asynchronously, all writes to a BufferedHandler
|
|
||||||
// never return an error and any errors from the wrapped handler are ignored.
|
|
||||||
func BufferedHandler(bufSize int, h Handler) Handler {
|
|
||||||
recs := make(chan *Record, bufSize)
|
|
||||||
go func() {
|
|
||||||
for m := range recs {
|
|
||||||
_ = h.Log(m)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return ChannelHandler(recs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LazyHandler writes all values to the wrapped handler after evaluating
|
|
||||||
// any lazy functions in the record's context. It is already wrapped
|
|
||||||
// around StreamHandler and SyslogHandler in this library, you'll only need
|
|
||||||
// it if you write your own Handler.
|
|
||||||
func LazyHandler(h Handler) Handler {
|
|
||||||
return FuncHandler(func(r *Record) error {
|
|
||||||
// go through the values (odd indices) and reassign
|
|
||||||
// the values of any lazy fn to the result of its execution
|
|
||||||
hadErr := false
|
|
||||||
for i := 1; i < len(r.Ctx); i += 2 {
|
|
||||||
lz, ok := r.Ctx[i].(Lazy)
|
|
||||||
if ok {
|
|
||||||
v, err := evaluateLazy(lz)
|
|
||||||
if err != nil {
|
|
||||||
hadErr = true
|
|
||||||
r.Ctx[i] = err
|
|
||||||
} else {
|
|
||||||
if cs, ok := v.(stack.CallStack); ok {
|
|
||||||
v = cs.TrimBelow(r.Call).TrimRuntime()
|
|
||||||
}
|
|
||||||
r.Ctx[i] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hadErr {
|
|
||||||
r.Ctx = append(r.Ctx, errorKey, "bad lazy")
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.Log(r)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateLazy(lz Lazy) (interface{}, error) {
|
func evaluateLazy(lz Lazy) (interface{}, error) {
|
||||||
|
|
@ -325,51 +49,175 @@ func evaluateLazy(lz Lazy) (interface{}, error) {
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscardHandler reports success for all writes but does nothing.
|
type discardHandler struct{}
|
||||||
// It is useful for dynamically disabling logging at runtime via
|
|
||||||
// a Logger's SetHandler method.
|
// DiscardHandler returns a no-op handler
|
||||||
func DiscardHandler() Handler {
|
func DiscardHandler() slog.Handler {
|
||||||
return FuncHandler(func(r *Record) error {
|
return &discardHandler{}
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
func (h *discardHandler) Handle(_ context.Context, r slog.Record) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *discardHandler) Enabled(_ context.Context, level slog.Level) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *discardHandler) WithGroup(name string) slog.Handler {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
return &discardHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TerminalHandler struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
wr io.Writer
|
||||||
|
lvl slog.Level
|
||||||
|
useColor bool
|
||||||
|
attrs []slog.Attr
|
||||||
|
// fieldPadding is a map with maximum field value lengths seen until now
|
||||||
|
// to allow padding log contexts in a bit smarter way.
|
||||||
|
fieldPadding map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTerminalHandler returns a handler which formats log records at all levels optimized for human readability on
|
||||||
|
// a terminal with color-coded level output and terser human friendly timestamp.
|
||||||
|
// This format should only be used for interactive programs or while developing.
|
||||||
|
//
|
||||||
|
// [LEVEL] [TIME] MESSAGE key=value key=value ...
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002
|
||||||
|
func NewTerminalHandler(wr io.Writer, useColor bool) *TerminalHandler {
|
||||||
|
return NewTerminalHandlerWithLevel(wr, levelMaxVerbosity, useColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTerminalHandlerWithLevel returns the same handler as NewTerminalHandler but only outputs
|
||||||
|
// records which are less than or equal to the specified verbosity level.
|
||||||
|
func NewTerminalHandlerWithLevel(wr io.Writer, lvl slog.Level, useColor bool) *TerminalHandler {
|
||||||
|
return &TerminalHandler{
|
||||||
|
wr: wr,
|
||||||
|
lvl: lvl,
|
||||||
|
useColor: useColor,
|
||||||
|
fieldPadding: make(map[string]int),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TerminalHandler) Handle(_ context.Context, r slog.Record) error {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
h.wr.Write(h.TerminalFormat(r, h.useColor))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TerminalHandler) Enabled(_ context.Context, level slog.Level) bool {
|
||||||
|
return level >= h.lvl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TerminalHandler) WithGroup(name string) slog.Handler {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
return &TerminalHandler{
|
||||||
|
wr: h.wr,
|
||||||
|
lvl: h.lvl,
|
||||||
|
useColor: h.useColor,
|
||||||
|
attrs: append(h.attrs, attrs...),
|
||||||
|
fieldPadding: make(map[string]int),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetFieldPadding zeroes the field-padding for all attribute pairs.
|
||||||
|
func (t *TerminalHandler) ResetFieldPadding() {
|
||||||
|
t.mu.Lock()
|
||||||
|
t.fieldPadding = make(map[string]int)
|
||||||
|
t.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
type leveler struct{ minLevel slog.Level }
|
||||||
|
|
||||||
|
func (l *leveler) Level() slog.Level {
|
||||||
|
return l.minLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func JSONHandler(wr io.Writer) slog.Handler {
|
||||||
|
return slog.NewJSONHandler(wr, &slog.HandlerOptions{
|
||||||
|
ReplaceAttr: builtinReplaceJSON,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must provides the following Handler creation functions
|
// LogfmtHandler returns a handler which prints records in logfmt format, an easy machine-parseable but human-readable
|
||||||
// which instead of returning an error parameter only return a Handler
|
// format for key/value pairs.
|
||||||
// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler
|
//
|
||||||
var Must muster
|
// For more details see: http://godoc.org/github.com/kr/logfmt
|
||||||
|
func LogfmtHandler(wr io.Writer) slog.Handler {
|
||||||
|
return slog.NewTextHandler(wr, &slog.HandlerOptions{
|
||||||
|
ReplaceAttr: builtinReplaceLogfmt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func must(h Handler, err error) Handler {
|
// LogfmtHandlerWithLevel returns the same handler as LogfmtHandler but it only outputs
|
||||||
if err != nil {
|
// records which are less than or equal to the specified verbosity level.
|
||||||
panic(err)
|
func LogfmtHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler {
|
||||||
|
return slog.NewTextHandler(wr, &slog.HandlerOptions{
|
||||||
|
ReplaceAttr: builtinReplaceLogfmt,
|
||||||
|
Level: &leveler{level},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinReplaceLogfmt(_ []string, attr slog.Attr) slog.Attr {
|
||||||
|
return builtinReplace(nil, attr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinReplaceJSON(_ []string, attr slog.Attr) slog.Attr {
|
||||||
|
return builtinReplace(nil, attr, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinReplace(_ []string, attr slog.Attr, logfmt bool) slog.Attr {
|
||||||
|
switch attr.Key {
|
||||||
|
case slog.TimeKey:
|
||||||
|
if attr.Value.Kind() == slog.KindTime {
|
||||||
|
if logfmt {
|
||||||
|
return slog.String("t", attr.Value.Time().Format(timeFormat))
|
||||||
|
} else {
|
||||||
|
return slog.Attr{Key: "t", Value: attr.Value}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case slog.LevelKey:
|
||||||
|
if l, ok := attr.Value.Any().(slog.Level); ok {
|
||||||
|
attr = slog.Any("lvl", LevelString(l))
|
||||||
|
return attr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
type muster struct{}
|
switch v := attr.Value.Any().(type) {
|
||||||
|
case time.Time:
|
||||||
func (m muster) FileHandler(path string, fmtr Format) Handler {
|
if logfmt {
|
||||||
return must(FileHandler(path, fmtr))
|
attr = slog.String(attr.Key, v.Format(timeFormat))
|
||||||
}
|
}
|
||||||
|
case *big.Int:
|
||||||
func (m muster) NetHandler(network, addr string, fmtr Format) Handler {
|
if v == nil {
|
||||||
return must(NetHandler(network, addr, fmtr))
|
attr.Value = slog.StringValue("<nil>")
|
||||||
}
|
} else {
|
||||||
|
attr.Value = slog.StringValue(v.String())
|
||||||
// swapHandler wraps another handler that may be swapped out
|
}
|
||||||
// dynamically at runtime in a thread-safe fashion.
|
case *uint256.Int:
|
||||||
type swapHandler struct {
|
if v == nil {
|
||||||
handler atomic.Value
|
attr.Value = slog.StringValue("<nil>")
|
||||||
}
|
} else {
|
||||||
|
attr.Value = slog.StringValue(v.Dec())
|
||||||
func (h *swapHandler) Log(r *Record) error {
|
}
|
||||||
return (*h.handler.Load().(*Handler)).Log(r)
|
case fmt.Stringer:
|
||||||
}
|
if v == nil || (reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil()) {
|
||||||
|
attr.Value = slog.StringValue("<nil>")
|
||||||
func (h *swapHandler) Swap(newHandler Handler) {
|
} else {
|
||||||
h.handler.Store(&newHandler)
|
attr.Value = slog.StringValue(v.String())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
func (h *swapHandler) Get() Handler {
|
return attr
|
||||||
return *h.handler.Load().(*Handler)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -25,54 +26,47 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// errVmoduleSyntax is returned when a user vmodule pattern is invalid.
|
// errVmoduleSyntax is returned when a user vmodule pattern is invalid.
|
||||||
var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N")
|
var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N")
|
||||||
|
|
||||||
// errTraceSyntax is returned when a user backtrace pattern is invalid.
|
|
||||||
var errTraceSyntax = errors.New("expect file.go:234")
|
|
||||||
|
|
||||||
// GlogHandler is a log handler that mimics the filtering features of Google's
|
// GlogHandler is a log handler that mimics the filtering features of Google's
|
||||||
// glog logger: setting global log levels; overriding with callsite pattern
|
// glog logger: setting global log levels; overriding with callsite pattern
|
||||||
// matches; and requesting backtraces at certain positions.
|
// matches; and requesting backtraces at certain positions.
|
||||||
type GlogHandler struct {
|
type GlogHandler struct {
|
||||||
origin Handler // The origin handler this wraps
|
origin slog.Handler // The origin handler this wraps
|
||||||
|
|
||||||
level atomic.Uint32 // Current log level, atomically accessible
|
level atomic.Int32 // Current log level, atomically accessible
|
||||||
override atomic.Bool // Flag whether overrides are used, atomically accessible
|
override atomic.Bool // Flag whether overrides are used, atomically accessible
|
||||||
backtrace atomic.Bool // Flag whether backtrace location is set
|
|
||||||
|
|
||||||
patterns []pattern // Current list of patterns to override with
|
patterns []pattern // Current list of patterns to override with
|
||||||
siteCache map[uintptr]Lvl // Cache of callsite pattern evaluations
|
siteCache map[uintptr]slog.Level // Cache of callsite pattern evaluations
|
||||||
location string // file:line location where to do a stackdump at
|
location string // file:line location where to do a stackdump at
|
||||||
lock sync.RWMutex // Lock protecting the override pattern list
|
lock sync.RWMutex // Lock protecting the override pattern list
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGlogHandler creates a new log handler with filtering functionality similar
|
// NewGlogHandler creates a new log handler with filtering functionality similar
|
||||||
// to Google's glog logger. The returned handler implements Handler.
|
// to Google's glog logger. The returned handler implements Handler.
|
||||||
func NewGlogHandler(h Handler) *GlogHandler {
|
func NewGlogHandler(h slog.Handler) *GlogHandler {
|
||||||
return &GlogHandler{
|
return &GlogHandler{
|
||||||
origin: h,
|
origin: h,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHandler updates the handler to write records to the specified sub-handler.
|
|
||||||
func (h *GlogHandler) SetHandler(nh Handler) {
|
|
||||||
h.origin = nh
|
|
||||||
}
|
|
||||||
|
|
||||||
// pattern contains a filter for the Vmodule option, holding a verbosity level
|
// pattern contains a filter for the Vmodule option, holding a verbosity level
|
||||||
// and a file pattern to match.
|
// and a file pattern to match.
|
||||||
type pattern struct {
|
type pattern struct {
|
||||||
pattern *regexp.Regexp
|
pattern *regexp.Regexp
|
||||||
level Lvl
|
level slog.Level
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
|
// Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
|
||||||
// and source files can be raised using Vmodule.
|
// and source files can be raised using Vmodule.
|
||||||
func (h *GlogHandler) Verbosity(level Lvl) {
|
func (h *GlogHandler) Verbosity(level slog.Level) {
|
||||||
h.level.Store(uint32(level))
|
h.level.Store(int32(level))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vmodule sets the glog verbosity pattern.
|
// Vmodule sets the glog verbosity pattern.
|
||||||
|
|
@ -108,11 +102,13 @@ func (h *GlogHandler) Vmodule(ruleset string) error {
|
||||||
return errVmoduleSyntax
|
return errVmoduleSyntax
|
||||||
}
|
}
|
||||||
// Parse the level and if correct, assemble the filter rule
|
// Parse the level and if correct, assemble the filter rule
|
||||||
level, err := strconv.Atoi(parts[1])
|
l, err := strconv.Atoi(parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errVmoduleSyntax
|
return errVmoduleSyntax
|
||||||
}
|
}
|
||||||
if level <= 0 {
|
level := FromLegacyLevel(l)
|
||||||
|
|
||||||
|
if level == LevelCrit {
|
||||||
continue // Ignore. It's harmless but no point in paying the overhead.
|
continue // Ignore. It's harmless but no point in paying the overhead.
|
||||||
}
|
}
|
||||||
// Compile the rule pattern into a regular expression
|
// Compile the rule pattern into a regular expression
|
||||||
|
|
@ -130,107 +126,84 @@ func (h *GlogHandler) Vmodule(ruleset string) error {
|
||||||
matcher = matcher + "$"
|
matcher = matcher + "$"
|
||||||
|
|
||||||
re, _ := regexp.Compile(matcher)
|
re, _ := regexp.Compile(matcher)
|
||||||
filter = append(filter, pattern{re, Lvl(level)})
|
filter = append(filter, pattern{re, level})
|
||||||
}
|
}
|
||||||
// Swap out the vmodule pattern for the new filter system
|
// Swap out the vmodule pattern for the new filter system
|
||||||
h.lock.Lock()
|
h.lock.Lock()
|
||||||
defer h.lock.Unlock()
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
h.patterns = filter
|
h.patterns = filter
|
||||||
h.siteCache = make(map[uintptr]Lvl)
|
h.siteCache = make(map[uintptr]slog.Level)
|
||||||
h.override.Store(len(filter) != 0)
|
h.override.Store(len(filter) != 0)
|
||||||
// Enable location storage (globally)
|
|
||||||
if len(h.patterns) > 0 {
|
|
||||||
stackEnabled.Store(true)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BacktraceAt sets the glog backtrace location. When set to a file and line
|
func (h *GlogHandler) Enabled(ctx context.Context, lvl slog.Level) bool {
|
||||||
// number holding a logging statement, a stack trace will be written to the Info
|
// fast-track skipping logging if override not enabled and the provided verbosity is above configured
|
||||||
// log whenever execution hits that statement.
|
return h.override.Load() || slog.Level(h.level.Load()) <= lvl
|
||||||
//
|
}
|
||||||
// Unlike with Vmodule, the ".go" must be present.
|
|
||||||
func (h *GlogHandler) BacktraceAt(location string) error {
|
|
||||||
// Ensure the backtrace location contains two non-empty elements
|
|
||||||
parts := strings.Split(location, ":")
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return errTraceSyntax
|
|
||||||
}
|
|
||||||
parts[0] = strings.TrimSpace(parts[0])
|
|
||||||
parts[1] = strings.TrimSpace(parts[1])
|
|
||||||
if len(parts[0]) == 0 || len(parts[1]) == 0 {
|
|
||||||
return errTraceSyntax
|
|
||||||
}
|
|
||||||
// Ensure the .go prefix is present and the line is valid
|
|
||||||
if !strings.HasSuffix(parts[0], ".go") {
|
|
||||||
return errTraceSyntax
|
|
||||||
}
|
|
||||||
if _, err := strconv.Atoi(parts[1]); err != nil {
|
|
||||||
return errTraceSyntax
|
|
||||||
}
|
|
||||||
// All seems valid
|
|
||||||
h.lock.Lock()
|
|
||||||
defer h.lock.Unlock()
|
|
||||||
|
|
||||||
h.location = location
|
func (h *GlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
h.backtrace.Store(len(location) > 0)
|
h.lock.RLock()
|
||||||
// Enable location storage (globally)
|
siteCache := make(map[uintptr]slog.Level)
|
||||||
stackEnabled.Store(true)
|
for k, v := range h.siteCache {
|
||||||
return nil
|
siteCache[k] = v
|
||||||
|
}
|
||||||
|
h.lock.RUnlock()
|
||||||
|
|
||||||
|
patterns := []pattern{}
|
||||||
|
patterns = append(patterns, h.patterns...)
|
||||||
|
|
||||||
|
res := GlogHandler{
|
||||||
|
origin: h.origin.WithAttrs(attrs),
|
||||||
|
patterns: patterns,
|
||||||
|
siteCache: siteCache,
|
||||||
|
location: h.location,
|
||||||
|
}
|
||||||
|
|
||||||
|
res.level.Store(h.level.Load())
|
||||||
|
res.override.Store(h.override.Load())
|
||||||
|
return &res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *GlogHandler) WithGroup(name string) slog.Handler {
|
||||||
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log implements Handler.Log, filtering a log record through the global, local
|
// Log implements Handler.Log, filtering a log record through the global, local
|
||||||
// and backtrace filters, finally emitting it if either allow it through.
|
// and backtrace filters, finally emitting it if either allow it through.
|
||||||
func (h *GlogHandler) Log(r *Record) error {
|
func (h *GlogHandler) Handle(_ context.Context, r slog.Record) error {
|
||||||
// If backtracing is requested, check whether this is the callsite
|
|
||||||
if h.backtrace.Load() {
|
|
||||||
// Everything below here is slow. Although we could cache the call sites the
|
|
||||||
// same way as for vmodule, backtracing is so rare it's not worth the extra
|
|
||||||
// complexity.
|
|
||||||
h.lock.RLock()
|
|
||||||
match := h.location == r.Call.String()
|
|
||||||
h.lock.RUnlock()
|
|
||||||
|
|
||||||
if match {
|
|
||||||
// Callsite matched, raise the log level to info and gather the stacks
|
|
||||||
r.Lvl = LvlInfo
|
|
||||||
|
|
||||||
buf := make([]byte, 1024*1024)
|
|
||||||
buf = buf[:runtime.Stack(buf, true)]
|
|
||||||
r.Msg += "\n\n" + string(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the global log level allows, fast track logging
|
// If the global log level allows, fast track logging
|
||||||
if h.level.Load() >= uint32(r.Lvl) {
|
if slog.Level(h.level.Load()) <= r.Level {
|
||||||
return h.origin.Log(r)
|
return h.origin.Handle(context.Background(), r)
|
||||||
}
|
|
||||||
// If no local overrides are present, fast track skipping
|
|
||||||
if !h.override.Load() {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check callsite cache for previously calculated log levels
|
// Check callsite cache for previously calculated log levels
|
||||||
h.lock.RLock()
|
h.lock.RLock()
|
||||||
lvl, ok := h.siteCache[r.Call.Frame().PC]
|
lvl, ok := h.siteCache[r.PC]
|
||||||
h.lock.RUnlock()
|
h.lock.RUnlock()
|
||||||
|
|
||||||
// If we didn't cache the callsite yet, calculate it
|
// If we didn't cache the callsite yet, calculate it
|
||||||
if !ok {
|
if !ok {
|
||||||
h.lock.Lock()
|
h.lock.Lock()
|
||||||
|
|
||||||
|
fs := runtime.CallersFrames([]uintptr{r.PC})
|
||||||
|
frame, _ := fs.Next()
|
||||||
|
|
||||||
for _, rule := range h.patterns {
|
for _, rule := range h.patterns {
|
||||||
if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) {
|
if rule.pattern.MatchString(fmt.Sprintf("%+s", frame.File)) {
|
||||||
h.siteCache[r.Call.Frame().PC], lvl, ok = rule.level, rule.level, true
|
h.siteCache[r.PC], lvl, ok = rule.level, rule.level, true
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If no rule matched, remember to drop log the next time
|
// If no rule matched, remember to drop log the next time
|
||||||
if !ok {
|
if !ok {
|
||||||
h.siteCache[r.Call.Frame().PC] = 0
|
h.siteCache[r.PC] = 0
|
||||||
}
|
}
|
||||||
h.lock.Unlock()
|
h.lock.Unlock()
|
||||||
}
|
}
|
||||||
if lvl >= r.Lvl {
|
if lvl <= r.Level {
|
||||||
return h.origin.Log(r)
|
return h.origin.Handle(context.Background(), r)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
380
log/logger.go
380
log/logger.go
|
|
@ -1,294 +1,222 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-stack/stack"
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const timeKey = "t"
|
const errorKey = "LOG_ERROR"
|
||||||
const lvlKey = "lvl"
|
|
||||||
const msgKey = "msg"
|
|
||||||
const ctxKey = "ctx"
|
|
||||||
const errorKey = "LOG15_ERROR"
|
|
||||||
const skipLevel = 2
|
|
||||||
|
|
||||||
type Lvl int
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LvlCrit Lvl = iota
|
legacyLevelCrit = iota
|
||||||
LvlError
|
legacyLevelError
|
||||||
LvlWarn
|
legacyLevelWarn
|
||||||
LvlInfo
|
legacyLevelInfo
|
||||||
LvlDebug
|
legacyLevelDebug
|
||||||
LvlTrace
|
legacyLevelTrace
|
||||||
)
|
)
|
||||||
|
|
||||||
// AlignedString returns a 5-character string containing the name of a Lvl.
|
const (
|
||||||
func (l Lvl) AlignedString() string {
|
levelMaxVerbosity slog.Level = math.MinInt
|
||||||
|
LevelTrace slog.Level = -8
|
||||||
|
LevelDebug = slog.LevelDebug
|
||||||
|
LevelInfo = slog.LevelInfo
|
||||||
|
LevelWarn = slog.LevelWarn
|
||||||
|
LevelError = slog.LevelError
|
||||||
|
LevelCrit slog.Level = 12
|
||||||
|
|
||||||
|
// for backward-compatibility
|
||||||
|
LvlTrace = LevelTrace
|
||||||
|
LvlInfo = LevelInfo
|
||||||
|
LvlDebug = LevelDebug
|
||||||
|
)
|
||||||
|
|
||||||
|
// convert from old Geth verbosity level constants
|
||||||
|
// to levels defined by slog
|
||||||
|
func FromLegacyLevel(lvl int) slog.Level {
|
||||||
|
switch lvl {
|
||||||
|
case legacyLevelCrit:
|
||||||
|
return LevelCrit
|
||||||
|
case legacyLevelError:
|
||||||
|
return slog.LevelError
|
||||||
|
case legacyLevelWarn:
|
||||||
|
return slog.LevelWarn
|
||||||
|
case legacyLevelInfo:
|
||||||
|
return slog.LevelInfo
|
||||||
|
case legacyLevelDebug:
|
||||||
|
return slog.LevelDebug
|
||||||
|
case legacyLevelTrace:
|
||||||
|
return LevelTrace
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should we allow use of custom levels or force them to match existing max/min if they fall outside the range as I am doing here?
|
||||||
|
if lvl > legacyLevelTrace {
|
||||||
|
return LevelTrace
|
||||||
|
}
|
||||||
|
return LevelCrit
|
||||||
|
}
|
||||||
|
|
||||||
|
// LevelAlignedString returns a 5-character string containing the name of a Lvl.
|
||||||
|
func LevelAlignedString(l slog.Level) string {
|
||||||
switch l {
|
switch l {
|
||||||
case LvlTrace:
|
case LevelTrace:
|
||||||
return "TRACE"
|
return "TRACE"
|
||||||
case LvlDebug:
|
case slog.LevelDebug:
|
||||||
return "DEBUG"
|
return "DEBUG"
|
||||||
case LvlInfo:
|
case slog.LevelInfo:
|
||||||
return "INFO "
|
return "INFO "
|
||||||
case LvlWarn:
|
case slog.LevelWarn:
|
||||||
return "WARN "
|
return "WARN "
|
||||||
case LvlError:
|
case slog.LevelError:
|
||||||
return "ERROR"
|
return "ERROR"
|
||||||
case LvlCrit:
|
case LevelCrit:
|
||||||
return "CRIT "
|
return "CRIT "
|
||||||
default:
|
default:
|
||||||
panic("bad level")
|
return "unknown level"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the name of a Lvl.
|
// LevelString returns a 5-character string containing the name of a Lvl.
|
||||||
func (l Lvl) String() string {
|
func LevelString(l slog.Level) string {
|
||||||
switch l {
|
switch l {
|
||||||
case LvlTrace:
|
case LevelTrace:
|
||||||
return "trce"
|
return "trace"
|
||||||
case LvlDebug:
|
case slog.LevelDebug:
|
||||||
return "dbug"
|
return "debug"
|
||||||
case LvlInfo:
|
case slog.LevelInfo:
|
||||||
return "info"
|
return "info"
|
||||||
case LvlWarn:
|
case slog.LevelWarn:
|
||||||
return "warn"
|
return "warn"
|
||||||
case LvlError:
|
case slog.LevelError:
|
||||||
return "eror"
|
return "eror"
|
||||||
case LvlCrit:
|
case LevelCrit:
|
||||||
return "crit"
|
return "crit"
|
||||||
default:
|
default:
|
||||||
panic("bad level")
|
return "unknown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LvlFromString returns the appropriate Lvl from a string name.
|
|
||||||
// Useful for parsing command line args and configuration files.
|
|
||||||
func LvlFromString(lvlString string) (Lvl, error) {
|
|
||||||
switch lvlString {
|
|
||||||
case "trace", "trce":
|
|
||||||
return LvlTrace, nil
|
|
||||||
case "debug", "dbug":
|
|
||||||
return LvlDebug, nil
|
|
||||||
case "info":
|
|
||||||
return LvlInfo, nil
|
|
||||||
case "warn":
|
|
||||||
return LvlWarn, nil
|
|
||||||
case "error", "eror":
|
|
||||||
return LvlError, nil
|
|
||||||
case "crit":
|
|
||||||
return LvlCrit, nil
|
|
||||||
default:
|
|
||||||
return LvlDebug, fmt.Errorf("unknown level: %v", lvlString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Record is what a Logger asks its handler to write
|
|
||||||
type Record struct {
|
|
||||||
Time time.Time
|
|
||||||
Lvl Lvl
|
|
||||||
Msg string
|
|
||||||
Ctx []interface{}
|
|
||||||
Call stack.Call
|
|
||||||
KeyNames RecordKeyNames
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordKeyNames gets stored in a Record when the write function is executed.
|
|
||||||
type RecordKeyNames struct {
|
|
||||||
Time string
|
|
||||||
Msg string
|
|
||||||
Lvl string
|
|
||||||
Ctx string
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Logger writes key/value pairs to a Handler
|
// A Logger writes key/value pairs to a Handler
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
// New returns a new Logger that has this logger's context plus the given context
|
// With returns a new Logger that has this logger's attributes plus the given attributes
|
||||||
|
With(ctx ...interface{}) Logger
|
||||||
|
|
||||||
|
// With returns a new Logger that has this logger's attributes plus the given attributes. Identical to 'With'.
|
||||||
New(ctx ...interface{}) Logger
|
New(ctx ...interface{}) Logger
|
||||||
|
|
||||||
// GetHandler gets the handler associated with the logger.
|
// Log logs a message at the specified level with context key/value pairs
|
||||||
GetHandler() Handler
|
Log(level slog.Level, msg string, ctx ...interface{})
|
||||||
|
|
||||||
// SetHandler updates the logger to write records to the specified handler.
|
// Trace log a message at the trace level with context key/value pairs
|
||||||
SetHandler(h Handler)
|
|
||||||
|
|
||||||
// Log a message at the trace level with context key/value pairs
|
|
||||||
//
|
|
||||||
// # Usage
|
|
||||||
//
|
|
||||||
// log.Trace("msg")
|
|
||||||
// log.Trace("msg", "key1", val1)
|
|
||||||
// log.Trace("msg", "key1", val1, "key2", val2)
|
|
||||||
Trace(msg string, ctx ...interface{})
|
Trace(msg string, ctx ...interface{})
|
||||||
|
|
||||||
// Log a message at the debug level with context key/value pairs
|
// Debug logs a message at the debug level with context key/value pairs
|
||||||
//
|
|
||||||
// # Usage Examples
|
|
||||||
//
|
|
||||||
// log.Debug("msg")
|
|
||||||
// log.Debug("msg", "key1", val1)
|
|
||||||
// log.Debug("msg", "key1", val1, "key2", val2)
|
|
||||||
Debug(msg string, ctx ...interface{})
|
Debug(msg string, ctx ...interface{})
|
||||||
|
|
||||||
// Log a message at the info level with context key/value pairs
|
// Info logs a message at the info level with context key/value pairs
|
||||||
//
|
|
||||||
// # Usage Examples
|
|
||||||
//
|
|
||||||
// log.Info("msg")
|
|
||||||
// log.Info("msg", "key1", val1)
|
|
||||||
// log.Info("msg", "key1", val1, "key2", val2)
|
|
||||||
Info(msg string, ctx ...interface{})
|
Info(msg string, ctx ...interface{})
|
||||||
|
|
||||||
// Log a message at the warn level with context key/value pairs
|
// Warn logs a message at the warn level with context key/value pairs
|
||||||
//
|
|
||||||
// # Usage Examples
|
|
||||||
//
|
|
||||||
// log.Warn("msg")
|
|
||||||
// log.Warn("msg", "key1", val1)
|
|
||||||
// log.Warn("msg", "key1", val1, "key2", val2)
|
|
||||||
Warn(msg string, ctx ...interface{})
|
Warn(msg string, ctx ...interface{})
|
||||||
|
|
||||||
// Log a message at the error level with context key/value pairs
|
// Error logs a message at the error level with context key/value pairs
|
||||||
//
|
|
||||||
// # Usage Examples
|
|
||||||
//
|
|
||||||
// log.Error("msg")
|
|
||||||
// log.Error("msg", "key1", val1)
|
|
||||||
// log.Error("msg", "key1", val1, "key2", val2)
|
|
||||||
Error(msg string, ctx ...interface{})
|
Error(msg string, ctx ...interface{})
|
||||||
|
|
||||||
// Log a message at the crit level with context key/value pairs, and then exit.
|
// Crit logs a message at the crit level with context key/value pairs, and exits
|
||||||
//
|
|
||||||
// # Usage Examples
|
|
||||||
//
|
|
||||||
// log.Crit("msg")
|
|
||||||
// log.Crit("msg", "key1", val1)
|
|
||||||
// log.Crit("msg", "key1", val1, "key2", val2)
|
|
||||||
Crit(msg string, ctx ...interface{})
|
Crit(msg string, ctx ...interface{})
|
||||||
|
|
||||||
|
// Write logs a message at the specified level
|
||||||
|
Write(level slog.Level, msg string, attrs ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
type logger struct {
|
type logger struct {
|
||||||
ctx []interface{}
|
inner *slog.Logger
|
||||||
h *swapHandler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logger) write(msg string, lvl Lvl, ctx []interface{}, skip int) {
|
// NewLogger returns a logger with the specified handler set
|
||||||
record := &Record{
|
func NewLogger(h slog.Handler) Logger {
|
||||||
Time: time.Now(),
|
return &logger{
|
||||||
Lvl: lvl,
|
slog.New(h),
|
||||||
Msg: msg,
|
|
||||||
Ctx: newContext(l.ctx, ctx),
|
|
||||||
KeyNames: RecordKeyNames{
|
|
||||||
Time: timeKey,
|
|
||||||
Msg: msgKey,
|
|
||||||
Lvl: lvlKey,
|
|
||||||
Ctx: ctxKey,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if stackEnabled.Load() {
|
}
|
||||||
record.Call = stack.Caller(skip)
|
|
||||||
|
// write logs a message at the specified level:
|
||||||
|
func (l *logger) Write(level slog.Level, msg string, attrs ...any) {
|
||||||
|
if !l.inner.Enabled(context.Background(), level) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
l.h.Log(record)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) New(ctx ...interface{}) Logger {
|
var pcs [1]uintptr
|
||||||
child := &logger{newContext(l.ctx, ctx), new(swapHandler)}
|
runtime.Callers(3, pcs[:])
|
||||||
child.SetHandler(l.h)
|
|
||||||
return child
|
|
||||||
}
|
|
||||||
|
|
||||||
func newContext(prefix []interface{}, suffix []interface{}) []interface{} {
|
if len(attrs)%2 != 0 {
|
||||||
normalizedSuffix := normalize(suffix)
|
attrs = append(attrs, nil, errorKey, "Normalized odd number of arguments by adding nil")
|
||||||
newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix))
|
}
|
||||||
n := copy(newCtx, prefix)
|
|
||||||
copy(newCtx[n:], normalizedSuffix)
|
|
||||||
return newCtx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Trace(msg string, ctx ...interface{}) {
|
// evaluate lazy values
|
||||||
l.write(msg, LvlTrace, ctx, skipLevel)
|
var hadErr bool
|
||||||
}
|
for i := 1; i < len(attrs); i += 2 {
|
||||||
|
lz, ok := attrs[i].(Lazy)
|
||||||
func (l *logger) Debug(msg string, ctx ...interface{}) {
|
if ok {
|
||||||
l.write(msg, LvlDebug, ctx, skipLevel)
|
v, err := evaluateLazy(lz)
|
||||||
}
|
if err != nil {
|
||||||
|
hadErr = true
|
||||||
func (l *logger) Info(msg string, ctx ...interface{}) {
|
attrs[i] = err
|
||||||
l.write(msg, LvlInfo, ctx, skipLevel)
|
} else {
|
||||||
}
|
attrs[i] = v
|
||||||
|
}
|
||||||
func (l *logger) Warn(msg string, ctx ...interface{}) {
|
|
||||||
l.write(msg, LvlWarn, ctx, skipLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Error(msg string, ctx ...interface{}) {
|
|
||||||
l.write(msg, LvlError, ctx, skipLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Crit(msg string, ctx ...interface{}) {
|
|
||||||
l.write(msg, LvlCrit, ctx, skipLevel)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) GetHandler() Handler {
|
|
||||||
return l.h.Get()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) SetHandler(h Handler) {
|
|
||||||
l.h.Swap(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalize(ctx []interface{}) []interface{} {
|
|
||||||
// if the caller passed a Ctx object, then expand it
|
|
||||||
if len(ctx) == 1 {
|
|
||||||
if ctxMap, ok := ctx[0].(Ctx); ok {
|
|
||||||
ctx = ctxMap.toArray()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ctx needs to be even because it's a series of key/value pairs
|
if hadErr {
|
||||||
// no one wants to check for errors on logging functions,
|
attrs = append(attrs, errorKey, "bad lazy")
|
||||||
// so instead of erroring on bad input, we'll just make sure
|
|
||||||
// that things are the right length and users can fix bugs
|
|
||||||
// when they see the output looks wrong
|
|
||||||
if len(ctx)%2 != 0 {
|
|
||||||
ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx
|
r := slog.NewRecord(time.Now(), level, msg, pcs[0])
|
||||||
|
r.Add(attrs...)
|
||||||
|
l.inner.Handler().Handle(context.Background(), r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lazy allows you to defer calculation of a logged value that is expensive
|
func (l *logger) Log(level slog.Level, msg string, attrs ...any) {
|
||||||
// to compute until it is certain that it must be evaluated with the given filters.
|
l.Write(level, msg, attrs...)
|
||||||
//
|
|
||||||
// Lazy may also be used in conjunction with a Logger's New() function
|
|
||||||
// to generate a child logger which always reports the current value of changing
|
|
||||||
// state.
|
|
||||||
//
|
|
||||||
// You may wrap any function which takes no arguments to Lazy. It may return any
|
|
||||||
// number of values of any type.
|
|
||||||
type Lazy struct {
|
|
||||||
Fn interface{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctx is a map of key/value pairs to pass as context to a log function
|
func (l *logger) With(ctx ...interface{}) Logger {
|
||||||
// Use this only if you really need greater safety around the arguments you pass
|
return &logger{l.inner.With(ctx...)}
|
||||||
// to the logging functions.
|
}
|
||||||
type Ctx map[string]interface{}
|
|
||||||
|
func (l *logger) New(ctx ...interface{}) Logger {
|
||||||
func (c Ctx) toArray() []interface{} {
|
return l.With(ctx...)
|
||||||
arr := make([]interface{}, len(c)*2)
|
}
|
||||||
|
|
||||||
i := 0
|
func (l *logger) Trace(msg string, ctx ...interface{}) {
|
||||||
for k, v := range c {
|
l.Write(LevelTrace, msg, ctx...)
|
||||||
arr[i] = k
|
}
|
||||||
arr[i+1] = v
|
|
||||||
i += 2
|
func (l *logger) Debug(msg string, ctx ...interface{}) {
|
||||||
}
|
l.Write(slog.LevelDebug, msg, ctx...)
|
||||||
|
}
|
||||||
return arr
|
|
||||||
|
func (l *logger) Info(msg string, ctx ...interface{}) {
|
||||||
|
l.Write(slog.LevelInfo, msg, ctx...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Warn(msg string, ctx ...any) {
|
||||||
|
l.Write(slog.LevelWarn, msg, ctx...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Error(msg string, ctx ...interface{}) {
|
||||||
|
l.Write(slog.LevelError, msg, ctx...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Crit(msg string, ctx ...interface{}) {
|
||||||
|
l.Write(LevelCrit, msg, ctx...)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,48 +5,18 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
|
||||||
|
|
||||||
// TestLoggingWithTrace checks that if BackTraceAt is set, then the
|
"golang.org/x/exp/slog"
|
||||||
// gloghandler is capable of spitting out a stacktrace
|
)
|
||||||
func TestLoggingWithTrace(t *testing.T) {
|
|
||||||
defer stackEnabled.Store(stackEnabled.Load())
|
|
||||||
out := new(bytes.Buffer)
|
|
||||||
logger := New()
|
|
||||||
{
|
|
||||||
glog := NewGlogHandler(StreamHandler(out, TerminalFormat(false)))
|
|
||||||
glog.Verbosity(LvlTrace)
|
|
||||||
if err := glog.BacktraceAt("logger_test.go:24"); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
logger.SetHandler(glog)
|
|
||||||
}
|
|
||||||
logger.Trace("a message", "foo", "bar") // Will be bumped to INFO
|
|
||||||
have := out.String()
|
|
||||||
if !strings.HasPrefix(have, "INFO") {
|
|
||||||
t.Fatalf("backtraceat should bump level to info: %s", have)
|
|
||||||
}
|
|
||||||
// The timestamp is locale-dependent, so we want to trim that off
|
|
||||||
// "INFO [01-01|00:00:00.000] a messag ..." -> "a messag..."
|
|
||||||
have = strings.Split(have, "]")[1]
|
|
||||||
wantPrefix := " a message\n\ngoroutine"
|
|
||||||
if !strings.HasPrefix(have, wantPrefix) {
|
|
||||||
t.Errorf("\nhave: %q\nwant: %q\n", have, wantPrefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestLoggingWithVmodule checks that vmodule works.
|
// TestLoggingWithVmodule checks that vmodule works.
|
||||||
func TestLoggingWithVmodule(t *testing.T) {
|
func TestLoggingWithVmodule(t *testing.T) {
|
||||||
defer stackEnabled.Store(stackEnabled.Load())
|
|
||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
logger := New()
|
glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false))
|
||||||
{
|
glog.Verbosity(LevelCrit)
|
||||||
glog := NewGlogHandler(StreamHandler(out, TerminalFormat(false)))
|
logger := NewLogger(glog)
|
||||||
glog.Verbosity(LvlCrit)
|
logger.Warn("This should not be seen", "ignored", "true")
|
||||||
logger.SetHandler(glog)
|
glog.Vmodule("logger_test.go=5")
|
||||||
logger.Warn("This should not be seen", "ignored", "true")
|
|
||||||
glog.Vmodule("logger_test.go=5")
|
|
||||||
}
|
|
||||||
logger.Trace("a message", "foo", "bar")
|
logger.Trace("a message", "foo", "bar")
|
||||||
have := out.String()
|
have := out.String()
|
||||||
// The timestamp is locale-dependent, so we want to trim that off
|
// The timestamp is locale-dependent, so we want to trim that off
|
||||||
|
|
@ -58,8 +28,24 @@ func TestLoggingWithVmodule(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTerminalHandlerWithAttrs(t *testing.T) {
|
||||||
|
out := new(bytes.Buffer)
|
||||||
|
glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false).WithAttrs([]slog.Attr{slog.String("baz", "bat")}))
|
||||||
|
glog.Verbosity(LevelTrace)
|
||||||
|
logger := NewLogger(glog)
|
||||||
|
logger.Trace("a message", "foo", "bar")
|
||||||
|
have := out.String()
|
||||||
|
// The timestamp is locale-dependent, so we want to trim that off
|
||||||
|
// "INFO [01-01|00:00:00.000] a messag ..." -> "a messag..."
|
||||||
|
have = strings.Split(have, "]")[1]
|
||||||
|
want := " a message baz=bat foo=bar\n"
|
||||||
|
if have != want {
|
||||||
|
t.Errorf("\nhave: %q\nwant: %q\n", have, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkTraceLogging(b *testing.B) {
|
func BenchmarkTraceLogging(b *testing.B) {
|
||||||
Root().SetHandler(LvlFilterHandler(LvlInfo, StreamHandler(os.Stderr, TerminalFormat(true))))
|
SetDefault(NewLogger(NewTerminalHandler(os.Stderr, true)))
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
Trace("a message", "v", i)
|
Trace("a message", "v", i)
|
||||||
|
|
|
||||||
49
log/root.go
49
log/root.go
|
|
@ -2,31 +2,33 @@ package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var root atomic.Value
|
||||||
root = &logger{[]interface{}{}, new(swapHandler)}
|
|
||||||
StdoutHandler = StreamHandler(os.Stdout, LogfmtFormat())
|
|
||||||
StderrHandler = StreamHandler(os.Stderr, LogfmtFormat())
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
root.SetHandler(DiscardHandler())
|
defaultLogger := &logger{slog.New(DiscardHandler())}
|
||||||
|
SetDefault(defaultLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new logger with the given context.
|
// SetDefault sets the default global logger
|
||||||
// New is a convenient alias for Root().New
|
func SetDefault(l Logger) {
|
||||||
func New(ctx ...interface{}) Logger {
|
root.Store(l)
|
||||||
return root.New(ctx...)
|
if lg, ok := l.(*logger); ok {
|
||||||
|
slog.SetDefault(lg.inner)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root returns the root logger
|
// Root returns the root logger
|
||||||
func Root() Logger {
|
func Root() Logger {
|
||||||
return root
|
return root.Load().(Logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following functions bypass the exported logger methods (logger.Debug,
|
// The following functions bypass the exported logger methods (logger.Debug,
|
||||||
// etc.) to keep the call depth the same for all paths to logger.write so
|
// etc.) to keep the call depth the same for all paths to logger.Write so
|
||||||
// runtime.Caller(2) always refers to the call site in client code.
|
// runtime.Caller(2) always refers to the call site in client code.
|
||||||
|
|
||||||
// Trace is a convenient alias for Root().Trace
|
// Trace is a convenient alias for Root().Trace
|
||||||
|
|
@ -39,7 +41,7 @@ func Root() Logger {
|
||||||
// log.Trace("msg", "key1", val1)
|
// log.Trace("msg", "key1", val1)
|
||||||
// log.Trace("msg", "key1", val1, "key2", val2)
|
// log.Trace("msg", "key1", val1, "key2", val2)
|
||||||
func Trace(msg string, ctx ...interface{}) {
|
func Trace(msg string, ctx ...interface{}) {
|
||||||
root.write(msg, LvlTrace, ctx, skipLevel)
|
Root().Write(LevelTrace, msg, ctx...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug is a convenient alias for Root().Debug
|
// Debug is a convenient alias for Root().Debug
|
||||||
|
|
@ -52,7 +54,7 @@ func Trace(msg string, ctx ...interface{}) {
|
||||||
// log.Debug("msg", "key1", val1)
|
// log.Debug("msg", "key1", val1)
|
||||||
// log.Debug("msg", "key1", val1, "key2", val2)
|
// log.Debug("msg", "key1", val1, "key2", val2)
|
||||||
func Debug(msg string, ctx ...interface{}) {
|
func Debug(msg string, ctx ...interface{}) {
|
||||||
root.write(msg, LvlDebug, ctx, skipLevel)
|
Root().Write(slog.LevelDebug, msg, ctx...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info is a convenient alias for Root().Info
|
// Info is a convenient alias for Root().Info
|
||||||
|
|
@ -65,7 +67,7 @@ func Debug(msg string, ctx ...interface{}) {
|
||||||
// log.Info("msg", "key1", val1)
|
// log.Info("msg", "key1", val1)
|
||||||
// log.Info("msg", "key1", val1, "key2", val2)
|
// log.Info("msg", "key1", val1, "key2", val2)
|
||||||
func Info(msg string, ctx ...interface{}) {
|
func Info(msg string, ctx ...interface{}) {
|
||||||
root.write(msg, LvlInfo, ctx, skipLevel)
|
Root().Write(slog.LevelInfo, msg, ctx...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn is a convenient alias for Root().Warn
|
// Warn is a convenient alias for Root().Warn
|
||||||
|
|
@ -78,7 +80,7 @@ func Info(msg string, ctx ...interface{}) {
|
||||||
// log.Warn("msg", "key1", val1)
|
// log.Warn("msg", "key1", val1)
|
||||||
// log.Warn("msg", "key1", val1, "key2", val2)
|
// log.Warn("msg", "key1", val1, "key2", val2)
|
||||||
func Warn(msg string, ctx ...interface{}) {
|
func Warn(msg string, ctx ...interface{}) {
|
||||||
root.write(msg, LvlWarn, ctx, skipLevel)
|
Root().Write(slog.LevelWarn, msg, ctx...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error is a convenient alias for Root().Error
|
// Error is a convenient alias for Root().Error
|
||||||
|
|
@ -91,7 +93,7 @@ func Warn(msg string, ctx ...interface{}) {
|
||||||
// log.Error("msg", "key1", val1)
|
// log.Error("msg", "key1", val1)
|
||||||
// log.Error("msg", "key1", val1, "key2", val2)
|
// log.Error("msg", "key1", val1, "key2", val2)
|
||||||
func Error(msg string, ctx ...interface{}) {
|
func Error(msg string, ctx ...interface{}) {
|
||||||
root.write(msg, LvlError, ctx, skipLevel)
|
Root().Write(slog.LevelError, msg, ctx...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crit is a convenient alias for Root().Crit
|
// Crit is a convenient alias for Root().Crit
|
||||||
|
|
@ -104,15 +106,12 @@ func Error(msg string, ctx ...interface{}) {
|
||||||
// log.Crit("msg", "key1", val1)
|
// log.Crit("msg", "key1", val1)
|
||||||
// log.Crit("msg", "key1", val1, "key2", val2)
|
// log.Crit("msg", "key1", val1, "key2", val2)
|
||||||
func Crit(msg string, ctx ...interface{}) {
|
func Crit(msg string, ctx ...interface{}) {
|
||||||
root.write(msg, LvlCrit, ctx, skipLevel)
|
Root().Write(LevelCrit, msg, ctx...)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output is a convenient alias for write, allowing for the modification of
|
// New returns a new logger with the given context.
|
||||||
// the calldepth (number of stack frames to skip).
|
// New is a convenient alias for Root().New
|
||||||
// calldepth influences the reported line number of the log message.
|
func New(ctx ...interface{}) Logger {
|
||||||
// A calldepth of zero reports the immediate caller of Output.
|
return Root().With(ctx...)
|
||||||
// Non-zero calldepth skips as many stack frames.
|
|
||||||
func Output(msg string, lvl Lvl, calldepth int, ctx ...interface{}) {
|
|
||||||
root.write(msg, lvl, ctx, calldepth+skipLevel)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
//go:build !windows && !plan9
|
|
||||||
// +build !windows,!plan9
|
|
||||||
|
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/syslog"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SyslogHandler opens a connection to the system syslog daemon by calling
|
|
||||||
// syslog.New and writes all records to it.
|
|
||||||
func SyslogHandler(priority syslog.Priority, tag string, fmtr Format) (Handler, error) {
|
|
||||||
wr, err := syslog.New(priority, tag)
|
|
||||||
return sharedSyslog(fmtr, wr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyslogNetHandler opens a connection to a log daemon over the network and writes
|
|
||||||
// all log records to it.
|
|
||||||
func SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) (Handler, error) {
|
|
||||||
wr, err := syslog.Dial(net, addr, priority, tag)
|
|
||||||
return sharedSyslog(fmtr, wr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) {
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
h := FuncHandler(func(r *Record) error {
|
|
||||||
var syslogFn = sysWr.Info
|
|
||||||
switch r.Lvl {
|
|
||||||
case LvlCrit:
|
|
||||||
syslogFn = sysWr.Crit
|
|
||||||
case LvlError:
|
|
||||||
syslogFn = sysWr.Err
|
|
||||||
case LvlWarn:
|
|
||||||
syslogFn = sysWr.Warning
|
|
||||||
case LvlInfo:
|
|
||||||
syslogFn = sysWr.Info
|
|
||||||
case LvlDebug:
|
|
||||||
syslogFn = sysWr.Debug
|
|
||||||
case LvlTrace:
|
|
||||||
syslogFn = func(m string) error { return nil } // There's no syslog level for trace
|
|
||||||
}
|
|
||||||
|
|
||||||
s := strings.TrimSpace(string(fmtr.Format(r)))
|
|
||||||
return syslogFn(s)
|
|
||||||
})
|
|
||||||
return LazyHandler(&closingHandler{sysWr, h}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m muster) SyslogHandler(priority syslog.Priority, tag string, fmtr Format) Handler {
|
|
||||||
return must(SyslogHandler(priority, tag, fmtr))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m muster) SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) Handler {
|
|
||||||
return must(SyslogNetHandler(net, addr, priority, tag, fmtr))
|
|
||||||
}
|
|
||||||
|
|
@ -45,7 +45,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
||||||
fdlimit.Raise(2048)
|
fdlimit.Raise(2048)
|
||||||
|
|
||||||
// Generate a batch of accounts to seal and fund with
|
// Generate a batch of accounts to seal and fund with
|
||||||
|
|
|
||||||
|
|
@ -557,12 +557,7 @@ func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 {
|
||||||
|
|
||||||
// Prefix logs with Node ID.
|
// Prefix logs with Node ID.
|
||||||
lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString())
|
lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString())
|
||||||
lfmt := log.TerminalFormat(false)
|
cfg.Log = testlog.Logger(t, log.LevelTrace).With("node-id", lprefix)
|
||||||
cfg.Log = testlog.Logger(t, log.LvlTrace)
|
|
||||||
cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error {
|
|
||||||
t.Logf("%s %s", lprefix, lfmt.Format(r))
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Listen.
|
// Listen.
|
||||||
socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}})
|
socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}})
|
||||||
|
|
|
||||||
|
|
@ -79,12 +79,7 @@ func startLocalhostV5(t *testing.T, cfg Config) *UDPv5 {
|
||||||
|
|
||||||
// Prefix logs with Node ID.
|
// Prefix logs with Node ID.
|
||||||
lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString())
|
lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString())
|
||||||
lfmt := log.TerminalFormat(false)
|
cfg.Log = testlog.Logger(t, log.LevelTrace).With("node-id", lprefix)
|
||||||
cfg.Log = testlog.Logger(t, log.LvlTrace)
|
|
||||||
cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error {
|
|
||||||
t.Logf("%s %s", lprefix, lfmt.Format(r))
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Listen.
|
// Listen.
|
||||||
socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}})
|
socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}})
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -375,9 +376,11 @@ type execNodeConfig struct {
|
||||||
|
|
||||||
func initLogging() {
|
func initLogging() {
|
||||||
// Initialize the logging by default first.
|
// Initialize the logging by default first.
|
||||||
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat()))
|
var innerHandler slog.Handler
|
||||||
glogger.Verbosity(log.LvlInfo)
|
innerHandler = slog.NewTextHandler(os.Stderr, nil)
|
||||||
log.Root().SetHandler(glogger)
|
glogger := log.NewGlogHandler(innerHandler)
|
||||||
|
glogger.Verbosity(log.LevelInfo)
|
||||||
|
log.SetDefault(log.NewLogger(glogger))
|
||||||
|
|
||||||
confEnv := os.Getenv(envNodeConfig)
|
confEnv := os.Getenv(envNodeConfig)
|
||||||
if confEnv == "" {
|
if confEnv == "" {
|
||||||
|
|
@ -395,14 +398,15 @@ func initLogging() {
|
||||||
}
|
}
|
||||||
writer = logWriter
|
writer = logWriter
|
||||||
}
|
}
|
||||||
var verbosity = log.LvlInfo
|
var verbosity = log.LevelInfo
|
||||||
if conf.Node.LogVerbosity <= log.LvlTrace && conf.Node.LogVerbosity >= log.LvlCrit {
|
if conf.Node.LogVerbosity <= log.LevelTrace && conf.Node.LogVerbosity >= log.LevelCrit {
|
||||||
verbosity = conf.Node.LogVerbosity
|
verbosity = log.FromLegacyLevel(int(conf.Node.LogVerbosity))
|
||||||
}
|
}
|
||||||
// Reinitialize the logger
|
// Reinitialize the logger
|
||||||
glogger = log.NewGlogHandler(log.StreamHandler(writer, log.TerminalFormat(true)))
|
innerHandler = log.NewTerminalHandler(writer, true)
|
||||||
|
glogger = log.NewGlogHandler(innerHandler)
|
||||||
glogger.Verbosity(verbosity)
|
glogger.Verbosity(verbosity)
|
||||||
log.Root().SetHandler(glogger)
|
log.SetDefault(log.NewLogger(glogger))
|
||||||
}
|
}
|
||||||
|
|
||||||
// execP2PNode starts a simulation node when the current binary is executed with
|
// execP2PNode starts a simulation node when the current binary is executed with
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Node represents a node in a simulation network which is created by a
|
// Node represents a node in a simulation network which is created by a
|
||||||
|
|
@ -129,7 +130,7 @@ type NodeConfig struct {
|
||||||
// LogVerbosity is the log verbosity of the p2p node at runtime.
|
// LogVerbosity is the log verbosity of the p2p node at runtime.
|
||||||
//
|
//
|
||||||
// The default verbosity is INFO.
|
// The default verbosity is INFO.
|
||||||
LogVerbosity log.Lvl
|
LogVerbosity slog.Level
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding
|
// nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding
|
||||||
|
|
@ -197,7 +198,7 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
|
||||||
n.Port = confJSON.Port
|
n.Port = confJSON.Port
|
||||||
n.EnableMsgEvents = confJSON.EnableMsgEvents
|
n.EnableMsgEvents = confJSON.EnableMsgEvents
|
||||||
n.LogFile = confJSON.LogFile
|
n.LogFile = confJSON.LogFile
|
||||||
n.LogVerbosity = log.Lvl(confJSON.LogVerbosity)
|
n.LogVerbosity = slog.Level(confJSON.LogVerbosity)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// set the log level to Trace
|
// set the log level to Trace
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, false)))
|
||||||
|
|
||||||
// register a single ping-pong service
|
// register a single ping-pong service
|
||||||
services := map[string]adapters.LifecycleConstructor{
|
services := map[string]adapters.LifecycleConstructor{
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,14 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
loglevel := flag.Int("loglevel", 2, "verbosity of logs")
|
loglevel := flag.Int("loglevel", 2, "verbosity of logs")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
log.PrintOrigins(true)
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), slog.Level(*loglevel), true)))
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,14 @@ package core
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuditLogger struct {
|
type AuditLogger struct {
|
||||||
|
|
@ -113,12 +115,13 @@ func (l *AuditLogger) Version(ctx context.Context) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) {
|
func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) {
|
||||||
l := log.New("api", "signer")
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
handler, err := log.FileHandler(path, log.LogfmtFormat())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
l.SetHandler(handler)
|
|
||||||
|
handler := slog.NewTextHandler(f, nil)
|
||||||
|
l := log.NewLogger(handler).With("api", "signer")
|
||||||
l.Info("Configured", "audit log", path)
|
l.Info("Configured", "audit log", path)
|
||||||
return &AuditLogger{l, api}, nil
|
return &AuditLogger{l, api}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEncryption(t *testing.T) {
|
func TestEncryption(t *testing.T) {
|
||||||
|
|
@ -92,7 +93,7 @@ func TestFileStorage(t *testing.T) {
|
||||||
}
|
}
|
||||||
func TestEnd2End(t *testing.T) {
|
func TestEnd2End(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(3), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), slog.LevelInfo, true)))
|
||||||
|
|
||||||
d := t.TempDir()
|
d := t.TempDir()
|
||||||
|
|
||||||
|
|
@ -115,7 +116,7 @@ func TestSwappedKeys(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// It should not be possible to swap the keys/values, so that
|
// It should not be possible to swap the keys/values, so that
|
||||||
// K1:V1, K2:V2 can be swapped into K1:V2, K2:V1
|
// K1:V1, K2:V2 can be swapped into K1:V2, K2:V1
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(3), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), slog.LevelInfo, true)))
|
||||||
|
|
||||||
d := t.TempDir()
|
d := t.TempDir()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,19 +74,19 @@ func TestExecutionSpec(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
|
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
|
||||||
if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil)); err != nil {
|
if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil {
|
||||||
t.Errorf("test in hash mode without snapshotter failed: %v", err)
|
t.Errorf("test in hash mode without snapshotter failed: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil)); err != nil {
|
if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil {
|
||||||
t.Errorf("test in hash mode with snapshotter failed: %v", err)
|
t.Errorf("test in hash mode with snapshotter failed: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil)); err != nil {
|
if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil {
|
||||||
t.Errorf("test in path mode without snapshotter failed: %v", err)
|
t.Errorf("test in path mode without snapshotter failed: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil)); err != nil {
|
if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil {
|
||||||
t.Errorf("test in path mode with snapshotter failed: %v", err)
|
t.Errorf("test in path mode with snapshotter failed: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ type btHeaderMarshaling struct {
|
||||||
ExcessBlobGas *math.HexOrDecimal64
|
ExcessBlobGas *math.HexOrDecimal64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger) error {
|
func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain)) (result error) {
|
||||||
config, ok := Forks[t.json.Network]
|
config, ok := Forks[t.json.Network]
|
||||||
if !ok {
|
if !ok {
|
||||||
return UnsupportedForkError{t.json.Network}
|
return UnsupportedForkError{t.json.Network}
|
||||||
|
|
@ -116,7 +116,9 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger) er
|
||||||
// import pre accounts & construct test genesis block & state root
|
// import pre accounts & construct test genesis block & state root
|
||||||
var (
|
var (
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
tconf = &trie.Config{}
|
tconf = &trie.Config{
|
||||||
|
Preimages: true,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
if scheme == rawdb.PathScheme {
|
if scheme == rawdb.PathScheme {
|
||||||
tconf.PathDB = pathdb.Defaults
|
tconf.PathDB = pathdb.Defaults
|
||||||
|
|
@ -141,7 +143,7 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger) er
|
||||||
// Wrap the original engine within the beacon-engine
|
// Wrap the original engine within the beacon-engine
|
||||||
engine := beacon.New(ethash.NewFaker())
|
engine := beacon.New(ethash.NewFaker())
|
||||||
|
|
||||||
cache := &core.CacheConfig{TrieCleanLimit: 0, StateScheme: scheme}
|
cache := &core.CacheConfig{TrieCleanLimit: 0, StateScheme: scheme, Preimages: true}
|
||||||
if snapshotter {
|
if snapshotter {
|
||||||
cache.SnapshotLimit = 1
|
cache.SnapshotLimit = 1
|
||||||
cache.SnapshotWait = true
|
cache.SnapshotWait = true
|
||||||
|
|
@ -158,6 +160,11 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger) er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Import succeeded: regardless of whether the _test_ succeeds or not, schedule
|
||||||
|
// the post-check to run
|
||||||
|
if postCheck != nil {
|
||||||
|
defer postCheck(result, chain)
|
||||||
|
}
|
||||||
cmlast := chain.CurrentBlock().Hash()
|
cmlast := chain.CurrentBlock().Hash()
|
||||||
if common.Hash(t.json.BestBlock) != cmlast {
|
if common.Hash(t.json.BestBlock) != cmlast {
|
||||||
return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", t.json.BestBlock, cmlast)
|
return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", t.json.BestBlock, cmlast)
|
||||||
|
|
|
||||||
|
|
@ -240,17 +240,6 @@ func (db *Database) Dereference(root common.Hash) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node retrieves the rlp-encoded node blob with provided node hash. It's
|
|
||||||
// only supported by hash-based database and will return an error for others.
|
|
||||||
// Note, this function should be deprecated once ETH66 is deprecated.
|
|
||||||
func (db *Database) Node(hash common.Hash) ([]byte, error) {
|
|
||||||
hdb, ok := db.backend.(*hashdb.Database)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("not supported")
|
|
||||||
}
|
|
||||||
return hdb.Node(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recover rollbacks the database to a specified historical point. The state is
|
// Recover rollbacks the database to a specified historical point. The state is
|
||||||
// supported as the rollback destination only if it's canonical state and the
|
// supported as the rollback destination only if it's canonical state and the
|
||||||
// corresponding trie histories are existent. It's only supported by path-based
|
// corresponding trie histories are existent. It's only supported by path-based
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,6 @@ var Defaults = &Config{
|
||||||
// Database is an intermediate write layer between the trie data structures and
|
// Database is an intermediate write layer between the trie data structures and
|
||||||
// the disk database. The aim is to accumulate trie writes in-memory and only
|
// the disk database. The aim is to accumulate trie writes in-memory and only
|
||||||
// periodically flush a couple tries to disk, garbage collecting the remainder.
|
// periodically flush a couple tries to disk, garbage collecting the remainder.
|
||||||
//
|
|
||||||
// Note, the trie Database is **not** thread safe in its mutations, but it **is**
|
|
||||||
// thread safe in providing individual, independent node access. The rationale
|
|
||||||
// behind this split design is to provide read access to RPC handlers and sync
|
|
||||||
// servers even while the trie is executing expensive garbage collection.
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
||||||
resolver ChildResolver // The handler to resolve children of nodes
|
resolver ChildResolver // The handler to resolve children of nodes
|
||||||
|
|
@ -113,7 +108,7 @@ type Database struct {
|
||||||
// cachedNode is all the information we know about a single cached trie node
|
// cachedNode is all the information we know about a single cached trie node
|
||||||
// in the memory database write layer.
|
// in the memory database write layer.
|
||||||
type cachedNode struct {
|
type cachedNode struct {
|
||||||
node []byte // Encoded node blob
|
node []byte // Encoded node blob, immutable
|
||||||
parents uint32 // Number of live nodes referencing this one
|
parents uint32 // Number of live nodes referencing this one
|
||||||
external map[common.Hash]struct{} // The set of external children
|
external map[common.Hash]struct{} // The set of external children
|
||||||
flushPrev common.Hash // Previous node in the flush-list
|
flushPrev common.Hash // Previous node in the flush-list
|
||||||
|
|
@ -152,9 +147,9 @@ func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Databas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert inserts a simplified trie node into the memory database.
|
// insert inserts a trie node into the memory database. All nodes inserted by
|
||||||
// All nodes inserted by this function will be reference tracked
|
// this function will be reference tracked. This function assumes the lock is
|
||||||
// and in theory should only used for **trie nodes** insertion.
|
// already held.
|
||||||
func (db *Database) insert(hash common.Hash, node []byte) {
|
func (db *Database) insert(hash common.Hash, node []byte) {
|
||||||
// If the node's already cached, skip
|
// If the node's already cached, skip
|
||||||
if _, ok := db.dirties[hash]; ok {
|
if _, ok := db.dirties[hash]; ok {
|
||||||
|
|
@ -183,9 +178,9 @@ func (db *Database) insert(hash common.Hash, node []byte) {
|
||||||
db.dirtiesSize += common.StorageSize(common.HashLength + len(node))
|
db.dirtiesSize += common.StorageSize(common.HashLength + len(node))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node retrieves an encoded cached trie node from memory. If it cannot be found
|
// node retrieves an encoded cached trie node from memory. If it cannot be found
|
||||||
// cached, the method queries the persistent database for the content.
|
// cached, the method queries the persistent database for the content.
|
||||||
func (db *Database) Node(hash common.Hash) ([]byte, error) {
|
func (db *Database) node(hash common.Hash) ([]byte, error) {
|
||||||
// It doesn't make sense to retrieve the metaroot
|
// It doesn't make sense to retrieve the metaroot
|
||||||
if hash == (common.Hash{}) {
|
if hash == (common.Hash{}) {
|
||||||
return nil, errors.New("not found")
|
return nil, errors.New("not found")
|
||||||
|
|
@ -198,11 +193,14 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) {
|
||||||
return enc, nil
|
return enc, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Retrieve the node from the dirty cache if available
|
// Retrieve the node from the dirty cache if available.
|
||||||
db.lock.RLock()
|
db.lock.RLock()
|
||||||
dirty := db.dirties[hash]
|
dirty := db.dirties[hash]
|
||||||
db.lock.RUnlock()
|
db.lock.RUnlock()
|
||||||
|
|
||||||
|
// Return the cached node if it's found in the dirty set.
|
||||||
|
// The dirty.node field is immutable and safe to read it
|
||||||
|
// even without lock guard.
|
||||||
if dirty != nil {
|
if dirty != nil {
|
||||||
memcacheDirtyHitMeter.Mark(1)
|
memcacheDirtyHitMeter.Mark(1)
|
||||||
memcacheDirtyReadMeter.Mark(int64(len(dirty.node)))
|
memcacheDirtyReadMeter.Mark(int64(len(dirty.node)))
|
||||||
|
|
@ -223,20 +221,6 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) {
|
||||||
return nil, errors.New("not found")
|
return nil, errors.New("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nodes retrieves the hashes of all the nodes cached within the memory database.
|
|
||||||
// This method is extremely expensive and should only be used to validate internal
|
|
||||||
// states in test code.
|
|
||||||
func (db *Database) Nodes() []common.Hash {
|
|
||||||
db.lock.RLock()
|
|
||||||
defer db.lock.RUnlock()
|
|
||||||
|
|
||||||
var hashes = make([]common.Hash, 0, len(db.dirties))
|
|
||||||
for hash := range db.dirties {
|
|
||||||
hashes = append(hashes, hash)
|
|
||||||
}
|
|
||||||
return hashes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reference adds a new reference from a parent node to a child node.
|
// Reference adds a new reference from a parent node to a child node.
|
||||||
// This function is used to add reference between internal trie node
|
// This function is used to add reference between internal trie node
|
||||||
// and external node(e.g. storage trie root), all internal trie nodes
|
// and external node(e.g. storage trie root), all internal trie nodes
|
||||||
|
|
@ -344,16 +328,16 @@ func (db *Database) dereference(hash common.Hash) {
|
||||||
|
|
||||||
// Cap iteratively flushes old but still referenced trie nodes until the total
|
// Cap iteratively flushes old but still referenced trie nodes until the total
|
||||||
// memory usage goes below the given threshold.
|
// memory usage goes below the given threshold.
|
||||||
//
|
|
||||||
// Note, this method is a non-synchronized mutator. It is unsafe to call this
|
|
||||||
// concurrently with other mutators.
|
|
||||||
func (db *Database) Cap(limit common.StorageSize) error {
|
func (db *Database) Cap(limit common.StorageSize) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
// Create a database batch to flush persistent data out. It is important that
|
// Create a database batch to flush persistent data out. It is important that
|
||||||
// outside code doesn't see an inconsistent state (referenced data removed from
|
// outside code doesn't see an inconsistent state (referenced data removed from
|
||||||
// memory cache during commit but not yet in persistent storage). This is ensured
|
// memory cache during commit but not yet in persistent storage). This is ensured
|
||||||
// by only uncaching existing data when the database write finalizes.
|
// by only uncaching existing data when the database write finalizes.
|
||||||
nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
|
|
||||||
batch := db.diskdb.NewBatch()
|
batch := db.diskdb.NewBatch()
|
||||||
|
nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
|
||||||
|
|
||||||
// db.dirtiesSize only contains the useful data in the cache, but when reporting
|
// db.dirtiesSize only contains the useful data in the cache, but when reporting
|
||||||
// the total memory consumption, the maintenance metadata is also needed to be
|
// the total memory consumption, the maintenance metadata is also needed to be
|
||||||
|
|
@ -391,9 +375,6 @@ func (db *Database) Cap(limit common.StorageSize) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Write successful, clear out the flushed data
|
// Write successful, clear out the flushed data
|
||||||
db.lock.Lock()
|
|
||||||
defer db.lock.Unlock()
|
|
||||||
|
|
||||||
for db.oldest != oldest {
|
for db.oldest != oldest {
|
||||||
node := db.dirties[db.oldest]
|
node := db.dirties[db.oldest]
|
||||||
delete(db.dirties, db.oldest)
|
delete(db.dirties, db.oldest)
|
||||||
|
|
@ -424,10 +405,10 @@ func (db *Database) Cap(limit common.StorageSize) error {
|
||||||
// Commit iterates over all the children of a particular node, writes them out
|
// Commit iterates over all the children of a particular node, writes them out
|
||||||
// to disk, forcefully tearing down all references in both directions. As a side
|
// to disk, forcefully tearing down all references in both directions. As a side
|
||||||
// effect, all pre-images accumulated up to this point are also written.
|
// effect, all pre-images accumulated up to this point are also written.
|
||||||
//
|
|
||||||
// Note, this method is a non-synchronized mutator. It is unsafe to call this
|
|
||||||
// concurrently with other mutators.
|
|
||||||
func (db *Database) Commit(node common.Hash, report bool) error {
|
func (db *Database) Commit(node common.Hash, report bool) error {
|
||||||
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
// Create a database batch to flush persistent data out. It is important that
|
// Create a database batch to flush persistent data out. It is important that
|
||||||
// outside code doesn't see an inconsistent state (referenced data removed from
|
// outside code doesn't see an inconsistent state (referenced data removed from
|
||||||
// memory cache during commit but not yet in persistent storage). This is ensured
|
// memory cache during commit but not yet in persistent storage). This is ensured
|
||||||
|
|
@ -449,8 +430,6 @@ func (db *Database) Commit(node common.Hash, report bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Uncache any leftovers in the last batch
|
// Uncache any leftovers in the last batch
|
||||||
db.lock.Lock()
|
|
||||||
defer db.lock.Unlock()
|
|
||||||
if err := batch.Replay(uncacher); err != nil {
|
if err := batch.Replay(uncacher); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -499,13 +478,11 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
db.lock.Lock()
|
|
||||||
err := batch.Replay(uncacher)
|
err := batch.Replay(uncacher)
|
||||||
batch.Reset()
|
|
||||||
db.lock.Unlock()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
batch.Reset()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -574,7 +551,7 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool {
|
||||||
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
|
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
|
||||||
// Ensure the parent state is present and signal a warning if not.
|
// Ensure the parent state is present and signal a warning if not.
|
||||||
if parent != types.EmptyRootHash {
|
if parent != types.EmptyRootHash {
|
||||||
if blob, _ := db.Node(parent); len(blob) == 0 {
|
if blob, _ := db.node(parent); len(blob) == 0 {
|
||||||
log.Error("parent state is not present")
|
log.Error("parent state is not present")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -655,7 +632,7 @@ func (db *Database) Scheme() string {
|
||||||
// Reader retrieves a node reader belonging to the given state root.
|
// Reader retrieves a node reader belonging to the given state root.
|
||||||
// An error will be returned if the requested state is not available.
|
// An error will be returned if the requested state is not available.
|
||||||
func (db *Database) Reader(root common.Hash) (*reader, error) {
|
func (db *Database) Reader(root common.Hash) (*reader, error) {
|
||||||
if _, err := db.Node(root); err != nil {
|
if _, err := db.node(root); err != nil {
|
||||||
return nil, fmt.Errorf("state %#x is not available, %v", root, err)
|
return nil, fmt.Errorf("state %#x is not available, %v", root, err)
|
||||||
}
|
}
|
||||||
return &reader{db: db}, nil
|
return &reader{db: db}, nil
|
||||||
|
|
@ -666,9 +643,9 @@ type reader struct {
|
||||||
db *Database
|
db *Database
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node retrieves the trie node with the given node hash.
|
// Node retrieves the trie node with the given node hash. No error will be
|
||||||
// No error will be returned if the node is not found.
|
// returned if the node is not found.
|
||||||
func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
|
func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
|
||||||
blob, _ := reader.db.Node(hash)
|
blob, _ := reader.db.node(hash)
|
||||||
return blob, nil
|
return blob, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,24 +31,24 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
accounts = map[common.Address]*types.StateAccount{
|
accounts = map[common.Address]*types.StateAccount{
|
||||||
common.Address{1}: {
|
{1}: {
|
||||||
Nonce: 100,
|
Nonce: 100,
|
||||||
Balance: big.NewInt(100),
|
Balance: big.NewInt(100),
|
||||||
CodeHash: common.Hash{0x1}.Bytes(),
|
CodeHash: common.Hash{0x1}.Bytes(),
|
||||||
},
|
},
|
||||||
common.Address{2}: {
|
{2}: {
|
||||||
Nonce: 200,
|
Nonce: 200,
|
||||||
Balance: big.NewInt(200),
|
Balance: big.NewInt(200),
|
||||||
CodeHash: common.Hash{0x2}.Bytes(),
|
CodeHash: common.Hash{0x2}.Bytes(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
storages = map[common.Address]map[common.Hash][]byte{
|
storages = map[common.Address]map[common.Hash][]byte{
|
||||||
common.Address{1}: {
|
{1}: {
|
||||||
common.Hash{10}: []byte{10},
|
common.Hash{10}: []byte{10},
|
||||||
common.Hash{11}: []byte{11},
|
common.Hash{11}: []byte{11},
|
||||||
common.MaxHash: []byte{0xff},
|
common.MaxHash: []byte{0xff},
|
||||||
},
|
},
|
||||||
common.Address{2}: {
|
{2}: {
|
||||||
common.Hash{20}: []byte{20},
|
common.Hash{20}: []byte{20},
|
||||||
common.Hash{21}: []byte{21},
|
common.Hash{21}: []byte{21},
|
||||||
common.MaxHash: []byte{0xff},
|
common.MaxHash: []byte{0xff},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue