mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
commit
e70c426bc5
31 changed files with 701 additions and 1100 deletions
|
|
@ -119,11 +119,22 @@ func unpack(t *Type, dst interface{}, src interface{}) error {
|
||||||
dstVal = reflect.ValueOf(dst).Elem()
|
dstVal = reflect.ValueOf(dst).Elem()
|
||||||
srcVal = reflect.ValueOf(src)
|
srcVal = reflect.ValueOf(src)
|
||||||
)
|
)
|
||||||
|
tuple, typ := false, t
|
||||||
if t.T != TupleTy && !((t.T == SliceTy || t.T == ArrayTy) && t.Elem.T == TupleTy) {
|
for {
|
||||||
|
if typ.T == SliceTy || typ.T == ArrayTy {
|
||||||
|
typ = typ.Elem
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tuple = typ.T == TupleTy
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !tuple {
|
||||||
return set(dstVal, srcVal)
|
return set(dstVal, srcVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dereferences interface or pointer wrapper
|
||||||
|
dstVal = indirectInterfaceOrPtr(dstVal)
|
||||||
|
|
||||||
switch t.T {
|
switch t.T {
|
||||||
case TupleTy:
|
case TupleTy:
|
||||||
if dstVal.Kind() != reflect.Struct {
|
if dstVal.Kind() != reflect.Struct {
|
||||||
|
|
@ -191,7 +202,7 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interfac
|
||||||
argument := arguments.NonIndexed()[0]
|
argument := arguments.NonIndexed()[0]
|
||||||
elem := reflect.ValueOf(v).Elem()
|
elem := reflect.ValueOf(v).Elem()
|
||||||
|
|
||||||
if elem.Kind() == reflect.Struct {
|
if elem.Kind() == reflect.Struct && argument.Type.T != TupleTy {
|
||||||
fieldmap, err := mapArgNamesToStructFields([]string{argument.Name}, elem)
|
fieldmap, err := mapArgNamesToStructFields([]string{argument.Name}, elem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/external"
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -43,7 +44,7 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
|
||||||
return NewKeyedTransactor(key.PrivateKey), nil
|
return NewKeyedTransactor(key.PrivateKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKeystoreTransactor is a utility method to easily create a transaction signer from
|
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from
|
||||||
// an decrypted key from a keystore
|
// an decrypted key from a keystore
|
||||||
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
|
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
|
||||||
return &TransactOpts{
|
return &TransactOpts{
|
||||||
|
|
@ -79,3 +80,17 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewClefTransactor is a utility method to easily create a transaction signer
|
||||||
|
// with a clef backend.
|
||||||
|
func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts {
|
||||||
|
return &TransactOpts{
|
||||||
|
From: account.Address,
|
||||||
|
Signer: func(signer types.Signer, address common.Address, transaction *types.Transaction) (*types.Transaction, error) {
|
||||||
|
if address != account.Address {
|
||||||
|
return nil, errors.New("not authorized to sign this account")
|
||||||
|
}
|
||||||
|
return clef.SignTx(account, transaction, nil) // Clef enforces its own chain id
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ package bind
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/format"
|
"go/format"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -38,6 +39,7 @@ type Lang int
|
||||||
const (
|
const (
|
||||||
LangGo Lang = iota
|
LangGo Lang = iota
|
||||||
LangJava
|
LangJava
|
||||||
|
LangObjC
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
||||||
|
|
@ -62,11 +64,12 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
return r
|
return r
|
||||||
}, abis[i])
|
}, abis[i])
|
||||||
|
|
||||||
// Extract the call and transact methods; events; and sort them alphabetically
|
// Extract the call and transact methods; events, struct definitions; and sort them alphabetically
|
||||||
var (
|
var (
|
||||||
calls = make(map[string]*tmplMethod)
|
calls = make(map[string]*tmplMethod)
|
||||||
transacts = make(map[string]*tmplMethod)
|
transacts = make(map[string]*tmplMethod)
|
||||||
events = make(map[string]*tmplEvent)
|
events = make(map[string]*tmplEvent)
|
||||||
|
structs = make(map[string]*tmplStruct)
|
||||||
)
|
)
|
||||||
for _, original := range evmABI.Methods {
|
for _, original := range evmABI.Methods {
|
||||||
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
||||||
|
|
@ -79,6 +82,9 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
if input.Name == "" {
|
if input.Name == "" {
|
||||||
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
||||||
}
|
}
|
||||||
|
if _, exist := structs[input.Type.String()]; input.Type.T == abi.TupleTy && !exist {
|
||||||
|
bindStructType[lang](input.Type, structs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
normalized.Outputs = make([]abi.Argument, len(original.Outputs))
|
normalized.Outputs = make([]abi.Argument, len(original.Outputs))
|
||||||
copy(normalized.Outputs, original.Outputs)
|
copy(normalized.Outputs, original.Outputs)
|
||||||
|
|
@ -86,6 +92,9 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
if output.Name != "" {
|
if output.Name != "" {
|
||||||
normalized.Outputs[j].Name = capitalise(output.Name)
|
normalized.Outputs[j].Name = capitalise(output.Name)
|
||||||
}
|
}
|
||||||
|
if _, exist := structs[output.Type.String()]; output.Type.T == abi.TupleTy && !exist {
|
||||||
|
bindStructType[lang](output.Type, structs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Append the methods to the call or transact lists
|
// Append the methods to the call or transact lists
|
||||||
if original.Const {
|
if original.Const {
|
||||||
|
|
@ -111,11 +120,20 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
if input.Name == "" {
|
if input.Name == "" {
|
||||||
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
||||||
}
|
}
|
||||||
|
if _, exist := structs[input.Type.String()]; input.Type.T == abi.TupleTy && !exist {
|
||||||
|
bindStructType[lang](input.Type, structs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Append the event to the accumulator list
|
// Append the event to the accumulator list
|
||||||
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
|
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There is no easy way to pass arbitrary java objects to the Go side.
|
||||||
|
if len(structs) > 0 && lang == LangJava {
|
||||||
|
return "", errors.New("java binding for tuple arguments is not supported yet")
|
||||||
|
}
|
||||||
|
|
||||||
contracts[types[i]] = &tmplContract{
|
contracts[types[i]] = &tmplContract{
|
||||||
Type: capitalise(types[i]),
|
Type: capitalise(types[i]),
|
||||||
InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
|
InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
|
||||||
|
|
@ -124,6 +142,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
Calls: calls,
|
Calls: calls,
|
||||||
Transacts: transacts,
|
Transacts: transacts,
|
||||||
Events: events,
|
Events: events,
|
||||||
|
Structs: structs,
|
||||||
}
|
}
|
||||||
if len(fsigs) > i {
|
if len(fsigs) > i {
|
||||||
contracts[types[i]].FuncSigs = fsigs[i]
|
contracts[types[i]].FuncSigs = fsigs[i]
|
||||||
|
|
@ -140,6 +159,8 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
"bindtype": bindType[lang],
|
"bindtype": bindType[lang],
|
||||||
"bindtopictype": bindTopicType[lang],
|
"bindtopictype": bindTopicType[lang],
|
||||||
"namedtype": namedType[lang],
|
"namedtype": namedType[lang],
|
||||||
|
"formatmethod": formatMethod,
|
||||||
|
"formatevent": formatEvent,
|
||||||
"capitalise": capitalise,
|
"capitalise": capitalise,
|
||||||
"decapitalise": decapitalise,
|
"decapitalise": decapitalise,
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +182,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
|
|
||||||
// bindType is a set of type binders that convert Solidity types to some supported
|
// bindType is a set of type binders that convert Solidity types to some supported
|
||||||
// programming language types.
|
// programming language types.
|
||||||
var bindType = map[Lang]func(kind abi.Type) string{
|
var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||||
LangGo: bindTypeGo,
|
LangGo: bindTypeGo,
|
||||||
LangJava: bindTypeJava,
|
LangJava: bindTypeJava,
|
||||||
}
|
}
|
||||||
|
|
@ -193,13 +214,14 @@ func bindBasicTypeGo(kind abi.Type) string {
|
||||||
// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping
|
// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping
|
||||||
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
|
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
|
||||||
// mapped will use an upscaled type (e.g. BigDecimal).
|
// mapped will use an upscaled type (e.g. BigDecimal).
|
||||||
func bindTypeGo(kind abi.Type) string {
|
func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
// todo(rjl493456442) tuple
|
|
||||||
switch kind.T {
|
switch kind.T {
|
||||||
|
case abi.TupleTy:
|
||||||
|
return structs[kind.String()].Name
|
||||||
case abi.ArrayTy:
|
case abi.ArrayTy:
|
||||||
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem)
|
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs)
|
||||||
case abi.SliceTy:
|
case abi.SliceTy:
|
||||||
return "[]" + bindTypeGo(*kind.Elem)
|
return "[]" + bindTypeGo(*kind.Elem, structs)
|
||||||
default:
|
default:
|
||||||
return bindBasicTypeGo(kind)
|
return bindBasicTypeGo(kind)
|
||||||
}
|
}
|
||||||
|
|
@ -248,15 +270,10 @@ func bindBasicTypeJava(kind abi.Type) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping
|
// pluralizeJavaType explicitly converts multidimensional types to predefined
|
||||||
// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly
|
// type in go side.
|
||||||
// mapped will use an upscaled type (e.g. BigDecimal).
|
func pluralizeJavaType(typ string) string {
|
||||||
func bindTypeJava(kind abi.Type) string {
|
switch typ {
|
||||||
switch kind.T {
|
|
||||||
case abi.ArrayTy, abi.SliceTy:
|
|
||||||
// Explicitly convert multidimensional types to predefined type in go side.
|
|
||||||
inner := bindTypeJava(*kind.Elem)
|
|
||||||
switch inner {
|
|
||||||
case "boolean":
|
case "boolean":
|
||||||
return "Bools"
|
return "Bools"
|
||||||
case "String":
|
case "String":
|
||||||
|
|
@ -268,7 +285,18 @@ func bindTypeJava(kind abi.Type) string {
|
||||||
case "BigInt":
|
case "BigInt":
|
||||||
return "BigInts"
|
return "BigInts"
|
||||||
}
|
}
|
||||||
return inner + "[]"
|
return typ + "[]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping
|
||||||
|
// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly
|
||||||
|
// mapped will use an upscaled type (e.g. BigDecimal).
|
||||||
|
func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
|
switch kind.T {
|
||||||
|
case abi.TupleTy:
|
||||||
|
return structs[kind.String()].Name
|
||||||
|
case abi.ArrayTy, abi.SliceTy:
|
||||||
|
return pluralizeJavaType(bindTypeJava(*kind.Elem, structs))
|
||||||
default:
|
default:
|
||||||
return bindBasicTypeJava(kind)
|
return bindBasicTypeJava(kind)
|
||||||
}
|
}
|
||||||
|
|
@ -276,15 +304,15 @@ func bindTypeJava(kind abi.Type) string {
|
||||||
|
|
||||||
// bindTopicType is a set of type binders that convert Solidity types to some
|
// bindTopicType is a set of type binders that convert Solidity types to some
|
||||||
// supported programming language topic types.
|
// supported programming language topic types.
|
||||||
var bindTopicType = map[Lang]func(kind abi.Type) string{
|
var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||||
LangGo: bindTopicTypeGo,
|
LangGo: bindTopicTypeGo,
|
||||||
LangJava: bindTopicTypeJava,
|
LangJava: bindTopicTypeJava,
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindTypeGo converts a Solidity topic type to a Go one. It is almost the same
|
// bindTypeGo converts a Solidity topic type to a Go one. It is almost the same
|
||||||
// funcionality as for simple types, but dynamic types get converted to hashes.
|
// funcionality as for simple types, but dynamic types get converted to hashes.
|
||||||
func bindTopicTypeGo(kind abi.Type) string {
|
func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
bound := bindTypeGo(kind)
|
bound := bindTypeGo(kind, structs)
|
||||||
if bound == "string" || bound == "[]byte" {
|
if bound == "string" || bound == "[]byte" {
|
||||||
bound = "common.Hash"
|
bound = "common.Hash"
|
||||||
}
|
}
|
||||||
|
|
@ -293,14 +321,77 @@ func bindTopicTypeGo(kind abi.Type) string {
|
||||||
|
|
||||||
// bindTypeGo converts a Solidity topic type to a Java one. It is almost the same
|
// bindTypeGo converts a Solidity topic type to a Java one. It is almost the same
|
||||||
// funcionality as for simple types, but dynamic types get converted to hashes.
|
// funcionality as for simple types, but dynamic types get converted to hashes.
|
||||||
func bindTopicTypeJava(kind abi.Type) string {
|
func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
bound := bindTypeJava(kind)
|
bound := bindTypeJava(kind, structs)
|
||||||
if bound == "String" || bound == "byte[]" {
|
if bound == "String" || bound == "byte[]" {
|
||||||
bound = "Hash"
|
bound = "Hash"
|
||||||
}
|
}
|
||||||
return bound
|
return bound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bindStructType is a set of type binders that convert Solidity tuple types to some supported
|
||||||
|
// programming language struct definition.
|
||||||
|
var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||||
|
LangGo: bindStructTypeGo,
|
||||||
|
LangJava: bindStructTypeJava,
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping
|
||||||
|
// in the given map.
|
||||||
|
// Notably, this function will resolve and record nested struct recursively.
|
||||||
|
func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
|
switch kind.T {
|
||||||
|
case abi.TupleTy:
|
||||||
|
if s, exist := structs[kind.String()]; exist {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
var fields []*tmplField
|
||||||
|
for i, elem := range kind.TupleElems {
|
||||||
|
field := bindStructTypeGo(*elem, structs)
|
||||||
|
fields = append(fields, &tmplField{Type: field, Name: capitalise(kind.TupleRawNames[i]), SolKind: *elem})
|
||||||
|
}
|
||||||
|
name := fmt.Sprintf("Struct%d", len(structs))
|
||||||
|
structs[kind.String()] = &tmplStruct{
|
||||||
|
Name: name,
|
||||||
|
Fields: fields,
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
case abi.ArrayTy:
|
||||||
|
return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs)
|
||||||
|
case abi.SliceTy:
|
||||||
|
return "[]" + bindStructTypeGo(*kind.Elem, structs)
|
||||||
|
default:
|
||||||
|
return bindBasicTypeGo(kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindStructTypeJava converts a Solidity tuple type to a Java one and records the mapping
|
||||||
|
// in the given map.
|
||||||
|
// Notably, this function will resolve and record nested struct recursively.
|
||||||
|
func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
|
switch kind.T {
|
||||||
|
case abi.TupleTy:
|
||||||
|
if s, exist := structs[kind.String()]; exist {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
var fields []*tmplField
|
||||||
|
for i, elem := range kind.TupleElems {
|
||||||
|
field := bindStructTypeJava(*elem, structs)
|
||||||
|
fields = append(fields, &tmplField{Type: field, Name: decapitalise(kind.TupleRawNames[i]), SolKind: *elem})
|
||||||
|
}
|
||||||
|
name := fmt.Sprintf("Class%d", len(structs))
|
||||||
|
structs[kind.String()] = &tmplStruct{
|
||||||
|
Name: name,
|
||||||
|
Fields: fields,
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
case abi.ArrayTy, abi.SliceTy:
|
||||||
|
return pluralizeJavaType(bindStructTypeJava(*kind.Elem, structs))
|
||||||
|
default:
|
||||||
|
return bindBasicTypeJava(kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// namedType is a set of functions that transform language specific types to
|
// namedType is a set of functions that transform language specific types to
|
||||||
// named versions that my be used inside method names.
|
// named versions that my be used inside method names.
|
||||||
var namedType = map[Lang]func(string, abi.Type) string{
|
var namedType = map[Lang]func(string, abi.Type) string{
|
||||||
|
|
@ -378,3 +469,63 @@ func structured(args abi.Arguments) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveArgName converts a raw argument representation into a user friendly format.
|
||||||
|
func resolveArgName(arg abi.Argument, structs map[string]*tmplStruct) string {
|
||||||
|
var (
|
||||||
|
prefix string
|
||||||
|
embedded string
|
||||||
|
typ = &arg.Type
|
||||||
|
)
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
switch typ.T {
|
||||||
|
case abi.SliceTy:
|
||||||
|
prefix += "[]"
|
||||||
|
case abi.ArrayTy:
|
||||||
|
prefix += fmt.Sprintf("[%d]", typ.Size)
|
||||||
|
default:
|
||||||
|
embedded = typ.String()
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
typ = typ.Elem
|
||||||
|
}
|
||||||
|
if s, exist := structs[embedded]; exist {
|
||||||
|
return prefix + s.Name
|
||||||
|
} else {
|
||||||
|
return arg.Type.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatMethod transforms raw method representation into a user friendly one.
|
||||||
|
func formatMethod(method abi.Method, structs map[string]*tmplStruct) string {
|
||||||
|
inputs := make([]string, len(method.Inputs))
|
||||||
|
for i, input := range method.Inputs {
|
||||||
|
inputs[i] = fmt.Sprintf("%v %v", resolveArgName(input, structs), input.Name)
|
||||||
|
}
|
||||||
|
outputs := make([]string, len(method.Outputs))
|
||||||
|
for i, output := range method.Outputs {
|
||||||
|
outputs[i] = resolveArgName(output, structs)
|
||||||
|
if len(output.Name) > 0 {
|
||||||
|
outputs[i] += fmt.Sprintf(" %v", output.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constant := ""
|
||||||
|
if method.Const {
|
||||||
|
constant = "constant "
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatEvent transforms raw event representation into a user friendly one.
|
||||||
|
func formatEvent(event abi.Event, structs map[string]*tmplStruct) string {
|
||||||
|
inputs := make([]string, len(event.Inputs))
|
||||||
|
for i, input := range event.Inputs {
|
||||||
|
if input.Indexed {
|
||||||
|
inputs[i] = fmt.Sprintf("%v indexed %v", resolveArgName(input, structs), input.Name)
|
||||||
|
} else {
|
||||||
|
inputs[i] = fmt.Sprintf("%v %v", resolveArgName(input, structs), input.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("event %v(%v)", event.Name, strings.Join(inputs, ", "))
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -34,6 +34,7 @@ type tmplContract struct {
|
||||||
Calls map[string]*tmplMethod // Contract calls that only read state data
|
Calls map[string]*tmplMethod // Contract calls that only read state data
|
||||||
Transacts map[string]*tmplMethod // Contract calls that write state data
|
Transacts map[string]*tmplMethod // Contract calls that write state data
|
||||||
Events map[string]*tmplEvent // Contract events accessors
|
Events map[string]*tmplEvent // Contract events accessors
|
||||||
|
Structs map[string]*tmplStruct // Contract struct type definitions
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
||||||
|
|
@ -50,6 +51,21 @@ type tmplEvent struct {
|
||||||
Normalized abi.Event // Normalized version of the parsed fields
|
Normalized abi.Event // Normalized version of the parsed fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tmplField is a wrapper around a struct field with binding language
|
||||||
|
// struct type definition and relative filed name.
|
||||||
|
type tmplField struct {
|
||||||
|
Type string // Field type representation depends on target binding language
|
||||||
|
Name string // Field name converted from the raw user-defined field name
|
||||||
|
SolKind abi.Type // Raw abi type information
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmplStruct is a wrapper around an abi.tuple contains a auto-generated
|
||||||
|
// struct name.
|
||||||
|
type tmplStruct struct {
|
||||||
|
Name string // Auto-generated struct name(We can't obtain the raw struct name through abi)
|
||||||
|
Fields []*tmplField // Struct fields definition depends on the binding language.
|
||||||
|
}
|
||||||
|
|
||||||
// tmplSource is language to template mapping containing all the supported
|
// tmplSource is language to template mapping containing all the supported
|
||||||
// programming languages the package can generate to.
|
// programming languages the package can generate to.
|
||||||
var tmplSource = map[Lang]string{
|
var tmplSource = map[Lang]string{
|
||||||
|
|
@ -90,6 +106,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
{{range $contract := .Contracts}}
|
{{range $contract := .Contracts}}
|
||||||
|
{{$structs := $contract.Structs}}
|
||||||
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
||||||
const {{.Type}}ABI = "{{.InputABI}}"
|
const {{.Type}}ABI = "{{.InputABI}}"
|
||||||
|
|
||||||
|
|
@ -107,7 +124,7 @@ var (
|
||||||
const {{.Type}}Bin = ` + "`" + `{{.InputBin}}` + "`" + `
|
const {{.Type}}Bin = ` + "`" + `{{.InputBin}}` + "`" + `
|
||||||
|
|
||||||
// Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
// Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
||||||
func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) {
|
func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) {
|
||||||
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, nil, nil, err
|
return common.Address{}, nil, nil, err
|
||||||
|
|
@ -262,16 +279,24 @@ var (
|
||||||
return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...)
|
return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{{range .Structs}}
|
||||||
|
// {{.Name}} is an auto generated low-level Go binding around an user-defined struct.
|
||||||
|
type {{.Name}} struct {
|
||||||
|
{{range $field := .Fields}}
|
||||||
|
{{$field.Name}} {{$field.Type}}{{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{range .Calls}}
|
{{range .Calls}}
|
||||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||||
//
|
//
|
||||||
// Solidity: {{.Original.String}}
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type}},{{end}}{{end}} error) {
|
func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) {
|
||||||
{{if .Structured}}ret := new(struct{
|
{{if .Structured}}ret := new(struct{
|
||||||
{{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}}
|
{{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}}
|
||||||
{{end}}
|
{{end}}
|
||||||
}){{else}}var (
|
}){{else}}var (
|
||||||
{{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type}})
|
{{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type $structs}})
|
||||||
{{end}}
|
{{end}}
|
||||||
){{end}}
|
){{end}}
|
||||||
out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}&[]interface{}{
|
out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}&[]interface{}{
|
||||||
|
|
@ -284,15 +309,15 @@ var (
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||||
//
|
//
|
||||||
// Solidity: {{.Original.String}}
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type}},{{end}} {{end}} error) {
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||||
//
|
//
|
||||||
// Solidity: {{.Original.String}}
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type}},{{end}} {{end}} error) {
|
func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -300,22 +325,22 @@ var (
|
||||||
{{range .Transacts}}
|
{{range .Transacts}}
|
||||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||||
//
|
//
|
||||||
// Solidity: {{.Original.String}}
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) {
|
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||||
//
|
//
|
||||||
// Solidity: {{.Original.String}}
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) {
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||||
//
|
//
|
||||||
// Solidity: {{.Original.String}}
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) {
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -387,14 +412,14 @@ var (
|
||||||
|
|
||||||
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
|
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
|
||||||
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
|
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
|
||||||
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type}}{{else}}{{bindtype .Type}}{{end}}; {{end}}
|
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}}
|
||||||
Raw types.Log // Blockchain specific contextual infos
|
Raw types.Log // Blockchain specific contextual infos
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.Id}}.
|
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.Id}}.
|
||||||
//
|
//
|
||||||
// Solidity: {{.Original.String}}
|
// Solidity: {{formatevent .Original $structs}}
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
|
||||||
{{range .Normalized.Inputs}}
|
{{range .Normalized.Inputs}}
|
||||||
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||||
for _, {{.Name}}Item := range {{.Name}} {
|
for _, {{.Name}}Item := range {{.Name}} {
|
||||||
|
|
@ -410,8 +435,8 @@ var (
|
||||||
|
|
||||||
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.Id}}.
|
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.Id}}.
|
||||||
//
|
//
|
||||||
// Solidity: {{.Original.String}}
|
// Solidity: {{formatevent .Original $structs}}
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (event.Subscription, error) {
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (event.Subscription, error) {
|
||||||
{{range .Normalized.Inputs}}
|
{{range .Normalized.Inputs}}
|
||||||
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||||
for _, {{.Name}}Item := range {{.Name}} {
|
for _, {{.Name}}Item := range {{.Name}} {
|
||||||
|
|
@ -477,6 +502,7 @@ import org.ethereum.geth.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
{{range $contract := .Contracts}}
|
{{range $contract := .Contracts}}
|
||||||
|
{{$structs := $contract.Structs}}
|
||||||
public class {{.Type}} {
|
public class {{.Type}} {
|
||||||
// ABI is the input ABI used to generate the binding from.
|
// ABI is the input ABI used to generate the binding from.
|
||||||
public final static String ABI = "{{.InputABI}}";
|
public final static String ABI = "{{.InputABI}}";
|
||||||
|
|
@ -496,9 +522,9 @@ public class {{.Type}} {
|
||||||
public final static String BYTECODE = "0x{{.InputBin}}";
|
public final static String BYTECODE = "0x{{.InputBin}}";
|
||||||
|
|
||||||
// deploy deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
// deploy deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
||||||
public static {{.Type}} deploy(TransactOpts auth, EthereumClient client{{range .Constructor.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception {
|
public static {{.Type}} deploy(TransactOpts auth, EthereumClient client{{range .Constructor.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception {
|
||||||
Interfaces args = Geth.newInterfaces({{(len .Constructor.Inputs)}});
|
Interfaces args = Geth.newInterfaces({{(len .Constructor.Inputs)}});
|
||||||
{{range $index, $element := .Constructor.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
|
{{range $index, $element := .Constructor.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
|
||||||
{{end}}
|
{{end}}
|
||||||
return new {{.Type}}(Geth.deployContract(auth, ABI, Geth.decodeFromHex(BYTECODE), client, args));
|
return new {{.Type}}(Geth.deployContract(auth, ABI, Geth.decodeFromHex(BYTECODE), client, args));
|
||||||
}
|
}
|
||||||
|
|
@ -529,7 +555,7 @@ public class {{.Type}} {
|
||||||
{{if gt (len .Normalized.Outputs) 1}}
|
{{if gt (len .Normalized.Outputs) 1}}
|
||||||
// {{capitalise .Normalized.Name}}Results is the output of a call to {{.Normalized.Name}}.
|
// {{capitalise .Normalized.Name}}Results is the output of a call to {{.Normalized.Name}}.
|
||||||
public class {{capitalise .Normalized.Name}}Results {
|
public class {{capitalise .Normalized.Name}}Results {
|
||||||
{{range $index, $item := .Normalized.Outputs}}public {{bindtype .Type}} {{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}};
|
{{range $index, $item := .Normalized.Outputs}}public {{bindtype .Type $structs}} {{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}};
|
||||||
{{end}}
|
{{end}}
|
||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -537,13 +563,13 @@ public class {{.Type}} {
|
||||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||||
//
|
//
|
||||||
// Solidity: {{.Original.String}}
|
// Solidity: {{.Original.String}}
|
||||||
public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else}}{{range .Normalized.Outputs}}{{bindtype .Type}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception {
|
public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception {
|
||||||
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
|
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
|
||||||
{{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
|
{{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
Interfaces results = Geth.newInterfaces({{(len .Normalized.Outputs)}});
|
Interfaces results = Geth.newInterfaces({{(len .Normalized.Outputs)}});
|
||||||
{{range $index, $item := .Normalized.Outputs}}Interface result{{$index}} = Geth.newInterface(); result{{$index}}.setDefault{{namedtype (bindtype .Type) .Type}}(); results.set({{$index}}, result{{$index}});
|
{{range $index, $item := .Normalized.Outputs}}Interface result{{$index}} = Geth.newInterface(); result{{$index}}.setDefault{{namedtype (bindtype .Type $structs) .Type}}(); results.set({{$index}}, result{{$index}});
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
if (opts == null) {
|
if (opts == null) {
|
||||||
|
|
@ -552,10 +578,10 @@ public class {{.Type}} {
|
||||||
this.Contract.call(opts, results, "{{.Original.Name}}", args);
|
this.Contract.call(opts, results, "{{.Original.Name}}", args);
|
||||||
{{if gt (len .Normalized.Outputs) 1}}
|
{{if gt (len .Normalized.Outputs) 1}}
|
||||||
{{capitalise .Normalized.Name}}Results result = new {{capitalise .Normalized.Name}}Results();
|
{{capitalise .Normalized.Name}}Results result = new {{capitalise .Normalized.Name}}Results();
|
||||||
{{range $index, $item := .Normalized.Outputs}}result.{{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}} = results.get({{$index}}).get{{namedtype (bindtype .Type) .Type}}();
|
{{range $index, $item := .Normalized.Outputs}}result.{{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}} = results.get({{$index}}).get{{namedtype (bindtype .Type $structs) .Type}}();
|
||||||
{{end}}
|
{{end}}
|
||||||
return result;
|
return result;
|
||||||
{{else}}{{range .Normalized.Outputs}}return results.get(0).get{{namedtype (bindtype .Type) .Type}}();{{end}}
|
{{else}}{{range .Normalized.Outputs}}return results.get(0).get{{namedtype (bindtype .Type $structs) .Type}}();{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -564,9 +590,9 @@ public class {{.Type}} {
|
||||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||||
//
|
//
|
||||||
// Solidity: {{.Original.String}}
|
// Solidity: {{.Original.String}}
|
||||||
public Transaction {{.Normalized.Name}}(TransactOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception {
|
public Transaction {{.Normalized.Name}}(TransactOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception {
|
||||||
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
|
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
|
||||||
{{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
|
{{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
|
||||||
{{end}}
|
{{end}}
|
||||||
return this.Contract.transact(opts, "{{.Original.Name}}" , args);
|
return this.Contract.transact(opts, "{{.Original.Name}}" , args);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,11 @@ const methoddata = `
|
||||||
[
|
[
|
||||||
{"type": "function", "name": "balance", "constant": true },
|
{"type": "function", "name": "balance", "constant": true },
|
||||||
{"type": "function", "name": "send", "constant": false, "inputs": [{ "name": "amount", "type": "uint256" }]},
|
{"type": "function", "name": "send", "constant": false, "inputs": [{ "name": "amount", "type": "uint256" }]},
|
||||||
{ "type" : "function", "name" : "transfer", "constant" : false, "inputs" : [ { "name" : "from", "type" : "address" }, { "name" : "to", "type" : "address" }, { "name" : "value", "type" : "uint256" } ], "outputs" : [ { "name" : "success", "type" : "bool" } ] }
|
{"type": "function", "name": "transfer", "constant": false, "inputs": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}], "outputs": [{"name": "success", "type": "bool"}]},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple"}],"name":"tuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[]"}],"name":"tupleSlice","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5]"}],"name":"tupleArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5][]"}],"name":"complexTuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}
|
||||||
]`
|
]`
|
||||||
|
|
||||||
func TestMethodString(t *testing.T) {
|
func TestMethodString(t *testing.T) {
|
||||||
|
|
@ -45,6 +49,22 @@ func TestMethodString(t *testing.T) {
|
||||||
method: "transfer",
|
method: "transfer",
|
||||||
expectation: "function transfer(address from, address to, uint256 value) returns(bool success)",
|
expectation: "function transfer(address from, address to, uint256 value) returns(bool success)",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: "tuple",
|
||||||
|
expectation: "function tuple((uint256,uint256) a) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleArray",
|
||||||
|
expectation: "function tupleArray((uint256,uint256)[5] a) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleSlice",
|
||||||
|
expectation: "function tupleSlice((uint256,uint256)[] a) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "complexTuple",
|
||||||
|
expectation: "function complexTuple((uint256,uint256)[5][] a) returns()",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(methoddata))
|
abi, err := JSON(strings.NewReader(methoddata))
|
||||||
|
|
@ -59,3 +79,50 @@ func TestMethodString(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMethodSig(t *testing.T) {
|
||||||
|
var cases = []struct {
|
||||||
|
method string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
method: "balance",
|
||||||
|
expect: "balance()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "send",
|
||||||
|
expect: "send(uint256)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "transfer",
|
||||||
|
expect: "transfer(address,address,uint256)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tuple",
|
||||||
|
expect: "tuple((uint256,uint256))",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleArray",
|
||||||
|
expect: "tupleArray((uint256,uint256)[5])",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleSlice",
|
||||||
|
expect: "tupleSlice((uint256,uint256)[])",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "complexTuple",
|
||||||
|
expect: "complexTuple((uint256,uint256)[5][])",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
abi, err := JSON(strings.NewReader(methoddata))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range cases {
|
||||||
|
got := abi.Methods[test.method].Sig()
|
||||||
|
if got != test.expect {
|
||||||
|
t.Errorf("expected string to be %s, got %s", test.expect, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,14 @@ func indirect(v reflect.Value) reflect.Value {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// indirectInterfaceOrPtr recursively dereferences the value until value is not interface.
|
||||||
|
func indirectInterfaceOrPtr(v reflect.Value) reflect.Value {
|
||||||
|
if (v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr) && v.Elem().IsValid() {
|
||||||
|
return indirect(v.Elem())
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
// reflectIntKind returns the reflect using the given size and
|
// reflectIntKind returns the reflect using the given size and
|
||||||
// unsignedness.
|
// unsignedness.
|
||||||
func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type) {
|
func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type) {
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,6 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
|
||||||
if strings.Count(t, "[") != strings.Count(t, "]") {
|
if strings.Count(t, "[") != strings.Count(t, "]") {
|
||||||
return Type{}, fmt.Errorf("invalid arg type in abi")
|
return Type{}, fmt.Errorf("invalid arg type in abi")
|
||||||
}
|
}
|
||||||
|
|
||||||
typ.stringKind = t
|
typ.stringKind = t
|
||||||
|
|
||||||
// if there are brackets, get ready to go into slice/array mode and
|
// if there are brackets, get ready to go into slice/array mode and
|
||||||
|
|
@ -92,9 +91,7 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
|
||||||
typ.Kind = reflect.Slice
|
typ.Kind = reflect.Slice
|
||||||
typ.Elem = &embeddedType
|
typ.Elem = &embeddedType
|
||||||
typ.Type = reflect.SliceOf(embeddedType.Type)
|
typ.Type = reflect.SliceOf(embeddedType.Type)
|
||||||
if embeddedType.T == TupleTy {
|
|
||||||
typ.stringKind = embeddedType.stringKind + sliced
|
typ.stringKind = embeddedType.stringKind + sliced
|
||||||
}
|
|
||||||
} else if len(intz) == 1 {
|
} else if len(intz) == 1 {
|
||||||
// is a array
|
// is a array
|
||||||
typ.T = ArrayTy
|
typ.T = ArrayTy
|
||||||
|
|
@ -105,9 +102,7 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
|
||||||
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||||
}
|
}
|
||||||
typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type)
|
typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type)
|
||||||
if embeddedType.T == TupleTy {
|
|
||||||
typ.stringKind = embeddedType.stringKind + sliced
|
typ.stringKind = embeddedType.stringKind + sliced
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return Type{}, fmt.Errorf("invalid formatting of array type")
|
return Type{}, fmt.Errorf("invalid formatting of array type")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -965,25 +965,21 @@ func TestUnpackTuple(t *testing.T) {
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // ret[a] = 1
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // ret[a] = 1
|
||||||
buff.Write(common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) // ret[b] = -1
|
buff.Write(common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) // ret[b] = -1
|
||||||
|
|
||||||
|
// If the result is single tuple, use struct as return value container directly.
|
||||||
v := struct {
|
v := struct {
|
||||||
Ret struct {
|
|
||||||
A *big.Int
|
A *big.Int
|
||||||
B *big.Int
|
B *big.Int
|
||||||
}
|
}{new(big.Int), new(big.Int)}
|
||||||
}{Ret: struct {
|
|
||||||
A *big.Int
|
|
||||||
B *big.Int
|
|
||||||
}{new(big.Int), new(big.Int)}}
|
|
||||||
|
|
||||||
err = abi.Unpack(&v, "tuple", buff.Bytes())
|
err = abi.Unpack(&v, "tuple", buff.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
} else {
|
} else {
|
||||||
if v.Ret.A.Cmp(big.NewInt(1)) != 0 {
|
if v.A.Cmp(big.NewInt(1)) != 0 {
|
||||||
t.Errorf("unexpected value unpacked: want %x, got %x", 1, v.Ret.A)
|
t.Errorf("unexpected value unpacked: want %x, got %x", 1, v.A)
|
||||||
}
|
}
|
||||||
if v.Ret.B.Cmp(big.NewInt(-1)) != 0 {
|
if v.B.Cmp(big.NewInt(-1)) != 0 {
|
||||||
t.Errorf("unexpected value unpacked: want %x, got %x", v.Ret.B, -1)
|
t.Errorf("unexpected value unpacked: want %x, got %x", v.B, -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
9
accounts/external/backend.go
vendored
9
accounts/external/backend.go
vendored
|
|
@ -182,18 +182,21 @@ func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]by
|
||||||
|
|
||||||
func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
res := ethapi.SignTransactionResult{}
|
res := ethapi.SignTransactionResult{}
|
||||||
to := common.NewMixedcaseAddress(*tx.To())
|
|
||||||
data := hexutil.Bytes(tx.Data())
|
data := hexutil.Bytes(tx.Data())
|
||||||
|
var to *common.MixedcaseAddress
|
||||||
|
if tx.To() != nil {
|
||||||
|
t := common.NewMixedcaseAddress(*tx.To())
|
||||||
|
to = &t
|
||||||
|
}
|
||||||
args := &core.SendTxArgs{
|
args := &core.SendTxArgs{
|
||||||
Data: &data,
|
Data: &data,
|
||||||
Nonce: hexutil.Uint64(tx.Nonce()),
|
Nonce: hexutil.Uint64(tx.Nonce()),
|
||||||
Value: hexutil.Big(*tx.Value()),
|
Value: hexutil.Big(*tx.Value()),
|
||||||
Gas: hexutil.Uint64(tx.Gas()),
|
Gas: hexutil.Uint64(tx.Gas()),
|
||||||
GasPrice: hexutil.Big(*tx.GasPrice()),
|
GasPrice: hexutil.Big(*tx.GasPrice()),
|
||||||
To: &to,
|
To: to,
|
||||||
From: common.NewMixedcaseAddress(account.Address),
|
From: common.NewMixedcaseAddress(account.Address),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
|
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,8 @@ func main() {
|
||||||
lang = bind.LangGo
|
lang = bind.LangGo
|
||||||
case "java":
|
case "java":
|
||||||
lang = bind.LangJava
|
lang = bind.LangJava
|
||||||
|
case "objc":
|
||||||
|
lang = bind.LangObjC
|
||||||
default:
|
default:
|
||||||
fmt.Printf("Unsupported destination language \"%s\" (--lang)\n", *langFlag)
|
fmt.Printf("Unsupported destination language \"%s\" (--lang)\n", *langFlag)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/external"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/console"
|
|
||||||
"github.com/ethereum/go-ethereum/contracts/checkpointoracle"
|
"github.com/ethereum/go-ethereum/contracts/checkpointoracle"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -111,56 +110,11 @@ func newContract(client *rpc.Client) (common.Address, *checkpointoracle.Checkpoi
|
||||||
return addr, contract
|
return addr, contract
|
||||||
}
|
}
|
||||||
|
|
||||||
// promptPassphrase prompts the user for a passphrase.
|
// newClefSigner sets up a clef backend and returns a clef transaction signer.
|
||||||
// Set confirmation to true to require the user to confirm the passphrase.
|
func newClefSigner(ctx *cli.Context) *bind.TransactOpts {
|
||||||
func promptPassphrase(confirmation bool) string {
|
clef, err := external.NewExternalSigner(ctx.String(clefURLFlag.Name))
|
||||||
passphrase, err := console.Stdin.PromptPassword("Passphrase: ")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to read passphrase: %v", err)
|
utils.Fatalf("Failed to create clef signer %v", err)
|
||||||
}
|
}
|
||||||
|
return bind.NewClefTransactor(clef, accounts.Account{Address: common.HexToAddress(ctx.String(signerFlag.Name))})
|
||||||
if confirmation {
|
|
||||||
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
|
|
||||||
}
|
|
||||||
if passphrase != confirm {
|
|
||||||
utils.Fatalf("Passphrases do not match")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return passphrase
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPassphrase obtains a passphrase given by the user. It first checks the
|
|
||||||
// --password command line flag and ultimately prompts the user for a
|
|
||||||
// passphrase.
|
|
||||||
func getPassphrase(ctx *cli.Context) string {
|
|
||||||
passphraseFile := ctx.String(utils.PasswordFileFlag.Name)
|
|
||||||
if passphraseFile != "" {
|
|
||||||
content, err := ioutil.ReadFile(passphraseFile)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to read passphrase file '%s': %v",
|
|
||||||
passphraseFile, err)
|
|
||||||
}
|
|
||||||
return strings.TrimRight(string(content), "\r\n")
|
|
||||||
}
|
|
||||||
// Otherwise prompt the user for the passphrase.
|
|
||||||
return promptPassphrase(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getKey retrieves the user key through specified key file.
|
|
||||||
func getKey(ctx *cli.Context) *keystore.Key {
|
|
||||||
// Read key from file.
|
|
||||||
keyFile := ctx.GlobalString(keyFileFlag.Name)
|
|
||||||
keyJson, err := ioutil.ReadFile(keyFile)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to read the keyfile at '%s': %v", keyFile, err)
|
|
||||||
}
|
|
||||||
// Decrypt key with passphrase.
|
|
||||||
passphrase := getPassphrase(ctx)
|
|
||||||
key, err := keystore.DecryptKey(keyJson, passphrase)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to decrypt user key '%s': %v", keyFile, err)
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"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"
|
||||||
|
|
@ -46,10 +45,9 @@ var commandDeploy = cli.Command{
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
nodeURLFlag,
|
nodeURLFlag,
|
||||||
clefURLFlag,
|
clefURLFlag,
|
||||||
|
signerFlag,
|
||||||
signersFlag,
|
signersFlag,
|
||||||
thresholdFlag,
|
thresholdFlag,
|
||||||
keyFileFlag,
|
|
||||||
utils.PasswordFileFlag,
|
|
||||||
},
|
},
|
||||||
Action: utils.MigrateFlags(deploy),
|
Action: utils.MigrateFlags(deploy),
|
||||||
}
|
}
|
||||||
|
|
@ -60,12 +58,10 @@ var commandSign = cli.Command{
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
nodeURLFlag,
|
nodeURLFlag,
|
||||||
clefURLFlag,
|
clefURLFlag,
|
||||||
|
signerFlag,
|
||||||
indexFlag,
|
indexFlag,
|
||||||
hashFlag,
|
hashFlag,
|
||||||
oracleFlag,
|
oracleFlag,
|
||||||
keyFileFlag,
|
|
||||||
signerFlag,
|
|
||||||
utils.PasswordFileFlag,
|
|
||||||
},
|
},
|
||||||
Action: utils.MigrateFlags(sign),
|
Action: utils.MigrateFlags(sign),
|
||||||
}
|
}
|
||||||
|
|
@ -75,10 +71,10 @@ var commandPublish = cli.Command{
|
||||||
Usage: "Publish a checkpoint into the oracle",
|
Usage: "Publish a checkpoint into the oracle",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
nodeURLFlag,
|
nodeURLFlag,
|
||||||
|
clefURLFlag,
|
||||||
|
signerFlag,
|
||||||
indexFlag,
|
indexFlag,
|
||||||
signaturesFlag,
|
signaturesFlag,
|
||||||
keyFileFlag,
|
|
||||||
utils.PasswordFileFlag,
|
|
||||||
},
|
},
|
||||||
Action: utils.MigrateFlags(publish),
|
Action: utils.MigrateFlags(publish),
|
||||||
}
|
}
|
||||||
|
|
@ -108,11 +104,11 @@ func deploy(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
fmt.Printf("\nSignatures needed to publish: %d\n", needed)
|
fmt.Printf("\nSignatures needed to publish: %d\n", needed)
|
||||||
|
|
||||||
// Retrieve the private key, create an abigen transactor and an RPC client
|
// setup clef signer, create an abigen transactor and an RPC client
|
||||||
transactor := bind.NewKeyedTransactor(getKey(ctx).PrivateKey)
|
transactor, client := newClefSigner(ctx), newClient(ctx)
|
||||||
client := newClient(ctx)
|
|
||||||
|
|
||||||
// Deploy the checkpoint oracle
|
// Deploy the checkpoint oracle
|
||||||
|
fmt.Println("Sending deploy request to Clef...")
|
||||||
oracle, tx, _, err := contract.DeployCheckpointOracle(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)),
|
oracle, tx, _, err := contract.DeployCheckpointOracle(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)),
|
||||||
big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed)))
|
big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -158,9 +154,7 @@ func sign(ctx *cli.Context) error {
|
||||||
node = newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
|
node = newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
|
||||||
|
|
||||||
checkpoint := getCheckpoint(ctx, node)
|
checkpoint := getCheckpoint(ctx, node)
|
||||||
chash = checkpoint.Hash()
|
chash, cindex, address = checkpoint.Hash(), checkpoint.SectionIndex, getContractAddr(node)
|
||||||
cindex = checkpoint.SectionIndex
|
|
||||||
address = getContractAddr(node)
|
|
||||||
|
|
||||||
// Check the validity of checkpoint
|
// Check the validity of checkpoint
|
||||||
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
|
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
|
@ -207,8 +201,6 @@ func sign(ctx *cli.Context) error {
|
||||||
fmt.Printf("Oracle => %s\n", address.Hex())
|
fmt.Printf("Oracle => %s\n", address.Hex())
|
||||||
fmt.Printf("Index %4d => %s\n", cindex, chash.Hex())
|
fmt.Printf("Index %4d => %s\n", cindex, chash.Hex())
|
||||||
|
|
||||||
switch {
|
|
||||||
case ctx.GlobalIsSet(clefURLFlag.Name):
|
|
||||||
// Sign checkpoint in clef mode.
|
// Sign checkpoint in clef mode.
|
||||||
signer = ctx.String(signerFlag.Name)
|
signer = ctx.String(signerFlag.Name)
|
||||||
|
|
||||||
|
|
@ -217,34 +209,17 @@ func sign(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clef := newRPCClient(ctx.GlobalString(clefURLFlag.Name))
|
clef := newRPCClient(ctx.String(clefURLFlag.Name))
|
||||||
p := make(map[string]string)
|
p := make(map[string]string)
|
||||||
buf := make([]byte, 8)
|
buf := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(buf, cindex)
|
binary.BigEndian.PutUint64(buf, cindex)
|
||||||
p["address"] = address.Hex()
|
p["address"] = address.Hex()
|
||||||
p["message"] = hexutil.Encode(append(buf, chash.Bytes()...))
|
p["message"] = hexutil.Encode(append(buf, chash.Bytes()...))
|
||||||
|
|
||||||
|
fmt.Println("Sending signing request to Clef...")
|
||||||
if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil {
|
if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil {
|
||||||
utils.Fatalf("Failed to sign checkpoint, err %v", err)
|
utils.Fatalf("Failed to sign checkpoint, err %v", err)
|
||||||
}
|
}
|
||||||
case ctx.GlobalIsSet(keyFileFlag.Name):
|
|
||||||
// Sign checkpoint in raw private key file mode.
|
|
||||||
key := getKey(ctx)
|
|
||||||
signer = key.Address.Hex()
|
|
||||||
|
|
||||||
if !offline {
|
|
||||||
if err := isAdmin(key.Address); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sig, err := crypto.Sign(sighash(cindex, address, chash), key.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to sign checkpoint, err %v", err)
|
|
||||||
}
|
|
||||||
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
|
||||||
signature = common.Bytes2Hex(sig)
|
|
||||||
default:
|
|
||||||
utils.Fatalf("Please specify clef URL or private key file path to sign checkpoint")
|
|
||||||
}
|
|
||||||
fmt.Printf("Signer => %s\n", signer)
|
fmt.Printf("Signer => %s\n", signer)
|
||||||
fmt.Printf("Signature => %s\n", signature)
|
fmt.Printf("Signature => %s\n", signature)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -326,7 +301,8 @@ func publish(ctx *cli.Context) error {
|
||||||
fmt.Printf("Sentry number => %d\nSentry hash => %s\n", recent.Number, recent.Hash().Hex())
|
fmt.Printf("Sentry number => %d\nSentry hash => %s\n", recent.Number, recent.Hash().Hex())
|
||||||
|
|
||||||
// Publish the checkpoint into the oracle
|
// Publish the checkpoint into the oracle
|
||||||
tx, err := oracle.RegisterCheckpoint(getKey(ctx).PrivateKey, checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs)
|
fmt.Println("Sending publish request to Clef...")
|
||||||
|
tx, err := oracle.RegisterCheckpoint(newClefSigner(ctx), checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Register contract failed %v", err)
|
utils.Fatalf("Register contract failed %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,7 @@ func init() {
|
||||||
}
|
}
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
oracleFlag,
|
oracleFlag,
|
||||||
keyFileFlag,
|
|
||||||
nodeURLFlag,
|
nodeURLFlag,
|
||||||
clefURLFlag,
|
|
||||||
utils.PasswordFileFlag,
|
|
||||||
}
|
}
|
||||||
cli.CommandHelpTemplate = commandHelperTemplate
|
cli.CommandHelpTemplate = commandHelperTemplate
|
||||||
}
|
}
|
||||||
|
|
@ -85,10 +82,6 @@ var (
|
||||||
Name: "threshold",
|
Name: "threshold",
|
||||||
Usage: "Minimal number of signatures required to approve a checkpoint",
|
Usage: "Minimal number of signatures required to approve a checkpoint",
|
||||||
}
|
}
|
||||||
keyFileFlag = cli.StringFlag{
|
|
||||||
Name: "keyfile",
|
|
||||||
Usage: "The private key file (keyfile signature is not recommended)",
|
|
||||||
}
|
|
||||||
nodeURLFlag = cli.StringFlag{
|
nodeURLFlag = cli.StringFlag{
|
||||||
Name: "rpc",
|
Name: "rpc",
|
||||||
Value: "http://localhost:8545",
|
Value: "http://localhost:8545",
|
||||||
|
|
@ -101,7 +94,7 @@ var (
|
||||||
}
|
}
|
||||||
signerFlag = cli.StringFlag{
|
signerFlag = cli.StringFlag{
|
||||||
Name: "signer",
|
Name: "signer",
|
||||||
Usage: "Signer address for clef mode signing",
|
Usage: "Signer address for clef signing",
|
||||||
}
|
}
|
||||||
signersFlag = cli.StringFlag{
|
signersFlag = cli.StringFlag{
|
||||||
Name: "signers",
|
Name: "signers",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ package checkpointoracle
|
||||||
//go:generate abigen --sol contract/oracle.sol --pkg contract --out contract/oracle.go
|
//go:generate abigen --sol contract/oracle.sol --pkg contract --out contract/oracle.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
|
@ -73,7 +72,7 @@ func (oracle *CheckpointOracle) LookupCheckpointEvents(blockLogs [][]*types.Log,
|
||||||
//
|
//
|
||||||
// Notably all signatures given should be transformed to "ethereum style" which transforms
|
// Notably all signatures given should be transformed to "ethereum style" which transforms
|
||||||
// v from 0/1 to 27/28 according to the yellow paper.
|
// v from 0/1 to 27/28 according to the yellow paper.
|
||||||
func (oracle *CheckpointOracle) RegisterCheckpoint(key *ecdsa.PrivateKey, index uint64, hash []byte, rnum *big.Int, rhash [32]byte, sigs [][]byte) (*types.Transaction, error) {
|
func (oracle *CheckpointOracle) RegisterCheckpoint(opts *bind.TransactOpts, index uint64, hash []byte, rnum *big.Int, rhash [32]byte, sigs [][]byte) (*types.Transaction, error) {
|
||||||
var (
|
var (
|
||||||
r [][32]byte
|
r [][32]byte
|
||||||
s [][32]byte
|
s [][32]byte
|
||||||
|
|
@ -87,5 +86,5 @@ func (oracle *CheckpointOracle) RegisterCheckpoint(key *ecdsa.PrivateKey, index
|
||||||
s = append(s, common.BytesToHash(sigs[i][32:64]))
|
s = append(s, common.BytesToHash(sigs[i][32:64]))
|
||||||
v = append(v, sigs[i][64])
|
v = append(v, sigs[i][64])
|
||||||
}
|
}
|
||||||
return oracle.contract.SetCheckpoint(bind.NewKeyedTransactor(key), rnum, rhash, common.BytesToHash(hash), index, v, r, s)
|
return oracle.contract.SetCheckpoint(opts, rnum, rhash, common.BytesToHash(hash), index, v, r, s)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -930,6 +930,8 @@ func (bc *BlockChain) truncateAncient(head uint64) error {
|
||||||
// InsertReceiptChain attempts to complete an already existing header chain with
|
// InsertReceiptChain attempts to complete an already existing header chain with
|
||||||
// transaction and receipt data.
|
// transaction and receipt data.
|
||||||
func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts, ancientLimit uint64) (int, error) {
|
func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts, ancientLimit uint64) (int, error) {
|
||||||
|
// We don't require the chainMu here since we want to maximize the
|
||||||
|
// concurrency of header insertion and receipt insertion.
|
||||||
bc.wg.Add(1)
|
bc.wg.Add(1)
|
||||||
defer bc.wg.Done()
|
defer bc.wg.Done()
|
||||||
|
|
||||||
|
|
@ -962,19 +964,21 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
|
||||||
// updateHead updates the head fast sync block if the inserted blocks are better
|
// updateHead updates the head fast sync block if the inserted blocks are better
|
||||||
// and returns a indicator whether the inserted blocks are canonical.
|
// and returns a indicator whether the inserted blocks are canonical.
|
||||||
updateHead := func(head *types.Block) bool {
|
updateHead := func(head *types.Block) bool {
|
||||||
var isCanonical bool
|
|
||||||
bc.chainmu.Lock()
|
bc.chainmu.Lock()
|
||||||
if td := bc.GetTd(head.Hash(), head.NumberU64()); td != nil { // Rewind may have occurred, skip in that case
|
|
||||||
currentFastBlock := bc.CurrentFastBlock()
|
// Rewind may have occurred, skip in that case.
|
||||||
|
if bc.CurrentHeader().Number.Cmp(head.Number()) >= 0 {
|
||||||
|
currentFastBlock, td := bc.CurrentFastBlock(), bc.GetTd(head.Hash(), head.NumberU64())
|
||||||
if bc.GetTd(currentFastBlock.Hash(), currentFastBlock.NumberU64()).Cmp(td) < 0 {
|
if bc.GetTd(currentFastBlock.Hash(), currentFastBlock.NumberU64()).Cmp(td) < 0 {
|
||||||
rawdb.WriteHeadFastBlockHash(bc.db, head.Hash())
|
rawdb.WriteHeadFastBlockHash(bc.db, head.Hash())
|
||||||
bc.currentFastBlock.Store(head)
|
bc.currentFastBlock.Store(head)
|
||||||
headFastBlockGauge.Update(int64(head.NumberU64()))
|
headFastBlockGauge.Update(int64(head.NumberU64()))
|
||||||
isCanonical = true
|
bc.chainmu.Unlock()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bc.chainmu.Unlock()
|
bc.chainmu.Unlock()
|
||||||
return isCanonical
|
return false
|
||||||
}
|
}
|
||||||
// writeAncient writes blockchain and corresponding receipt chain into ancient store.
|
// writeAncient writes blockchain and corresponding receipt chain into ancient store.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -418,7 +418,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
|
||||||
// actual canonical chain and rolls back reorged sections if necessary to ensure that stored
|
// actual canonical chain and rolls back reorged sections if necessary to ensure that stored
|
||||||
// sections are all valid
|
// sections are all valid
|
||||||
func (c *ChainIndexer) verifyLastHead() {
|
func (c *ChainIndexer) verifyLastHead() {
|
||||||
for c.storedSections > 0 {
|
for c.storedSections > 0 && c.storedSections > c.checkpointSections {
|
||||||
if c.SectionHead(c.storedSections-1) == rawdb.ReadCanonicalHash(c.chainDb, c.storedSections*c.sectionSize-1) {
|
if c.SectionHead(c.storedSections-1) == rawdb.ReadCanonicalHash(c.chainDb, c.storedSections*c.sectionSize-1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
456
les/api.go
456
les/api.go
|
|
@ -17,462 +17,16 @@
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
|
||||||
"github.com/ethereum/go-ethereum/les/csvlogger"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrMinCap = errors.New("capacity too small")
|
errNoCheckpoint = errors.New("no local checkpoint provided")
|
||||||
ErrTotalCap = errors.New("total capacity exceeded")
|
errNotActivated = errors.New("checkpoint registrar is not activated")
|
||||||
ErrUnknownBenchmarkType = errors.New("unknown benchmark type")
|
|
||||||
ErrNoCheckpoint = errors.New("no local checkpoint provided")
|
|
||||||
ErrNotActivated = errors.New("checkpoint registrar is not activated")
|
|
||||||
|
|
||||||
dropCapacityDelay = time.Second // delay applied to decreasing capacity changes
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrivateLightServerAPI provides an API to access the LES light server.
|
|
||||||
// It offers only methods that operate on public data that is freely available to anyone.
|
|
||||||
type PrivateLightServerAPI struct {
|
|
||||||
server *LesServer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPrivateLightServerAPI creates a new LES light server API.
|
|
||||||
func NewPrivateLightServerAPI(server *LesServer) *PrivateLightServerAPI {
|
|
||||||
return &PrivateLightServerAPI{
|
|
||||||
server: server,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TotalCapacity queries total available capacity for all clients
|
|
||||||
func (api *PrivateLightServerAPI) TotalCapacity() hexutil.Uint64 {
|
|
||||||
return hexutil.Uint64(api.server.priorityClientPool.totalCapacity())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeTotalCapacity subscribes to changed total capacity events.
|
|
||||||
// If onlyUnderrun is true then notification is sent only if the total capacity
|
|
||||||
// drops under the total capacity of connected priority clients.
|
|
||||||
//
|
|
||||||
// Note: actually applying decreasing total capacity values is delayed while the
|
|
||||||
// notification is sent instantly. This allows lowering the capacity of a priority client
|
|
||||||
// or choosing which one to drop before the system drops some of them automatically.
|
|
||||||
func (api *PrivateLightServerAPI) SubscribeTotalCapacity(ctx context.Context, onlyUnderrun bool) (*rpc.Subscription, error) {
|
|
||||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
|
||||||
if !supported {
|
|
||||||
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
|
|
||||||
}
|
|
||||||
rpcSub := notifier.CreateSubscription()
|
|
||||||
api.server.priorityClientPool.subscribeTotalCapacity(&tcSubscription{notifier, rpcSub, onlyUnderrun})
|
|
||||||
return rpcSub, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
// tcSubscription represents a total capacity subscription
|
|
||||||
tcSubscription struct {
|
|
||||||
notifier *rpc.Notifier
|
|
||||||
rpcSub *rpc.Subscription
|
|
||||||
onlyUnderrun bool
|
|
||||||
}
|
|
||||||
tcSubs map[*tcSubscription]struct{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// send sends a changed total capacity event to the subscribers
|
|
||||||
func (s tcSubs) send(tc uint64, underrun bool) {
|
|
||||||
for sub := range s {
|
|
||||||
select {
|
|
||||||
case <-sub.rpcSub.Err():
|
|
||||||
delete(s, sub)
|
|
||||||
case <-sub.notifier.Closed():
|
|
||||||
delete(s, sub)
|
|
||||||
default:
|
|
||||||
if underrun || !sub.onlyUnderrun {
|
|
||||||
sub.notifier.Notify(sub.rpcSub.ID, tc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinimumCapacity queries minimum assignable capacity for a single client
|
|
||||||
func (api *PrivateLightServerAPI) MinimumCapacity() hexutil.Uint64 {
|
|
||||||
return hexutil.Uint64(api.server.minCapacity)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FreeClientCapacity queries the capacity provided for free clients
|
|
||||||
func (api *PrivateLightServerAPI) FreeClientCapacity() hexutil.Uint64 {
|
|
||||||
return hexutil.Uint64(api.server.freeClientCap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClientCapacity sets the priority capacity assigned to a given client.
|
|
||||||
// If the assigned capacity is bigger than zero then connection is always
|
|
||||||
// guaranteed. The sum of capacity assigned to priority clients can not exceed
|
|
||||||
// the total available capacity.
|
|
||||||
//
|
|
||||||
// Note: assigned capacity can be changed while the client is connected with
|
|
||||||
// immediate effect.
|
|
||||||
func (api *PrivateLightServerAPI) SetClientCapacity(id enode.ID, cap uint64) error {
|
|
||||||
if cap != 0 && cap < api.server.minCapacity {
|
|
||||||
return ErrMinCap
|
|
||||||
}
|
|
||||||
return api.server.priorityClientPool.setClientCapacity(id, cap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClientCapacity returns the capacity assigned to a given client
|
|
||||||
func (api *PrivateLightServerAPI) GetClientCapacity(id enode.ID) hexutil.Uint64 {
|
|
||||||
api.server.priorityClientPool.lock.Lock()
|
|
||||||
defer api.server.priorityClientPool.lock.Unlock()
|
|
||||||
|
|
||||||
return hexutil.Uint64(api.server.priorityClientPool.clients[id].cap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// clientPool is implemented by both the free and priority client pools
|
|
||||||
type clientPool interface {
|
|
||||||
peerSetNotify
|
|
||||||
setLimits(count int, totalCap uint64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// priorityClientPool stores information about prioritized clients
|
|
||||||
type priorityClientPool struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
child clientPool
|
|
||||||
ps *peerSet
|
|
||||||
clients map[enode.ID]priorityClientInfo
|
|
||||||
totalCap, totalCapAnnounced uint64
|
|
||||||
totalConnectedCap, freeClientCap uint64
|
|
||||||
maxPeers, priorityCount int
|
|
||||||
logger *csvlogger.Logger
|
|
||||||
logTotalPriConn *csvlogger.Channel
|
|
||||||
|
|
||||||
subs tcSubs
|
|
||||||
updateSchedule []scheduledUpdate
|
|
||||||
scheduleCounter uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// scheduledUpdate represents a delayed total capacity update
|
|
||||||
type scheduledUpdate struct {
|
|
||||||
time mclock.AbsTime
|
|
||||||
totalCap, id uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// priorityClientInfo entries exist for all prioritized clients and currently connected non-priority clients
|
|
||||||
type priorityClientInfo struct {
|
|
||||||
cap uint64 // zero for non-priority clients
|
|
||||||
connected bool
|
|
||||||
peer *peer
|
|
||||||
}
|
|
||||||
|
|
||||||
// newPriorityClientPool creates a new priority client pool
|
|
||||||
func newPriorityClientPool(freeClientCap uint64, ps *peerSet, child clientPool, metricsLogger, eventLogger *csvlogger.Logger) *priorityClientPool {
|
|
||||||
return &priorityClientPool{
|
|
||||||
clients: make(map[enode.ID]priorityClientInfo),
|
|
||||||
freeClientCap: freeClientCap,
|
|
||||||
ps: ps,
|
|
||||||
child: child,
|
|
||||||
logger: eventLogger,
|
|
||||||
logTotalPriConn: metricsLogger.NewChannel("totalPriConn", 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerPeer is called when a new client is connected. If the client has no
|
|
||||||
// priority assigned then it is passed to the child pool which may either keep it
|
|
||||||
// or disconnect it.
|
|
||||||
//
|
|
||||||
// Note: priorityClientPool also stores a record about free clients while they are
|
|
||||||
// connected in order to be able to assign priority to them later.
|
|
||||||
func (v *priorityClientPool) registerPeer(p *peer) {
|
|
||||||
v.lock.Lock()
|
|
||||||
defer v.lock.Unlock()
|
|
||||||
|
|
||||||
id := p.ID()
|
|
||||||
c := v.clients[id]
|
|
||||||
v.logger.Event(fmt.Sprintf("priorityClientPool: registerPeer cap=%d connected=%v, %x", c.cap, c.connected, id.Bytes()))
|
|
||||||
if c.connected {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.cap == 0 && v.child != nil {
|
|
||||||
v.child.registerPeer(p)
|
|
||||||
}
|
|
||||||
if c.cap != 0 && v.totalConnectedCap+c.cap > v.totalCap {
|
|
||||||
v.logger.Event(fmt.Sprintf("priorityClientPool: rejected, %x", id.Bytes()))
|
|
||||||
go v.ps.Unregister(p.id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.connected = true
|
|
||||||
c.peer = p
|
|
||||||
v.clients[id] = c
|
|
||||||
if c.cap != 0 {
|
|
||||||
v.priorityCount++
|
|
||||||
v.totalConnectedCap += c.cap
|
|
||||||
v.logger.Event(fmt.Sprintf("priorityClientPool: accepted with %d capacity, %x", c.cap, id.Bytes()))
|
|
||||||
v.logTotalPriConn.Update(float64(v.totalConnectedCap))
|
|
||||||
if v.child != nil {
|
|
||||||
v.child.setLimits(v.maxPeers-v.priorityCount, v.totalCap-v.totalConnectedCap)
|
|
||||||
}
|
|
||||||
p.updateCapacity(c.cap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unregisterPeer is called when a client is disconnected. If the client has no
|
|
||||||
// priority assigned then it is also removed from the child pool.
|
|
||||||
func (v *priorityClientPool) unregisterPeer(p *peer) {
|
|
||||||
v.lock.Lock()
|
|
||||||
defer v.lock.Unlock()
|
|
||||||
|
|
||||||
id := p.ID()
|
|
||||||
c := v.clients[id]
|
|
||||||
v.logger.Event(fmt.Sprintf("priorityClientPool: unregisterPeer cap=%d connected=%v, %x", c.cap, c.connected, id.Bytes()))
|
|
||||||
if !c.connected {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.cap != 0 {
|
|
||||||
c.connected = false
|
|
||||||
v.clients[id] = c
|
|
||||||
v.priorityCount--
|
|
||||||
v.totalConnectedCap -= c.cap
|
|
||||||
v.logTotalPriConn.Update(float64(v.totalConnectedCap))
|
|
||||||
if v.child != nil {
|
|
||||||
v.child.setLimits(v.maxPeers-v.priorityCount, v.totalCap-v.totalConnectedCap)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if v.child != nil {
|
|
||||||
v.child.unregisterPeer(p)
|
|
||||||
}
|
|
||||||
delete(v.clients, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setLimits updates the allowed peer count and total capacity of the priority
|
|
||||||
// client pool. Since the free client pool is a child of the priority pool the
|
|
||||||
// remaining peer count and capacity is assigned to the free pool by calling its
|
|
||||||
// own setLimits function.
|
|
||||||
//
|
|
||||||
// Note: a decreasing change of the total capacity is applied with a delay.
|
|
||||||
func (v *priorityClientPool) setLimits(count int, totalCap uint64) {
|
|
||||||
v.lock.Lock()
|
|
||||||
defer v.lock.Unlock()
|
|
||||||
|
|
||||||
v.totalCapAnnounced = totalCap
|
|
||||||
if totalCap > v.totalCap {
|
|
||||||
v.setLimitsNow(count, totalCap)
|
|
||||||
v.subs.send(totalCap, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
v.setLimitsNow(count, v.totalCap)
|
|
||||||
if totalCap < v.totalCap {
|
|
||||||
v.subs.send(totalCap, totalCap < v.totalConnectedCap)
|
|
||||||
for i, s := range v.updateSchedule {
|
|
||||||
if totalCap >= s.totalCap {
|
|
||||||
s.totalCap = totalCap
|
|
||||||
v.updateSchedule = v.updateSchedule[:i+1]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.updateSchedule = append(v.updateSchedule, scheduledUpdate{time: mclock.Now() + mclock.AbsTime(dropCapacityDelay), totalCap: totalCap})
|
|
||||||
if len(v.updateSchedule) == 1 {
|
|
||||||
v.scheduleCounter++
|
|
||||||
id := v.scheduleCounter
|
|
||||||
v.updateSchedule[0].id = id
|
|
||||||
time.AfterFunc(dropCapacityDelay, func() { v.checkUpdate(id) })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v.updateSchedule = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkUpdate performs the next scheduled update if possible and schedules
|
|
||||||
// the one after that
|
|
||||||
func (v *priorityClientPool) checkUpdate(id uint64) {
|
|
||||||
v.lock.Lock()
|
|
||||||
defer v.lock.Unlock()
|
|
||||||
|
|
||||||
if len(v.updateSchedule) == 0 || v.updateSchedule[0].id != id {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
v.setLimitsNow(v.maxPeers, v.updateSchedule[0].totalCap)
|
|
||||||
v.updateSchedule = v.updateSchedule[1:]
|
|
||||||
if len(v.updateSchedule) != 0 {
|
|
||||||
v.scheduleCounter++
|
|
||||||
id := v.scheduleCounter
|
|
||||||
v.updateSchedule[0].id = id
|
|
||||||
dt := time.Duration(v.updateSchedule[0].time - mclock.Now())
|
|
||||||
time.AfterFunc(dt, func() { v.checkUpdate(id) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setLimits updates the allowed peer count and total capacity immediately
|
|
||||||
func (v *priorityClientPool) setLimitsNow(count int, totalCap uint64) {
|
|
||||||
if v.priorityCount > count || v.totalConnectedCap > totalCap {
|
|
||||||
for id, c := range v.clients {
|
|
||||||
if c.connected {
|
|
||||||
v.logger.Event(fmt.Sprintf("priorityClientPool: setLimitsNow kicked out, %x", id.Bytes()))
|
|
||||||
c.connected = false
|
|
||||||
v.totalConnectedCap -= c.cap
|
|
||||||
v.logTotalPriConn.Update(float64(v.totalConnectedCap))
|
|
||||||
v.priorityCount--
|
|
||||||
v.clients[id] = c
|
|
||||||
go v.ps.Unregister(c.peer.id)
|
|
||||||
if v.priorityCount <= count && v.totalConnectedCap <= totalCap {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.maxPeers = count
|
|
||||||
v.totalCap = totalCap
|
|
||||||
if v.child != nil {
|
|
||||||
v.child.setLimits(v.maxPeers-v.priorityCount, v.totalCap-v.totalConnectedCap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// totalCapacity queries total available capacity for all clients
|
|
||||||
func (v *priorityClientPool) totalCapacity() uint64 {
|
|
||||||
v.lock.Lock()
|
|
||||||
defer v.lock.Unlock()
|
|
||||||
|
|
||||||
return v.totalCapAnnounced
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscribeTotalCapacity subscribes to changed total capacity events
|
|
||||||
func (v *priorityClientPool) subscribeTotalCapacity(sub *tcSubscription) {
|
|
||||||
v.lock.Lock()
|
|
||||||
defer v.lock.Unlock()
|
|
||||||
|
|
||||||
v.subs[sub] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setClientCapacity sets the priority capacity assigned to a given client
|
|
||||||
func (v *priorityClientPool) setClientCapacity(id enode.ID, cap uint64) error {
|
|
||||||
v.lock.Lock()
|
|
||||||
defer v.lock.Unlock()
|
|
||||||
|
|
||||||
c := v.clients[id]
|
|
||||||
if c.cap == cap {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if c.connected {
|
|
||||||
if v.totalConnectedCap+cap > v.totalCap+c.cap {
|
|
||||||
return ErrTotalCap
|
|
||||||
}
|
|
||||||
if c.cap == 0 {
|
|
||||||
if v.child != nil {
|
|
||||||
v.child.unregisterPeer(c.peer)
|
|
||||||
}
|
|
||||||
v.priorityCount++
|
|
||||||
}
|
|
||||||
if cap == 0 {
|
|
||||||
v.priorityCount--
|
|
||||||
}
|
|
||||||
v.totalConnectedCap += cap - c.cap
|
|
||||||
v.logTotalPriConn.Update(float64(v.totalConnectedCap))
|
|
||||||
if v.child != nil {
|
|
||||||
v.child.setLimits(v.maxPeers-v.priorityCount, v.totalCap-v.totalConnectedCap)
|
|
||||||
}
|
|
||||||
if cap == 0 {
|
|
||||||
if v.child != nil {
|
|
||||||
v.child.registerPeer(c.peer)
|
|
||||||
}
|
|
||||||
c.peer.updateCapacity(v.freeClientCap)
|
|
||||||
} else {
|
|
||||||
c.peer.updateCapacity(cap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cap != 0 || c.connected {
|
|
||||||
c.cap = cap
|
|
||||||
v.clients[id] = c
|
|
||||||
} else {
|
|
||||||
delete(v.clients, id)
|
|
||||||
}
|
|
||||||
if c.connected {
|
|
||||||
v.logger.Event(fmt.Sprintf("priorityClientPool: changed capacity to %d, %x", cap, id.Bytes()))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Benchmark runs a request performance benchmark with a given set of measurement setups
|
|
||||||
// in multiple passes specified by passCount. The measurement time for each setup in each
|
|
||||||
// pass is specified in milliseconds by length.
|
|
||||||
//
|
|
||||||
// Note: measurement time is adjusted for each pass depending on the previous ones.
|
|
||||||
// Therefore a controlled total measurement time is achievable in multiple passes.
|
|
||||||
func (api *PrivateLightServerAPI) Benchmark(setups []map[string]interface{}, passCount, length int) ([]map[string]interface{}, error) {
|
|
||||||
benchmarks := make([]requestBenchmark, len(setups))
|
|
||||||
for i, setup := range setups {
|
|
||||||
if t, ok := setup["type"].(string); ok {
|
|
||||||
getInt := func(field string, def int) int {
|
|
||||||
if value, ok := setup[field].(float64); ok {
|
|
||||||
return int(value)
|
|
||||||
}
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
getBool := func(field string, def bool) bool {
|
|
||||||
if value, ok := setup[field].(bool); ok {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
switch t {
|
|
||||||
case "header":
|
|
||||||
benchmarks[i] = &benchmarkBlockHeaders{
|
|
||||||
amount: getInt("amount", 1),
|
|
||||||
skip: getInt("skip", 1),
|
|
||||||
byHash: getBool("byHash", false),
|
|
||||||
reverse: getBool("reverse", false),
|
|
||||||
}
|
|
||||||
case "body":
|
|
||||||
benchmarks[i] = &benchmarkBodiesOrReceipts{receipts: false}
|
|
||||||
case "receipts":
|
|
||||||
benchmarks[i] = &benchmarkBodiesOrReceipts{receipts: true}
|
|
||||||
case "proof":
|
|
||||||
benchmarks[i] = &benchmarkProofsOrCode{code: false}
|
|
||||||
case "code":
|
|
||||||
benchmarks[i] = &benchmarkProofsOrCode{code: true}
|
|
||||||
case "cht":
|
|
||||||
benchmarks[i] = &benchmarkHelperTrie{
|
|
||||||
bloom: false,
|
|
||||||
reqCount: getInt("amount", 1),
|
|
||||||
}
|
|
||||||
case "bloom":
|
|
||||||
benchmarks[i] = &benchmarkHelperTrie{
|
|
||||||
bloom: true,
|
|
||||||
reqCount: getInt("amount", 1),
|
|
||||||
}
|
|
||||||
case "txSend":
|
|
||||||
benchmarks[i] = &benchmarkTxSend{}
|
|
||||||
case "txStatus":
|
|
||||||
benchmarks[i] = &benchmarkTxStatus{}
|
|
||||||
default:
|
|
||||||
return nil, ErrUnknownBenchmarkType
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, ErrUnknownBenchmarkType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rs := api.server.protocolManager.runBenchmark(benchmarks, passCount, time.Millisecond*time.Duration(length))
|
|
||||||
result := make([]map[string]interface{}, len(setups))
|
|
||||||
for i, r := range rs {
|
|
||||||
res := make(map[string]interface{})
|
|
||||||
if r.err == nil {
|
|
||||||
res["totalCount"] = r.totalCount
|
|
||||||
res["avgTime"] = r.avgTime
|
|
||||||
res["maxInSize"] = r.maxInSize
|
|
||||||
res["maxOutSize"] = r.maxOutSize
|
|
||||||
} else {
|
|
||||||
res["error"] = r.err.Error()
|
|
||||||
}
|
|
||||||
result[i] = res
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivateLightAPI provides an API to access the LES light server or light client.
|
// PrivateLightAPI provides an API to access the LES light server or light client.
|
||||||
type PrivateLightAPI struct {
|
type PrivateLightAPI struct {
|
||||||
backend *lesCommons
|
backend *lesCommons
|
||||||
|
|
@ -498,7 +52,7 @@ func (api *PrivateLightAPI) LatestCheckpoint() ([4]string, error) {
|
||||||
var res [4]string
|
var res [4]string
|
||||||
cp := api.backend.latestLocalCheckpoint()
|
cp := api.backend.latestLocalCheckpoint()
|
||||||
if cp.Empty() {
|
if cp.Empty() {
|
||||||
return res, ErrNoCheckpoint
|
return res, errNoCheckpoint
|
||||||
}
|
}
|
||||||
res[0] = hexutil.EncodeUint64(cp.SectionIndex)
|
res[0] = hexutil.EncodeUint64(cp.SectionIndex)
|
||||||
res[1], res[2], res[3] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex()
|
res[1], res[2], res[3] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex()
|
||||||
|
|
@ -515,7 +69,7 @@ func (api *PrivateLightAPI) GetCheckpoint(index uint64) ([3]string, error) {
|
||||||
var res [3]string
|
var res [3]string
|
||||||
cp := api.backend.getLocalCheckpoint(index)
|
cp := api.backend.getLocalCheckpoint(index)
|
||||||
if cp.Empty() {
|
if cp.Empty() {
|
||||||
return res, ErrNoCheckpoint
|
return res, errNoCheckpoint
|
||||||
}
|
}
|
||||||
res[0], res[1], res[2] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex()
|
res[0], res[1], res[2] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex()
|
||||||
return res, nil
|
return res, nil
|
||||||
|
|
@ -524,7 +78,7 @@ func (api *PrivateLightAPI) GetCheckpoint(index uint64) ([3]string, error) {
|
||||||
// GetCheckpointContractAddress returns the contract contract address in hex format.
|
// GetCheckpointContractAddress returns the contract contract address in hex format.
|
||||||
func (api *PrivateLightAPI) GetCheckpointContractAddress() (string, error) {
|
func (api *PrivateLightAPI) GetCheckpointContractAddress() (string, error) {
|
||||||
if api.reg == nil {
|
if api.reg == nil {
|
||||||
return "", ErrNotActivated
|
return "", errNotActivated
|
||||||
}
|
}
|
||||||
return api.reg.config.Address.Hex(), nil
|
return api.reg.config.Address.Hex(), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
@ -27,7 +26,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/les/csvlogger"
|
|
||||||
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
@ -96,40 +94,50 @@ const (
|
||||||
// as the number of cost units per nanosecond of serving time in a single thread.
|
// as the number of cost units per nanosecond of serving time in a single thread.
|
||||||
// It is based on statistics collected during serving requests in high-load periods
|
// It is based on statistics collected during serving requests in high-load periods
|
||||||
// and practically acts as a one-dimension request price scaling factor over the
|
// and practically acts as a one-dimension request price scaling factor over the
|
||||||
// pre-defined cost estimate table. Instead of scaling the cost values, the real
|
// pre-defined cost estimate table.
|
||||||
// value of cost units is changed by applying the factor to the serving times. This
|
//
|
||||||
// is more convenient because the changes in the cost factor can be applied immediately
|
// The reason for dynamically maintaining the global factor on the server side is:
|
||||||
// without always notifying the clients about the changed cost tables.
|
// the estimated time cost of the request is fixed(hardcoded) but the configuration
|
||||||
|
// of the machine running the server is really different. Therefore, the request serving
|
||||||
|
// time in different machine will vary greatly. And also, the request serving time
|
||||||
|
// in same machine may vary greatly with different request pressure.
|
||||||
|
//
|
||||||
|
// In order to more effectively limit resources, we apply the global factor to serving
|
||||||
|
// time to make the result as close as possible to the estimated time cost no matter
|
||||||
|
// the server is slow or fast. And also we scale the totalRecharge with global factor
|
||||||
|
// so that fast server can serve more requests than estimation and slow server can
|
||||||
|
// reduce request pressure.
|
||||||
|
//
|
||||||
|
// Instead of scaling the cost values, the real value of cost units is changed by
|
||||||
|
// applying the factor to the serving times. This is more convenient because the
|
||||||
|
// changes in the cost factor can be applied immediately without always notifying
|
||||||
|
// the clients about the changed cost tables.
|
||||||
type costTracker struct {
|
type costTracker struct {
|
||||||
db ethdb.Database
|
db ethdb.Database
|
||||||
stopCh chan chan struct{}
|
stopCh chan chan struct{}
|
||||||
|
|
||||||
inSizeFactor, outSizeFactor float64
|
inSizeFactor float64
|
||||||
gf, utilTarget float64
|
outSizeFactor float64
|
||||||
|
factor float64
|
||||||
|
utilTarget float64
|
||||||
minBufLimit uint64
|
minBufLimit uint64
|
||||||
|
|
||||||
gfUpdateCh chan gfUpdate
|
|
||||||
gfLock sync.RWMutex
|
gfLock sync.RWMutex
|
||||||
|
reqInfoCh chan reqInfo
|
||||||
totalRechargeCh chan uint64
|
totalRechargeCh chan uint64
|
||||||
|
|
||||||
stats map[uint64][]uint64
|
stats map[uint64][]uint64 // Used for testing purpose.
|
||||||
logger *csvlogger.Logger
|
|
||||||
logRecentTime, logRecentAvg, logTotalRecharge, logRelCost *csvlogger.Channel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCostTracker creates a cost tracker and loads the cost factor statistics from the database.
|
// newCostTracker creates a cost tracker and loads the cost factor statistics from the database.
|
||||||
// It also returns the minimum capacity that can be assigned to any peer.
|
// It also returns the minimum capacity that can be assigned to any peer.
|
||||||
func newCostTracker(db ethdb.Database, config *eth.Config, logger *csvlogger.Logger) (*costTracker, uint64) {
|
func newCostTracker(db ethdb.Database, config *eth.Config) (*costTracker, uint64) {
|
||||||
utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100
|
utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100
|
||||||
ct := &costTracker{
|
ct := &costTracker{
|
||||||
db: db,
|
db: db,
|
||||||
stopCh: make(chan chan struct{}),
|
stopCh: make(chan chan struct{}),
|
||||||
|
reqInfoCh: make(chan reqInfo, 100),
|
||||||
utilTarget: utilTarget,
|
utilTarget: utilTarget,
|
||||||
logger: logger,
|
|
||||||
logRelCost: logger.NewMinMaxChannel("relativeCost", true),
|
|
||||||
logRecentTime: logger.NewMinMaxChannel("recentTime", true),
|
|
||||||
logRecentAvg: logger.NewMinMaxChannel("recentAvg", true),
|
|
||||||
logTotalRecharge: logger.NewChannel("totalRecharge", 0.01),
|
|
||||||
}
|
}
|
||||||
if config.LightBandwidthIn > 0 {
|
if config.LightBandwidthIn > 0 {
|
||||||
ct.inSizeFactor = utilTarget / float64(config.LightBandwidthIn)
|
ct.inSizeFactor = utilTarget / float64(config.LightBandwidthIn)
|
||||||
|
|
@ -204,8 +212,15 @@ func (ct *costTracker) makeCostList(globalFactor float64) RequestCostList {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
type gfUpdate struct {
|
// reqInfo contains the estimated time cost and the actual request serving time
|
||||||
avgTimeCost, servingTime float64
|
// which acts as a feed source to update factor maintained by costTracker.
|
||||||
|
type reqInfo struct {
|
||||||
|
// avgTimeCost is the estimated time cost corresponding to maxCostTable.
|
||||||
|
avgTimeCost float64
|
||||||
|
|
||||||
|
// servingTime is the CPU time corresponding to the actual processing of
|
||||||
|
// the request.
|
||||||
|
servingTime float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// gfLoop starts an event loop which updates the global cost factor which is
|
// gfLoop starts an event loop which updates the global cost factor which is
|
||||||
|
|
@ -218,43 +233,48 @@ type gfUpdate struct {
|
||||||
// total allowed serving time per second but nominated in cost units, should
|
// total allowed serving time per second but nominated in cost units, should
|
||||||
// also be scaled with the cost factor and is also updated by this loop.
|
// also be scaled with the cost factor and is also updated by this loop.
|
||||||
func (ct *costTracker) gfLoop() {
|
func (ct *costTracker) gfLoop() {
|
||||||
var gfLog, recentTime, recentAvg float64
|
var (
|
||||||
lastUpdate := mclock.Now()
|
factor, totalRecharge float64
|
||||||
expUpdate := lastUpdate
|
gfLog, recentTime, recentAvg float64
|
||||||
|
|
||||||
|
lastUpdate, expUpdate = mclock.Now(), mclock.Now()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Load historical cost factor statistics from the database.
|
||||||
data, _ := ct.db.Get([]byte(gfDbKey))
|
data, _ := ct.db.Get([]byte(gfDbKey))
|
||||||
if len(data) == 8 {
|
if len(data) == 8 {
|
||||||
gfLog = math.Float64frombits(binary.BigEndian.Uint64(data[:]))
|
gfLog = math.Float64frombits(binary.BigEndian.Uint64(data[:]))
|
||||||
}
|
}
|
||||||
gf := math.Exp(gfLog)
|
ct.factor = math.Exp(gfLog)
|
||||||
ct.gf = gf
|
factor, totalRecharge = ct.factor, ct.utilTarget*ct.factor
|
||||||
totalRecharge := ct.utilTarget * gf
|
|
||||||
ct.gfUpdateCh = make(chan gfUpdate, 100)
|
// In order to perform factor data statistics under the high request pressure,
|
||||||
threshold := gfUsageThreshold * float64(gfUsageTC) * ct.utilTarget / 1000000
|
// we only adjust factor when recent factor usage beyond the threshold.
|
||||||
|
threshold := gfUsageThreshold * float64(gfUsageTC) * ct.utilTarget / flowcontrol.FixedPointMultiplier
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
saveCostFactor := func() {
|
saveCostFactor := func() {
|
||||||
var data [8]byte
|
var data [8]byte
|
||||||
binary.BigEndian.PutUint64(data[:], math.Float64bits(gfLog))
|
binary.BigEndian.PutUint64(data[:], math.Float64bits(gfLog))
|
||||||
ct.db.Put([]byte(gfDbKey), data[:])
|
ct.db.Put([]byte(gfDbKey), data[:])
|
||||||
log.Debug("global cost factor saved", "value", gf)
|
log.Debug("global cost factor saved", "value", factor)
|
||||||
}
|
}
|
||||||
saveTicker := time.NewTicker(time.Minute * 10)
|
saveTicker := time.NewTicker(time.Minute * 10)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case r := <-ct.gfUpdateCh:
|
case r := <-ct.reqInfoCh:
|
||||||
|
requestServedMeter.Mark(int64(r.servingTime))
|
||||||
|
requestEstimatedMeter.Mark(int64(r.avgTimeCost / factor))
|
||||||
|
requestServedTimer.Update(time.Duration(r.servingTime))
|
||||||
|
relativeCostHistogram.Update(int64(r.avgTimeCost / factor / r.servingTime))
|
||||||
|
|
||||||
now := mclock.Now()
|
now := mclock.Now()
|
||||||
if ct.logRelCost != nil && r.avgTimeCost > 1e-20 {
|
|
||||||
ct.logRelCost.Update(r.servingTime * gf / r.avgTimeCost)
|
|
||||||
}
|
|
||||||
if r.servingTime > 1000000000 {
|
|
||||||
ct.logger.Event(fmt.Sprintf("Very long servingTime = %f avgTimeCost = %f costFactor = %f", r.servingTime, r.avgTimeCost, gf))
|
|
||||||
}
|
|
||||||
dt := float64(now - expUpdate)
|
dt := float64(now - expUpdate)
|
||||||
expUpdate = now
|
expUpdate = now
|
||||||
exp := math.Exp(-dt / float64(gfUsageTC))
|
exp := math.Exp(-dt / float64(gfUsageTC))
|
||||||
// calculate gf correction until now, based on previous values
|
|
||||||
|
// calculate factor correction until now, based on previous values
|
||||||
var gfCorr float64
|
var gfCorr float64
|
||||||
max := recentTime
|
max := recentTime
|
||||||
if recentAvg > max {
|
if recentAvg > max {
|
||||||
|
|
@ -268,27 +288,28 @@ func (ct *costTracker) gfLoop() {
|
||||||
} else {
|
} else {
|
||||||
gfCorr = math.Log(max/threshold) * float64(gfUsageTC)
|
gfCorr = math.Log(max/threshold) * float64(gfUsageTC)
|
||||||
}
|
}
|
||||||
// calculate log(gf) correction with the right direction and time constant
|
// calculate log(factor) correction with the right direction and time constant
|
||||||
if recentTime > recentAvg {
|
if recentTime > recentAvg {
|
||||||
// drop gf if actual serving times are larger than average estimates
|
// drop factor if actual serving times are larger than average estimates
|
||||||
gfCorr /= -float64(gfDropTC)
|
gfCorr /= -float64(gfDropTC)
|
||||||
} else {
|
} else {
|
||||||
// raise gf if actual serving times are smaller than average estimates
|
// raise factor if actual serving times are smaller than average estimates
|
||||||
gfCorr /= float64(gfRaiseTC)
|
gfCorr /= float64(gfRaiseTC)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update recent cost values with current request
|
// update recent cost values with current request
|
||||||
recentTime = recentTime*exp + r.servingTime
|
recentTime = recentTime*exp + r.servingTime
|
||||||
recentAvg = recentAvg*exp + r.avgTimeCost/gf
|
recentAvg = recentAvg*exp + r.avgTimeCost/factor
|
||||||
|
|
||||||
if gfCorr != 0 {
|
if gfCorr != 0 {
|
||||||
|
// Apply the correction to factor
|
||||||
gfLog += gfCorr
|
gfLog += gfCorr
|
||||||
gf = math.Exp(gfLog)
|
factor = math.Exp(gfLog)
|
||||||
|
// Notify outside modules the new factor and totalRecharge.
|
||||||
if time.Duration(now-lastUpdate) > time.Second {
|
if time.Duration(now-lastUpdate) > time.Second {
|
||||||
totalRecharge = ct.utilTarget * gf
|
totalRecharge, lastUpdate = ct.utilTarget*factor, now
|
||||||
lastUpdate = now
|
|
||||||
ct.gfLock.Lock()
|
ct.gfLock.Lock()
|
||||||
ct.gf = gf
|
ct.factor = factor
|
||||||
ch := ct.totalRechargeCh
|
ch := ct.totalRechargeCh
|
||||||
ct.gfLock.Unlock()
|
ct.gfLock.Unlock()
|
||||||
if ch != nil {
|
if ch != nil {
|
||||||
|
|
@ -297,12 +318,12 @@ func (ct *costTracker) gfLoop() {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debug("global cost factor updated", "gf", gf)
|
log.Debug("global cost factor updated", "factor", factor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ct.logRecentTime.Update(recentTime)
|
recentServedGauge.Update(int64(recentTime))
|
||||||
ct.logRecentAvg.Update(recentAvg)
|
recentEstimatedGauge.Update(int64(recentAvg))
|
||||||
ct.logTotalRecharge.Update(totalRecharge)
|
totalRechargeGauge.Update(int64(totalRecharge))
|
||||||
|
|
||||||
case <-saveTicker.C:
|
case <-saveTicker.C:
|
||||||
saveCostFactor()
|
saveCostFactor()
|
||||||
|
|
@ -321,7 +342,7 @@ func (ct *costTracker) globalFactor() float64 {
|
||||||
ct.gfLock.RLock()
|
ct.gfLock.RLock()
|
||||||
defer ct.gfLock.RUnlock()
|
defer ct.gfLock.RUnlock()
|
||||||
|
|
||||||
return ct.gf
|
return ct.factor
|
||||||
}
|
}
|
||||||
|
|
||||||
// totalRecharge returns the current total recharge parameter which is used by
|
// totalRecharge returns the current total recharge parameter which is used by
|
||||||
|
|
@ -330,7 +351,7 @@ func (ct *costTracker) totalRecharge() uint64 {
|
||||||
ct.gfLock.RLock()
|
ct.gfLock.RLock()
|
||||||
defer ct.gfLock.RUnlock()
|
defer ct.gfLock.RUnlock()
|
||||||
|
|
||||||
return uint64(ct.gf * ct.utilTarget)
|
return uint64(ct.factor * ct.utilTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
// subscribeTotalRecharge returns all future updates to the total recharge value
|
// subscribeTotalRecharge returns all future updates to the total recharge value
|
||||||
|
|
@ -340,7 +361,7 @@ func (ct *costTracker) subscribeTotalRecharge(ch chan uint64) uint64 {
|
||||||
defer ct.gfLock.Unlock()
|
defer ct.gfLock.Unlock()
|
||||||
|
|
||||||
ct.totalRechargeCh = ch
|
ct.totalRechargeCh = ch
|
||||||
return uint64(ct.gf * ct.utilTarget)
|
return uint64(ct.factor * ct.utilTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateStats updates the global cost factor and (if enabled) the real cost vs.
|
// updateStats updates the global cost factor and (if enabled) the real cost vs.
|
||||||
|
|
@ -349,7 +370,7 @@ func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) {
|
||||||
avg := reqAvgTimeCost[code]
|
avg := reqAvgTimeCost[code]
|
||||||
avgTimeCost := avg.baseCost + amount*avg.reqCost
|
avgTimeCost := avg.baseCost + amount*avg.reqCost
|
||||||
select {
|
select {
|
||||||
case ct.gfUpdateCh <- gfUpdate{float64(avgTimeCost), float64(servingTime)}:
|
case ct.reqInfoCh <- reqInfo{float64(avgTimeCost), float64(servingTime)}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
if makeCostStats {
|
if makeCostStats {
|
||||||
|
|
|
||||||
|
|
@ -1,227 +0,0 @@
|
||||||
// Copyright 2019 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 csvlogger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Logger is a metrics/events logger that writes logged values and events into a comma separated file
|
|
||||||
type Logger struct {
|
|
||||||
file *os.File
|
|
||||||
started mclock.AbsTime
|
|
||||||
channels []*Channel
|
|
||||||
period time.Duration
|
|
||||||
stopCh, stopped chan struct{}
|
|
||||||
storeCh chan string
|
|
||||||
eventHeader string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLogger creates a new Logger
|
|
||||||
func NewLogger(fileName string, updatePeriod time.Duration, eventHeader string) *Logger {
|
|
||||||
if fileName == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
f, err := os.Create(fileName)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error creating log file", "name", fileName, "error", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Logger{
|
|
||||||
file: f,
|
|
||||||
period: updatePeriod,
|
|
||||||
stopCh: make(chan struct{}),
|
|
||||||
storeCh: make(chan string, 1),
|
|
||||||
eventHeader: eventHeader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewChannel creates a new value logger channel that writes values in a single
|
|
||||||
// column. If the relative change of the value is bigger than the given threshold
|
|
||||||
// then a new line is added immediately (threshold can also be 0).
|
|
||||||
func (l *Logger) NewChannel(name string, threshold float64) *Channel {
|
|
||||||
if l == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c := &Channel{
|
|
||||||
logger: l,
|
|
||||||
name: name,
|
|
||||||
threshold: threshold,
|
|
||||||
}
|
|
||||||
l.channels = append(l.channels, c)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMinMaxChannel creates a new value logger channel that writes the minimum and
|
|
||||||
// maximum of the tracked value in two columns. It never triggers adding a new line.
|
|
||||||
// If zeroDefault is true then 0 is written to both min and max columns if no update
|
|
||||||
// was given during the last period. If it is false then the last update will appear
|
|
||||||
// in both columns.
|
|
||||||
func (l *Logger) NewMinMaxChannel(name string, zeroDefault bool) *Channel {
|
|
||||||
if l == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c := &Channel{
|
|
||||||
logger: l,
|
|
||||||
name: name,
|
|
||||||
minmax: true,
|
|
||||||
mmZeroDefault: zeroDefault,
|
|
||||||
}
|
|
||||||
l.channels = append(l.channels, c)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Logger) store(event string) {
|
|
||||||
s := fmt.Sprintf("%g", float64(mclock.Now()-l.started)/1000000000)
|
|
||||||
for _, ch := range l.channels {
|
|
||||||
s += ", " + ch.store()
|
|
||||||
}
|
|
||||||
if event != "" {
|
|
||||||
s += ", " + event
|
|
||||||
}
|
|
||||||
l.file.WriteString(s + "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start writes the header line and starts the logger
|
|
||||||
func (l *Logger) Start() {
|
|
||||||
if l == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.started = mclock.Now()
|
|
||||||
s := "Time"
|
|
||||||
for _, ch := range l.channels {
|
|
||||||
s += ", " + ch.header()
|
|
||||||
}
|
|
||||||
if l.eventHeader != "" {
|
|
||||||
s += ", " + l.eventHeader
|
|
||||||
}
|
|
||||||
l.file.WriteString(s + "\n")
|
|
||||||
go func() {
|
|
||||||
timer := time.NewTimer(l.period)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-timer.C:
|
|
||||||
l.store("")
|
|
||||||
timer.Reset(l.period)
|
|
||||||
case event := <-l.storeCh:
|
|
||||||
l.store(event)
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
timer.Reset(l.period)
|
|
||||||
case <-l.stopCh:
|
|
||||||
close(l.stopped)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the logger and closes the file
|
|
||||||
func (l *Logger) Stop() {
|
|
||||||
if l == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.stopped = make(chan struct{})
|
|
||||||
close(l.stopCh)
|
|
||||||
<-l.stopped
|
|
||||||
l.file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event immediately adds a new line and adds the given event string in the last column
|
|
||||||
func (l *Logger) Event(event string) {
|
|
||||||
if l == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case l.storeCh <- event:
|
|
||||||
case <-l.stopCh:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Channel represents a logger channel tracking a single value
|
|
||||||
type Channel struct {
|
|
||||||
logger *Logger
|
|
||||||
lock sync.Mutex
|
|
||||||
name string
|
|
||||||
threshold, storeMin, storeMax, lastValue, min, max float64
|
|
||||||
minmax, mmSet, mmZeroDefault bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates the tracked value
|
|
||||||
func (lc *Channel) Update(value float64) {
|
|
||||||
if lc == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lc.lock.Lock()
|
|
||||||
defer lc.lock.Unlock()
|
|
||||||
|
|
||||||
lc.lastValue = value
|
|
||||||
if lc.minmax {
|
|
||||||
if value > lc.max || !lc.mmSet {
|
|
||||||
lc.max = value
|
|
||||||
}
|
|
||||||
if value < lc.min || !lc.mmSet {
|
|
||||||
lc.min = value
|
|
||||||
}
|
|
||||||
lc.mmSet = true
|
|
||||||
} else {
|
|
||||||
if value < lc.storeMin || value > lc.storeMax {
|
|
||||||
select {
|
|
||||||
case lc.logger.storeCh <- "":
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *Channel) store() (s string) {
|
|
||||||
lc.lock.Lock()
|
|
||||||
defer lc.lock.Unlock()
|
|
||||||
|
|
||||||
if lc.minmax {
|
|
||||||
s = fmt.Sprintf("%g, %g", lc.min, lc.max)
|
|
||||||
lc.mmSet = false
|
|
||||||
if lc.mmZeroDefault {
|
|
||||||
lc.min = 0
|
|
||||||
} else {
|
|
||||||
lc.min = lc.lastValue
|
|
||||||
}
|
|
||||||
lc.max = lc.min
|
|
||||||
} else {
|
|
||||||
s = fmt.Sprintf("%g", lc.lastValue)
|
|
||||||
lc.storeMin = lc.lastValue * (1 - lc.threshold)
|
|
||||||
lc.storeMax = lc.lastValue * (1 + lc.threshold)
|
|
||||||
if lc.lastValue < 0 {
|
|
||||||
lc.storeMin, lc.storeMax = lc.storeMax, lc.storeMin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *Channel) header() string {
|
|
||||||
if lc.minmax {
|
|
||||||
return lc.name + " (min), " + lc.name + " (max)"
|
|
||||||
}
|
|
||||||
return lc.name
|
|
||||||
}
|
|
||||||
|
|
@ -26,7 +26,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/common/prque"
|
"github.com/ethereum/go-ethereum/common/prque"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/les/csvlogger"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
@ -53,8 +52,7 @@ type freeClientPool struct {
|
||||||
|
|
||||||
connectedLimit, totalLimit int
|
connectedLimit, totalLimit int
|
||||||
freeClientCap uint64
|
freeClientCap uint64
|
||||||
logger *csvlogger.Logger
|
connectedCap uint64
|
||||||
logTotalFreeConn *csvlogger.Channel
|
|
||||||
|
|
||||||
addressMap map[string]*freeClientPoolEntry
|
addressMap map[string]*freeClientPoolEntry
|
||||||
connPool, disconnPool *prque.Prque
|
connPool, disconnPool *prque.Prque
|
||||||
|
|
@ -69,7 +67,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// newFreeClientPool creates a new free client pool
|
// newFreeClientPool creates a new free client pool
|
||||||
func newFreeClientPool(db ethdb.Database, freeClientCap uint64, totalLimit int, clock mclock.Clock, removePeer func(string), metricsLogger, eventLogger *csvlogger.Logger) *freeClientPool {
|
func newFreeClientPool(db ethdb.Database, freeClientCap uint64, totalLimit int, clock mclock.Clock, removePeer func(string)) *freeClientPool {
|
||||||
pool := &freeClientPool{
|
pool := &freeClientPool{
|
||||||
db: db,
|
db: db,
|
||||||
clock: clock,
|
clock: clock,
|
||||||
|
|
@ -78,8 +76,6 @@ func newFreeClientPool(db ethdb.Database, freeClientCap uint64, totalLimit int,
|
||||||
disconnPool: prque.New(poolSetIndex),
|
disconnPool: prque.New(poolSetIndex),
|
||||||
freeClientCap: freeClientCap,
|
freeClientCap: freeClientCap,
|
||||||
totalLimit: totalLimit,
|
totalLimit: totalLimit,
|
||||||
logger: eventLogger,
|
|
||||||
logTotalFreeConn: metricsLogger.NewChannel("totalFreeConn", 0),
|
|
||||||
removePeer: removePeer,
|
removePeer: removePeer,
|
||||||
}
|
}
|
||||||
pool.loadFromDb()
|
pool.loadFromDb()
|
||||||
|
|
@ -126,10 +122,7 @@ func (f *freeClientPool) connect(address, id string) bool {
|
||||||
if f.closed {
|
if f.closed {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
f.logger.Event("freeClientPool: connecting from " + address + ", " + id)
|
|
||||||
if f.connectedLimit == 0 {
|
if f.connectedLimit == 0 {
|
||||||
f.logger.Event("freeClientPool: rejected, " + id)
|
|
||||||
log.Debug("Client rejected", "address", address)
|
log.Debug("Client rejected", "address", address)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +134,6 @@ func (f *freeClientPool) connect(address, id string) bool {
|
||||||
f.addressMap[address] = e
|
f.addressMap[address] = e
|
||||||
} else {
|
} else {
|
||||||
if e.connected {
|
if e.connected {
|
||||||
f.logger.Event("freeClientPool: already connected, " + id)
|
|
||||||
log.Debug("Client already connected", "address", address)
|
log.Debug("Client already connected", "address", address)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -154,12 +146,13 @@ func (f *freeClientPool) connect(address, id string) bool {
|
||||||
if e.linUsage+int64(connectedBias)-i.linUsage < 0 {
|
if e.linUsage+int64(connectedBias)-i.linUsage < 0 {
|
||||||
// kick it out and accept the new client
|
// kick it out and accept the new client
|
||||||
f.dropClient(i, now)
|
f.dropClient(i, now)
|
||||||
f.logger.Event("freeClientPool: kicked out, " + i.id)
|
clientKickedMeter.Mark(1)
|
||||||
|
f.connectedCap -= f.freeClientCap
|
||||||
} else {
|
} else {
|
||||||
// keep the old client and reject the new one
|
// keep the old client and reject the new one
|
||||||
f.connPool.Push(i, i.linUsage)
|
f.connPool.Push(i, i.linUsage)
|
||||||
f.logger.Event("freeClientPool: rejected, " + id)
|
|
||||||
log.Debug("Client rejected", "address", address)
|
log.Debug("Client rejected", "address", address)
|
||||||
|
clientRejectedMeter.Mark(1)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -167,11 +160,12 @@ func (f *freeClientPool) connect(address, id string) bool {
|
||||||
e.connected = true
|
e.connected = true
|
||||||
e.id = id
|
e.id = id
|
||||||
f.connPool.Push(e, e.linUsage)
|
f.connPool.Push(e, e.linUsage)
|
||||||
f.logTotalFreeConn.Update(float64(uint64(f.connPool.Size()) * f.freeClientCap))
|
|
||||||
if f.connPool.Size()+f.disconnPool.Size() > f.totalLimit {
|
if f.connPool.Size()+f.disconnPool.Size() > f.totalLimit {
|
||||||
f.disconnPool.Pop()
|
f.disconnPool.Pop()
|
||||||
}
|
}
|
||||||
f.logger.Event("freeClientPool: accepted, " + id)
|
f.connectedCap += f.freeClientCap
|
||||||
|
totalConnectedGauge.Update(int64(f.connectedCap))
|
||||||
|
clientConnectedMeter.Mark(1)
|
||||||
log.Debug("Client accepted", "address", address)
|
log.Debug("Client accepted", "address", address)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -203,13 +197,12 @@ func (f *freeClientPool) disconnect(address string) {
|
||||||
log.Debug("Client already disconnected", "address", address)
|
log.Debug("Client already disconnected", "address", address)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f.connPool.Remove(e.index)
|
f.connPool.Remove(e.index)
|
||||||
f.logTotalFreeConn.Update(float64(uint64(f.connPool.Size()) * f.freeClientCap))
|
|
||||||
f.calcLogUsage(e, now)
|
f.calcLogUsage(e, now)
|
||||||
e.connected = false
|
e.connected = false
|
||||||
f.disconnPool.Push(e, -e.logUsage)
|
f.disconnPool.Push(e, -e.logUsage)
|
||||||
f.logger.Event("freeClientPool: disconnected, " + e.id)
|
f.connectedCap -= f.freeClientCap
|
||||||
|
totalConnectedGauge.Update(int64(f.connectedCap))
|
||||||
log.Debug("Client disconnected", "address", address)
|
log.Debug("Client disconnected", "address", address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,15 +220,15 @@ func (f *freeClientPool) setLimits(count int, totalCap uint64) {
|
||||||
for f.connPool.Size() > f.connectedLimit {
|
for f.connPool.Size() > f.connectedLimit {
|
||||||
i := f.connPool.PopItem().(*freeClientPoolEntry)
|
i := f.connPool.PopItem().(*freeClientPoolEntry)
|
||||||
f.dropClient(i, now)
|
f.dropClient(i, now)
|
||||||
f.logger.Event("freeClientPool: setLimits kicked out, " + i.id)
|
f.connectedCap -= f.freeClientCap
|
||||||
}
|
}
|
||||||
|
totalConnectedGauge.Update(int64(f.connectedCap))
|
||||||
}
|
}
|
||||||
|
|
||||||
// dropClient disconnects a client and also moves it from the connected to the
|
// dropClient disconnects a client and also moves it from the connected to the
|
||||||
// disconnected pool
|
// disconnected pool
|
||||||
func (f *freeClientPool) dropClient(i *freeClientPoolEntry, now mclock.AbsTime) {
|
func (f *freeClientPool) dropClient(i *freeClientPoolEntry, now mclock.AbsTime) {
|
||||||
f.connPool.Remove(i.index)
|
f.connPool.Remove(i.index)
|
||||||
f.logTotalFreeConn.Update(float64(uint64(f.connPool.Size()) * f.freeClientCap))
|
|
||||||
f.calcLogUsage(i, now)
|
f.calcLogUsage(i, now)
|
||||||
i.connected = false
|
i.connected = false
|
||||||
f.disconnPool.Push(i, -i.logUsage)
|
f.disconnPool.Push(i, -i.logUsage)
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ func testFreeClientPool(t *testing.T, connLimit, clientCount int) {
|
||||||
}
|
}
|
||||||
disconnCh <- i
|
disconnCh <- i
|
||||||
}
|
}
|
||||||
pool = newFreeClientPool(db, 1, 10000, &clock, disconnFn, nil, nil)
|
pool = newFreeClientPool(db, 1, 10000, &clock, disconnFn)
|
||||||
)
|
)
|
||||||
pool.setLimits(connLimit, uint64(connLimit))
|
pool.setLimits(connLimit, uint64(connLimit))
|
||||||
|
|
||||||
|
|
@ -130,7 +130,7 @@ func testFreeClientPool(t *testing.T, connLimit, clientCount int) {
|
||||||
|
|
||||||
// close and restart pool
|
// close and restart pool
|
||||||
pool.stop()
|
pool.stop()
|
||||||
pool = newFreeClientPool(db, 1, 10000, &clock, disconnFn, nil, nil)
|
pool = newFreeClientPool(db, 1, 10000, &clock, disconnFn)
|
||||||
pool.setLimits(connLimit, uint64(connLimit))
|
pool.setLimits(connLimit, uint64(connLimit))
|
||||||
|
|
||||||
// try connecting all known peers (connLimit should be filled up)
|
// try connecting all known peers (connLimit should be filled up)
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/les/csvlogger"
|
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
|
@ -124,7 +123,6 @@ type ProtocolManager struct {
|
||||||
|
|
||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
eventMux *event.TypeMux
|
eventMux *event.TypeMux
|
||||||
logger *csvlogger.Logger
|
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
synced func() bool
|
synced func() bool
|
||||||
|
|
@ -262,11 +260,12 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||||
// Ignore maxPeers if this is a trusted peer
|
// Ignore maxPeers if this is a trusted peer
|
||||||
// In server mode we try to check into the client pool after handshake
|
// In server mode we try to check into the client pool after handshake
|
||||||
if pm.client && pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted {
|
if pm.client && pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted {
|
||||||
pm.logger.Event("Rejected (too many peers), " + p.id)
|
clientRejectedMeter.Mark(1)
|
||||||
return p2p.DiscTooManyPeers
|
return p2p.DiscTooManyPeers
|
||||||
}
|
}
|
||||||
// Reject light clients if server is not synced.
|
// Reject light clients if server is not synced.
|
||||||
if !pm.client && !pm.synced() {
|
if !pm.client && !pm.synced() {
|
||||||
|
clientRejectedMeter.Mark(1)
|
||||||
return p2p.DiscRequested
|
return p2p.DiscRequested
|
||||||
}
|
}
|
||||||
p.Log().Debug("Light Ethereum peer connected", "name", p.Name())
|
p.Log().Debug("Light Ethereum peer connected", "name", p.Name())
|
||||||
|
|
@ -281,7 +280,7 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||||
)
|
)
|
||||||
if err := p.Handshake(td, hash, number, genesis.Hash(), pm.server); err != nil {
|
if err := p.Handshake(td, hash, number, genesis.Hash(), pm.server); err != nil {
|
||||||
p.Log().Debug("Light Ethereum handshake failed", "err", err)
|
p.Log().Debug("Light Ethereum handshake failed", "err", err)
|
||||||
pm.logger.Event("Handshake error: " + err.Error() + ", " + p.id)
|
clientErrorMeter.Mark(1)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if p.fcClient != nil {
|
if p.fcClient != nil {
|
||||||
|
|
@ -294,14 +293,14 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||||
|
|
||||||
// Register the peer locally
|
// Register the peer locally
|
||||||
if err := pm.peers.Register(p); err != nil {
|
if err := pm.peers.Register(p); err != nil {
|
||||||
|
clientErrorMeter.Mark(1)
|
||||||
p.Log().Error("Light Ethereum peer registration failed", "err", err)
|
p.Log().Error("Light Ethereum peer registration failed", "err", err)
|
||||||
pm.logger.Event("Peer registration error: " + err.Error() + ", " + p.id)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pm.logger.Event("Connection established, " + p.id)
|
connectedAt := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
pm.logger.Event("Closed connection, " + p.id)
|
|
||||||
pm.removePeer(p.id)
|
pm.removePeer(p.id)
|
||||||
|
connectionTimer.UpdateSince(connectedAt)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Register the peer in the downloader. If the downloader considers it banned, we disconnect
|
// Register the peer in the downloader. If the downloader considers it banned, we disconnect
|
||||||
|
|
@ -317,11 +316,9 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||||
pm.serverPool.registered(p.poolEntry)
|
pm.serverPool.registered(p.poolEntry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// main loop. handle incoming messages.
|
// main loop. handle incoming messages.
|
||||||
for {
|
for {
|
||||||
if err := pm.handleMsg(p); err != nil {
|
if err := pm.handleMsg(p); err != nil {
|
||||||
pm.logger.Event("Message handling error: " + err.Error() + ", " + p.id)
|
|
||||||
p.Log().Debug("Light Ethereum message handling failed", "err", err)
|
p.Log().Debug("Light Ethereum message handling failed", "err", err)
|
||||||
if p.fcServer != nil {
|
if p.fcServer != nil {
|
||||||
p.fcServer.DumpLogs()
|
p.fcServer.DumpLogs()
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ func newTestProtocolManager(lightSync bool, blocks int, odr *LesOdr, indexers []
|
||||||
if !lightSync {
|
if !lightSync {
|
||||||
srv := &LesServer{lesCommons: lesCommons{protocolManager: pm, chainDb: db}}
|
srv := &LesServer{lesCommons: lesCommons{protocolManager: pm, chainDb: db}}
|
||||||
pm.server = srv
|
pm.server = srv
|
||||||
pm.servingQueue = newServingQueue(int64(time.Millisecond*10), 1, nil)
|
pm.servingQueue = newServingQueue(int64(time.Millisecond*10), 1)
|
||||||
pm.servingQueue.setThreads(4)
|
pm.servingQueue.setThreads(4)
|
||||||
|
|
||||||
srv.defParams = flowcontrol.ServerParams{
|
srv.defParams = flowcontrol.ServerParams{
|
||||||
|
|
|
||||||
|
|
@ -22,46 +22,31 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
/* propTxnInPacketsMeter = metrics.NewMeter("eth/prop/txns/in/packets")
|
|
||||||
propTxnInTrafficMeter = metrics.NewMeter("eth/prop/txns/in/traffic")
|
|
||||||
propTxnOutPacketsMeter = metrics.NewMeter("eth/prop/txns/out/packets")
|
|
||||||
propTxnOutTrafficMeter = metrics.NewMeter("eth/prop/txns/out/traffic")
|
|
||||||
propHashInPacketsMeter = metrics.NewMeter("eth/prop/hashes/in/packets")
|
|
||||||
propHashInTrafficMeter = metrics.NewMeter("eth/prop/hashes/in/traffic")
|
|
||||||
propHashOutPacketsMeter = metrics.NewMeter("eth/prop/hashes/out/packets")
|
|
||||||
propHashOutTrafficMeter = metrics.NewMeter("eth/prop/hashes/out/traffic")
|
|
||||||
propBlockInPacketsMeter = metrics.NewMeter("eth/prop/blocks/in/packets")
|
|
||||||
propBlockInTrafficMeter = metrics.NewMeter("eth/prop/blocks/in/traffic")
|
|
||||||
propBlockOutPacketsMeter = metrics.NewMeter("eth/prop/blocks/out/packets")
|
|
||||||
propBlockOutTrafficMeter = metrics.NewMeter("eth/prop/blocks/out/traffic")
|
|
||||||
reqHashInPacketsMeter = metrics.NewMeter("eth/req/hashes/in/packets")
|
|
||||||
reqHashInTrafficMeter = metrics.NewMeter("eth/req/hashes/in/traffic")
|
|
||||||
reqHashOutPacketsMeter = metrics.NewMeter("eth/req/hashes/out/packets")
|
|
||||||
reqHashOutTrafficMeter = metrics.NewMeter("eth/req/hashes/out/traffic")
|
|
||||||
reqBlockInPacketsMeter = metrics.NewMeter("eth/req/blocks/in/packets")
|
|
||||||
reqBlockInTrafficMeter = metrics.NewMeter("eth/req/blocks/in/traffic")
|
|
||||||
reqBlockOutPacketsMeter = metrics.NewMeter("eth/req/blocks/out/packets")
|
|
||||||
reqBlockOutTrafficMeter = metrics.NewMeter("eth/req/blocks/out/traffic")
|
|
||||||
reqHeaderInPacketsMeter = metrics.NewMeter("eth/req/headers/in/packets")
|
|
||||||
reqHeaderInTrafficMeter = metrics.NewMeter("eth/req/headers/in/traffic")
|
|
||||||
reqHeaderOutPacketsMeter = metrics.NewMeter("eth/req/headers/out/packets")
|
|
||||||
reqHeaderOutTrafficMeter = metrics.NewMeter("eth/req/headers/out/traffic")
|
|
||||||
reqBodyInPacketsMeter = metrics.NewMeter("eth/req/bodies/in/packets")
|
|
||||||
reqBodyInTrafficMeter = metrics.NewMeter("eth/req/bodies/in/traffic")
|
|
||||||
reqBodyOutPacketsMeter = metrics.NewMeter("eth/req/bodies/out/packets")
|
|
||||||
reqBodyOutTrafficMeter = metrics.NewMeter("eth/req/bodies/out/traffic")
|
|
||||||
reqStateInPacketsMeter = metrics.NewMeter("eth/req/states/in/packets")
|
|
||||||
reqStateInTrafficMeter = metrics.NewMeter("eth/req/states/in/traffic")
|
|
||||||
reqStateOutPacketsMeter = metrics.NewMeter("eth/req/states/out/packets")
|
|
||||||
reqStateOutTrafficMeter = metrics.NewMeter("eth/req/states/out/traffic")
|
|
||||||
reqReceiptInPacketsMeter = metrics.NewMeter("eth/req/receipts/in/packets")
|
|
||||||
reqReceiptInTrafficMeter = metrics.NewMeter("eth/req/receipts/in/traffic")
|
|
||||||
reqReceiptOutPacketsMeter = metrics.NewMeter("eth/req/receipts/out/packets")
|
|
||||||
reqReceiptOutTrafficMeter = metrics.NewMeter("eth/req/receipts/out/traffic")*/
|
|
||||||
miscInPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets", nil)
|
miscInPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets", nil)
|
||||||
miscInTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic", nil)
|
miscInTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic", nil)
|
||||||
miscOutPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets", nil)
|
miscOutPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets", nil)
|
||||||
miscOutTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic", nil)
|
miscOutTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic", nil)
|
||||||
|
|
||||||
|
connectionTimer = metrics.NewRegisteredTimer("les/connectionTime", nil)
|
||||||
|
|
||||||
|
totalConnectedGauge = metrics.NewRegisteredGauge("les/server/totalConnected", nil)
|
||||||
|
totalCapacityGauge = metrics.NewRegisteredGauge("les/server/totalCapacity", nil)
|
||||||
|
totalRechargeGauge = metrics.NewRegisteredGauge("les/server/totalRecharge", nil)
|
||||||
|
blockProcessingTimer = metrics.NewRegisteredTimer("les/server/blockProcessingTime", nil)
|
||||||
|
requestServedTimer = metrics.NewRegisteredTimer("les/server/requestServed", nil)
|
||||||
|
requestServedMeter = metrics.NewRegisteredMeter("les/server/totalRequestServed", nil)
|
||||||
|
requestEstimatedMeter = metrics.NewRegisteredMeter("les/server/totalRequestEstimated", nil)
|
||||||
|
relativeCostHistogram = metrics.NewRegisteredHistogram("les/server/relativeCost", nil, metrics.NewExpDecaySample(1028, 0.015))
|
||||||
|
recentServedGauge = metrics.NewRegisteredGauge("les/server/recentRequestServed", nil)
|
||||||
|
recentEstimatedGauge = metrics.NewRegisteredGauge("les/server/recentRequestEstimated", nil)
|
||||||
|
sqServedGauge = metrics.NewRegisteredGauge("les/server/servingQueue/served", nil)
|
||||||
|
sqQueuedGauge = metrics.NewRegisteredGauge("les/server/servingQueue/queued", nil)
|
||||||
|
clientConnectedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/connected", nil)
|
||||||
|
clientRejectedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/rejected", nil)
|
||||||
|
clientKickedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/kicked", nil)
|
||||||
|
// clientDisconnectedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/disconnected", nil)
|
||||||
|
clientFreezeMeter = metrics.NewRegisteredMeter("les/server/clientEvent/freeze", nil)
|
||||||
|
clientErrorMeter = metrics.NewRegisteredMeter("les/server/clientEvent/error", nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of
|
// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/les/csvlogger"
|
|
||||||
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -40,15 +39,6 @@ import (
|
||||||
|
|
||||||
const bufLimitRatio = 6000 // fixed bufLimit/MRR ratio
|
const bufLimitRatio = 6000 // fixed bufLimit/MRR ratio
|
||||||
|
|
||||||
const (
|
|
||||||
logFileName = "" // csv log file name (disabled if empty)
|
|
||||||
logClientPoolMetrics = true // log client pool metrics
|
|
||||||
logClientPoolEvents = false // detailed client pool event logging
|
|
||||||
logRequestServing = true // log request serving metrics and events
|
|
||||||
logBlockProcEvents = true // log block processing events
|
|
||||||
logProtocolHandler = true // log protocol handler events
|
|
||||||
)
|
|
||||||
|
|
||||||
type LesServer struct {
|
type LesServer struct {
|
||||||
lesCommons
|
lesCommons
|
||||||
|
|
||||||
|
|
@ -62,26 +52,15 @@ type LesServer struct {
|
||||||
privateKey *ecdsa.PrivateKey
|
privateKey *ecdsa.PrivateKey
|
||||||
quitSync chan struct{}
|
quitSync chan struct{}
|
||||||
onlyAnnounce bool
|
onlyAnnounce bool
|
||||||
csvLogger *csvlogger.Logger
|
|
||||||
logTotalCap *csvlogger.Channel
|
|
||||||
|
|
||||||
thcNormal, thcBlockProcessing int // serving thread count for normal operation and block processing mode
|
thcNormal, thcBlockProcessing int // serving thread count for normal operation and block processing mode
|
||||||
|
|
||||||
maxPeers int
|
maxPeers int
|
||||||
minCapacity, freeClientCap uint64
|
minCapacity, freeClientCap uint64
|
||||||
freeClientPool *freeClientPool
|
freeClientPool *freeClientPool
|
||||||
priorityClientPool *priorityClientPool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
var csvLogger *csvlogger.Logger
|
|
||||||
if logFileName != "" {
|
|
||||||
csvLogger = csvlogger.NewLogger(logFileName, time.Second*10, "event, peerId")
|
|
||||||
}
|
|
||||||
requestLogger := csvLogger
|
|
||||||
if !logRequestServing {
|
|
||||||
requestLogger = nil
|
|
||||||
}
|
|
||||||
lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions))
|
lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions))
|
||||||
for i, pv := range AdvertiseProtocolVersions {
|
for i, pv := range AdvertiseProtocolVersions {
|
||||||
lesTopics[i] = lesTopic(e.BlockChain().Genesis().Hash(), pv)
|
lesTopics[i] = lesTopic(e.BlockChain().Genesis().Hash(), pv)
|
||||||
|
|
@ -99,10 +78,8 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
quitSync: quitSync,
|
quitSync: quitSync,
|
||||||
lesTopics: lesTopics,
|
lesTopics: lesTopics,
|
||||||
onlyAnnounce: config.OnlyAnnounce,
|
onlyAnnounce: config.OnlyAnnounce,
|
||||||
csvLogger: csvLogger,
|
|
||||||
logTotalCap: requestLogger.NewChannel("totalCapacity", 0.01),
|
|
||||||
}
|
}
|
||||||
srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config, requestLogger)
|
srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config)
|
||||||
|
|
||||||
logger := log.New()
|
logger := log.New()
|
||||||
srv.thcNormal = config.LightServ * 4 / 100
|
srv.thcNormal = config.LightServ * 4 / 100
|
||||||
|
|
@ -131,10 +108,7 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
srv.protocolManager = pm
|
srv.protocolManager = pm
|
||||||
if logProtocolHandler {
|
pm.servingQueue = newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100)
|
||||||
pm.logger = csvLogger
|
|
||||||
}
|
|
||||||
pm.servingQueue = newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100, requestLogger)
|
|
||||||
pm.server = srv
|
pm.server = srv
|
||||||
|
|
||||||
return srv, nil
|
return srv, nil
|
||||||
|
|
@ -142,12 +116,6 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
|
|
||||||
func (s *LesServer) APIs() []rpc.API {
|
func (s *LesServer) APIs() []rpc.API {
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
{
|
|
||||||
Namespace: "les",
|
|
||||||
Version: "1.0",
|
|
||||||
Service: NewPrivateLightServerAPI(s),
|
|
||||||
Public: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Namespace: "les",
|
Namespace: "les",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
|
|
@ -163,11 +131,10 @@ func (s *LesServer) APIs() []rpc.API {
|
||||||
func (s *LesServer) startEventLoop() {
|
func (s *LesServer) startEventLoop() {
|
||||||
s.protocolManager.wg.Add(1)
|
s.protocolManager.wg.Add(1)
|
||||||
|
|
||||||
blockProcLogger := s.csvLogger
|
var (
|
||||||
if !logBlockProcEvents {
|
processing, procLast bool
|
||||||
blockProcLogger = nil
|
procStarted time.Time
|
||||||
}
|
)
|
||||||
var processing, procLast bool
|
|
||||||
blockProcFeed := make(chan bool, 100)
|
blockProcFeed := make(chan bool, 100)
|
||||||
s.protocolManager.blockchain.(*core.BlockChain).SubscribeBlockProcessingEvent(blockProcFeed)
|
s.protocolManager.blockchain.(*core.BlockChain).SubscribeBlockProcessingEvent(blockProcFeed)
|
||||||
totalRechargeCh := make(chan uint64, 100)
|
totalRechargeCh := make(chan uint64, 100)
|
||||||
|
|
@ -176,13 +143,13 @@ func (s *LesServer) startEventLoop() {
|
||||||
updateRecharge := func() {
|
updateRecharge := func() {
|
||||||
if processing {
|
if processing {
|
||||||
if !procLast {
|
if !procLast {
|
||||||
blockProcLogger.Event("block processing started")
|
procStarted = time.Now()
|
||||||
}
|
}
|
||||||
s.protocolManager.servingQueue.setThreads(s.thcBlockProcessing)
|
s.protocolManager.servingQueue.setThreads(s.thcBlockProcessing)
|
||||||
s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge, totalRecharge}})
|
s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge, totalRecharge}})
|
||||||
} else {
|
} else {
|
||||||
if procLast {
|
if procLast {
|
||||||
blockProcLogger.Event("block processing finished")
|
blockProcessingTimer.UpdateSince(procStarted)
|
||||||
}
|
}
|
||||||
s.protocolManager.servingQueue.setThreads(s.thcNormal)
|
s.protocolManager.servingQueue.setThreads(s.thcNormal)
|
||||||
s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge / 16, totalRecharge / 2}, {totalRecharge / 2, totalRecharge / 2}, {totalRecharge, totalRecharge}})
|
s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge / 16, totalRecharge / 2}, {totalRecharge / 2, totalRecharge / 2}, {totalRecharge, totalRecharge}})
|
||||||
|
|
@ -191,7 +158,7 @@ func (s *LesServer) startEventLoop() {
|
||||||
}
|
}
|
||||||
updateRecharge()
|
updateRecharge()
|
||||||
totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh)
|
totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh)
|
||||||
s.priorityClientPool.setLimits(s.maxPeers, totalCapacity)
|
s.freeClientPool.setLimits(s.maxPeers, totalCapacity)
|
||||||
|
|
||||||
var maxFreePeers uint64
|
var maxFreePeers uint64
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -202,13 +169,13 @@ func (s *LesServer) startEventLoop() {
|
||||||
case totalRecharge = <-totalRechargeCh:
|
case totalRecharge = <-totalRechargeCh:
|
||||||
updateRecharge()
|
updateRecharge()
|
||||||
case totalCapacity = <-totalCapacityCh:
|
case totalCapacity = <-totalCapacityCh:
|
||||||
s.logTotalCap.Update(float64(totalCapacity))
|
totalCapacityGauge.Update(int64(totalCapacity))
|
||||||
newFreePeers := totalCapacity / s.freeClientCap
|
newFreePeers := totalCapacity / s.freeClientCap
|
||||||
if newFreePeers < maxFreePeers && newFreePeers < uint64(s.maxPeers) {
|
if newFreePeers < maxFreePeers && newFreePeers < uint64(s.maxPeers) {
|
||||||
log.Warn("Reduced total capacity", "maxFreePeers", newFreePeers)
|
log.Warn("Reduced total capacity", "maxFreePeers", newFreePeers)
|
||||||
}
|
}
|
||||||
maxFreePeers = newFreePeers
|
maxFreePeers = newFreePeers
|
||||||
s.priorityClientPool.setLimits(s.maxPeers, totalCapacity)
|
s.freeClientPool.setLimits(s.maxPeers, totalCapacity)
|
||||||
case <-s.protocolManager.quitSync:
|
case <-s.protocolManager.quitSync:
|
||||||
s.protocolManager.wg.Done()
|
s.protocolManager.wg.Done()
|
||||||
return
|
return
|
||||||
|
|
@ -243,19 +210,9 @@ func (s *LesServer) Start(srvr *p2p.Server) {
|
||||||
maxCapacity = totalRecharge
|
maxCapacity = totalRecharge
|
||||||
}
|
}
|
||||||
s.fcManager.SetCapacityLimits(s.freeClientCap, maxCapacity, s.freeClientCap*2)
|
s.fcManager.SetCapacityLimits(s.freeClientCap, maxCapacity, s.freeClientCap*2)
|
||||||
poolMetricsLogger := s.csvLogger
|
s.freeClientPool = newFreeClientPool(s.chainDb, s.freeClientCap, 10000, mclock.System{}, func(id string) { go s.protocolManager.removePeer(id) })
|
||||||
if !logClientPoolMetrics {
|
s.protocolManager.peers.notify(s.freeClientPool)
|
||||||
poolMetricsLogger = nil
|
|
||||||
}
|
|
||||||
poolEventLogger := s.csvLogger
|
|
||||||
if !logClientPoolEvents {
|
|
||||||
poolEventLogger = nil
|
|
||||||
}
|
|
||||||
s.freeClientPool = newFreeClientPool(s.chainDb, s.freeClientCap, 10000, mclock.System{}, func(id string) { go s.protocolManager.removePeer(id) }, poolMetricsLogger, poolEventLogger)
|
|
||||||
s.priorityClientPool = newPriorityClientPool(s.freeClientCap, s.protocolManager.peers, s.freeClientPool, poolMetricsLogger, poolEventLogger)
|
|
||||||
|
|
||||||
s.protocolManager.peers.notify(s.priorityClientPool)
|
|
||||||
s.csvLogger.Start()
|
|
||||||
s.startEventLoop()
|
s.startEventLoop()
|
||||||
s.protocolManager.Start(s.config.LightPeers)
|
s.protocolManager.Start(s.config.LightPeers)
|
||||||
if srvr.DiscV5 != nil {
|
if srvr.DiscV5 != nil {
|
||||||
|
|
@ -296,7 +253,6 @@ func (s *LesServer) Stop() {
|
||||||
s.freeClientPool.stop()
|
s.freeClientPool.stop()
|
||||||
s.costTracker.stop()
|
s.costTracker.stop()
|
||||||
s.protocolManager.Stop()
|
s.protocolManager.Stop()
|
||||||
s.csvLogger.Stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(rjl493456442) separate client and server implementation.
|
// todo(rjl493456442) separate client and server implementation.
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,12 @@
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/common/prque"
|
"github.com/ethereum/go-ethereum/common/prque"
|
||||||
"github.com/ethereum/go-ethereum/les/csvlogger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// servingQueue allows running tasks in a limited number of threads and puts the
|
// servingQueue allows running tasks in a limited number of threads and puts the
|
||||||
|
|
@ -44,10 +42,6 @@ type servingQueue struct {
|
||||||
queue *prque.Prque // priority queue for waiting or suspended tasks
|
queue *prque.Prque // priority queue for waiting or suspended tasks
|
||||||
best *servingTask // the highest priority task (not included in the queue)
|
best *servingTask // the highest priority task (not included in the queue)
|
||||||
suspendBias int64 // priority bias against suspending an already running task
|
suspendBias int64 // priority bias against suspending an already running task
|
||||||
|
|
||||||
logger *csvlogger.Logger
|
|
||||||
logRecentTime *csvlogger.Channel
|
|
||||||
logQueuedTime *csvlogger.Channel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// servingTask represents a request serving task. Tasks can be implemented to
|
// servingTask represents a request serving task. Tasks can be implemented to
|
||||||
|
|
@ -127,7 +121,7 @@ func (t *servingTask) waitOrStop() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newServingQueue returns a new servingQueue
|
// newServingQueue returns a new servingQueue
|
||||||
func newServingQueue(suspendBias int64, utilTarget float64, logger *csvlogger.Logger) *servingQueue {
|
func newServingQueue(suspendBias int64, utilTarget float64) *servingQueue {
|
||||||
sq := &servingQueue{
|
sq := &servingQueue{
|
||||||
queue: prque.New(nil),
|
queue: prque.New(nil),
|
||||||
suspendBias: suspendBias,
|
suspendBias: suspendBias,
|
||||||
|
|
@ -140,9 +134,6 @@ func newServingQueue(suspendBias int64, utilTarget float64, logger *csvlogger.Lo
|
||||||
burstDropLimit: uint64(utilTarget * bufLimitRatio * 1000000),
|
burstDropLimit: uint64(utilTarget * bufLimitRatio * 1000000),
|
||||||
burstDecRate: utilTarget,
|
burstDecRate: utilTarget,
|
||||||
lastUpdate: mclock.Now(),
|
lastUpdate: mclock.Now(),
|
||||||
logger: logger,
|
|
||||||
logRecentTime: logger.NewMinMaxChannel("recentTime", false),
|
|
||||||
logQueuedTime: logger.NewMinMaxChannel("queuedTime", false),
|
|
||||||
}
|
}
|
||||||
sq.wg.Add(2)
|
sq.wg.Add(2)
|
||||||
go sq.queueLoop()
|
go sq.queueLoop()
|
||||||
|
|
@ -246,16 +237,13 @@ func (sq *servingQueue) freezePeers() {
|
||||||
}
|
}
|
||||||
sort.Sort(peerList)
|
sort.Sort(peerList)
|
||||||
drop := true
|
drop := true
|
||||||
sq.logger.Event("freezing peers")
|
|
||||||
for _, tasks := range peerList {
|
for _, tasks := range peerList {
|
||||||
if drop {
|
if drop {
|
||||||
tasks.peer.freezeClient()
|
tasks.peer.freezeClient()
|
||||||
tasks.peer.fcClient.Freeze()
|
tasks.peer.fcClient.Freeze()
|
||||||
sq.queuedTime -= tasks.sumTime
|
sq.queuedTime -= tasks.sumTime
|
||||||
if sq.logQueuedTime != nil {
|
sqQueuedGauge.Update(int64(sq.queuedTime))
|
||||||
sq.logQueuedTime.Update(float64(sq.queuedTime) / 1000)
|
clientFreezeMeter.Mark(1)
|
||||||
}
|
|
||||||
sq.logger.Event(fmt.Sprintf("frozen peer sumTime=%d, %v", tasks.sumTime, tasks.peer.id))
|
|
||||||
drop = sq.recentTime+sq.queuedTime > sq.burstDropLimit
|
drop = sq.recentTime+sq.queuedTime > sq.burstDropLimit
|
||||||
for _, task := range tasks.list {
|
for _, task := range tasks.list {
|
||||||
task.tokenCh <- nil
|
task.tokenCh <- nil
|
||||||
|
|
@ -299,10 +287,8 @@ func (sq *servingQueue) addTask(task *servingTask) {
|
||||||
}
|
}
|
||||||
sq.updateRecentTime()
|
sq.updateRecentTime()
|
||||||
sq.queuedTime += task.expTime
|
sq.queuedTime += task.expTime
|
||||||
if sq.logQueuedTime != nil {
|
sqServedGauge.Update(int64(sq.recentTime))
|
||||||
sq.logRecentTime.Update(float64(sq.recentTime) / 1000)
|
sqQueuedGauge.Update(int64(sq.queuedTime))
|
||||||
sq.logQueuedTime.Update(float64(sq.queuedTime) / 1000)
|
|
||||||
}
|
|
||||||
if sq.recentTime+sq.queuedTime > sq.burstLimit {
|
if sq.recentTime+sq.queuedTime > sq.burstLimit {
|
||||||
sq.freezePeers()
|
sq.freezePeers()
|
||||||
}
|
}
|
||||||
|
|
@ -322,10 +308,8 @@ func (sq *servingQueue) queueLoop() {
|
||||||
sq.updateRecentTime()
|
sq.updateRecentTime()
|
||||||
sq.queuedTime -= expTime
|
sq.queuedTime -= expTime
|
||||||
sq.recentTime += expTime
|
sq.recentTime += expTime
|
||||||
if sq.logQueuedTime != nil {
|
sqServedGauge.Update(int64(sq.recentTime))
|
||||||
sq.logRecentTime.Update(float64(sq.recentTime) / 1000)
|
sqQueuedGauge.Update(int64(sq.queuedTime))
|
||||||
sq.logQueuedTime.Update(float64(sq.queuedTime) / 1000)
|
|
||||||
}
|
|
||||||
if sq.queue.Size() == 0 {
|
if sq.queue.Size() == 0 {
|
||||||
sq.best = nil
|
sq.best = nil
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
|
|
@ -82,7 +83,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
|
||||||
data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
|
data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
|
||||||
sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey)
|
sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey)
|
||||||
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||||
if _, err := server.pm.reg.contract.RegisterCheckpoint(signerKey, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
|
if _, err := server.pm.reg.contract.RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
|
||||||
t.Error("register checkpoint failed", err)
|
t.Error("register checkpoint failed", err)
|
||||||
}
|
}
|
||||||
server.backend.Commit()
|
server.backend.Commit()
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp
|
||||||
fmt.Printf("Account: %s\n", request.Address.String())
|
fmt.Printf("Account: %s\n", request.Address.String())
|
||||||
fmt.Printf("messages:\n")
|
fmt.Printf("messages:\n")
|
||||||
for _, nvt := range request.Messages {
|
for _, nvt := range request.Messages {
|
||||||
fmt.Printf("%v\n", nvt.Pprint(1))
|
fmt.Printf("\u00a0\u00a0%v\n", strings.TrimSpace(nvt.Pprint(1)))
|
||||||
}
|
}
|
||||||
fmt.Printf("raw data: \n%q\n", request.Rawdata)
|
fmt.Printf("raw data: \n%q\n", request.Rawdata)
|
||||||
fmt.Printf("data hash: %v\n", request.Hash)
|
fmt.Printf("data hash: %v\n", request.Hash)
|
||||||
|
|
|
||||||
5
vendor/github.com/karalabe/usb/usb_disabled.go
generated
vendored
5
vendor/github.com/karalabe/usb/usb_disabled.go
generated
vendored
|
|
@ -44,3 +44,8 @@ func EnumerateRaw(vendorID uint16, productID uint16) ([]DeviceInfo, error) {
|
||||||
func EnumerateHid(vendorID uint16, productID uint16) ([]DeviceInfo, error) {
|
func EnumerateHid(vendorID uint16, productID uint16) ([]DeviceInfo, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open connects to a previsouly discovered USB device.
|
||||||
|
func (info DeviceInfo) Open() (Device, error) {
|
||||||
|
return nil, ErrUnsupportedPlatform
|
||||||
|
}
|
||||||
|
|
|
||||||
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
|
|
@ -243,10 +243,10 @@
|
||||||
"revisionTime": "2017-04-30T22:20:11Z"
|
"revisionTime": "2017-04-30T22:20:11Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "TU/WaqL7fYPDovmGVRSo8btD4ZM=",
|
"checksumSHA1": "X7ZY5gt+qBd/lafKNbPbouL819w=",
|
||||||
"path": "github.com/karalabe/usb",
|
"path": "github.com/karalabe/usb",
|
||||||
"revision": "4d6ba34a841453237dba721eeec1af0ef9a7a890",
|
"revision": "6a7de9d893feb2324aaef49331e923ce279c7973",
|
||||||
"revisionTime": "2019-06-13T07:02:55Z",
|
"revisionTime": "2019-07-03T09:51:11Z",
|
||||||
"tree": true
|
"tree": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue