diff --git a/.gitignore b/.gitignore index 3c697ad..d43a767 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,19 @@ *.exe~ *.so *.dylib +main # Test binary, built with `go test -c` *.test +test/contracts + # Output of the go coverage tool, specifically when used with LiteIDE *.out +.DS_Store # Dependency directories (remove the comment below to include it) -# vendor/ +vendor/ + +# ide files +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 33a4ff5..37f8aba 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,3 @@ 使用请参考 [test/main.go](./test/main.go) -# 安装运行 -1. windoss -要把./cgofuns/cdll/win下面的所有dll文件与可执行程序放在一起。 -2. linux -- 版本低于ubuntu-16.04时,把./cgofuns/cdll/linx下面的所有so文件与可执行程序放在一起。 -- 版本等于或者高于ubuntu-16.04,不做其它多余操作。 -3. arm -- 以下方式暂时方式 -- 打开cgofun/cgo.go文件,删除这一行:#cgo linux LDFLAGS: -Wl,-RPATH="./" -L ./clib/linux/ -lsignature -lboost_regex -lcrypto -lssl -ldl -lstdc++ -- 在相应位置增加这一行:#cgo LDFLAGS: -L ./clib/arm/ -lsignature -lboost_regex -lssl -lcrypto -lstdc++ -ldl -- CGO_ENABLED=1 GOOS=linux GOARCH=arm CC=arm-linux-gnueabihf-gcc go build -4. aarch64 -- 以下方式暂时方式 -- 打开cgofun/cgo.go文件,删除这一行:#cgo linux LDFLAGS: -Wl,-RPATH="./" -L ./clib/linux/ -lsignature -lboost_regex -lcrypto -lssl -ldl -lstdc++ -- 在相应位置增加这一行:#cgo LDFLAGS: -L ./clib/aarch64/ -lsignature -lboost_regex -lssl -lcrypto -lstdc++ -ldl -- CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc go build main.go diff --git a/abigen/abi/abi.go b/abigen/abi/abi.go new file mode 100644 index 0000000..98eec49 --- /dev/null +++ b/abigen/abi/abi.go @@ -0,0 +1,255 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/crypto" +) + +// The ABI holds information about a contract's context and available +// invokable methods. It will allow you to type check function calls and +// packs data accordingly. +type ABI struct { + Constructor Method + Methods map[string]Method + Events map[string]Event + Errors map[string]Error + + // Additional "special" functions introduced in solidity v0.6.0. + // It's separated from the original default fallback. Each contract + // can only define one fallback and receive function. + Fallback Method // Note it's also used to represent legacy fallback before v0.6.0 + Receive Method +} + +// JSON returns a parsed ABI interface and error if it failed. +func JSON(reader io.Reader) (ABI, error) { + dec := json.NewDecoder(reader) + + var abi ABI + if err := dec.Decode(&abi); err != nil { + return ABI{}, err + } + return abi, nil +} + +// Pack the given method name to conform the ABI. Method call's data +// will consist of method_id, args0, arg1, ... argN. Method id consists +// of 4 bytes and arguments are all 32 bytes. +// Method ids are created from the first 4 bytes of the hash of the +// methods string signature. (signature = baz(uint32,string32)) +func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { + // Fetch the ABI of the requested method + if name == "" { + // constructor + arguments, err := abi.Constructor.Inputs.Pack(args...) + if err != nil { + return nil, err + } + return arguments, nil + } + method, exist := abi.Methods[name] + if !exist { + return nil, fmt.Errorf("method '%s' not found", name) + } + arguments, err := method.Inputs.Pack(args...) + if err != nil { + return nil, err + } + // Pack up the method ID too if not a constructor and return + return append(method.ID, arguments...), nil +} + +func (abi ABI) getArguments(name string, data []byte) (Arguments, error) { + // since there can't be naming collisions with contracts and events, + // we need to decide whether we're calling a method or an event + var args Arguments + if method, ok := abi.Methods[name]; ok { + if len(data)%32 != 0 { + return nil, fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) + } + args = method.Outputs + } + if event, ok := abi.Events[name]; ok { + args = event.Inputs + } + if args == nil { + return nil, errors.New("abi: could not locate named method or event") + } + return args, nil +} + +// Unpack unpacks the output according to the abi specification. +func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) { + args, err := abi.getArguments(name, data) + if err != nil { + return nil, err + } + return args.Unpack(data) +} + +// UnpackIntoInterface unpacks the output in v according to the abi specification. +// It performs an additional copy. Please only use, if you want to unpack into a +// structure that does not strictly conform to the abi structure (e.g. has additional arguments) +func (abi ABI) UnpackIntoInterface(v interface{}, name string, data []byte) error { + args, err := abi.getArguments(name, data) + if err != nil { + return err + } + unpacked, err := args.Unpack(data) + if err != nil { + return err + } + return args.Copy(v, unpacked) +} + +// UnpackIntoMap unpacks a log into the provided map[string]interface{}. +func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { + args, err := abi.getArguments(name, data) + if err != nil { + return err + } + return args.UnpackIntoMap(v, data) +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (abi *ABI) UnmarshalJSON(data []byte) error { + var fields []struct { + Type string + Name string + Inputs []Argument + Outputs []Argument + + // Status indicator which can be: "pure", "view", + // "nonpayable" or "payable". + StateMutability string + + // Deprecated Status indicators, but removed in v0.6.0. + Constant bool // True if function is either pure or view + Payable bool // True if function is payable + + // Event relevant indicator represents the event is + // declared as anonymous. + Anonymous bool + } + if err := json.Unmarshal(data, &fields); err != nil { + return err + } + abi.Methods = make(map[string]Method) + abi.Events = make(map[string]Event) + abi.Errors = make(map[string]Error) + for _, field := range fields { + switch field.Type { + case "constructor": + abi.Constructor = NewMethod("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil) + case "function": + name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok }) + abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, field.Inputs, field.Outputs) + case "fallback": + // New introduced function type in v0.6.0, check more detail + // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function + if abi.HasFallback() { + return errors.New("only single fallback is allowed") + } + abi.Fallback = NewMethod("", "", Fallback, field.StateMutability, field.Constant, field.Payable, nil, nil) + case "receive": + // New introduced function type in v0.6.0, check more detail + // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function + if abi.HasReceive() { + return errors.New("only single receive is allowed") + } + if field.StateMutability != "payable" { + return errors.New("the statemutability of receive can only be payable") + } + abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil) + case "event": + name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok }) + abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs) + case "error": + // Errors cannot be overloaded or overridden but are inherited, + // no need to resolve the name conflict here. + abi.Errors[field.Name] = NewError(field.Name, field.Inputs) + default: + return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name) + } + } + return nil +} + +// MethodById looks up a method by the 4-byte id, +// returns nil if none found. +func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { + if len(sigdata) < 4 { + return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata)) + } + for _, method := range abi.Methods { + if bytes.Equal(method.ID, sigdata[:4]) { + return &method, nil + } + } + return nil, fmt.Errorf("no method with id: %#x", sigdata[:4]) +} + +// EventByID looks an event up by its topic hash in the +// ABI and returns nil if none found. +func (abi *ABI) EventByID(topic common.Hash) (*Event, error) { + for _, event := range abi.Events { + if bytes.Equal(event.ID.Bytes(), topic.Bytes()) { + return &event, nil + } + } + return nil, fmt.Errorf("no event with id: %#x", topic.Hex()) +} + +// HasFallback returns an indicator whether a fallback function is included. +func (abi *ABI) HasFallback() bool { + return abi.Fallback.Type == Fallback +} + +// HasReceive returns an indicator whether a receive function is included. +func (abi *ABI) HasReceive() bool { + return abi.Receive.Type == Receive +} + +// revertSelector is a special function selector for revert reason unpacking. +var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4] + +// UnpackRevert resolves the abi-encoded revert reason. According to the solidity +// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert, +// the provided revert reason is abi-encoded as if it were a call to a function +// `Error(string)`. So it's a special tool for it. +func UnpackRevert(data []byte) (string, error) { + if len(data) < 4 { + return "", errors.New("invalid data for unpacking") + } + if !bytes.Equal(data[:4], revertSelector) { + return "", errors.New("invalid data for unpacking") + } + typ, _ := NewType("string", "", nil) + unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:]) + if err != nil { + return "", err + } + return unpacked[0].(string), nil +} diff --git a/abigen/abi/argument.go b/abigen/abi/argument.go new file mode 100644 index 0000000..c5326d5 --- /dev/null +++ b/abigen/abi/argument.go @@ -0,0 +1,272 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// Argument holds the name of the argument and the corresponding type. +// Types are used when packing and testing arguments. +type Argument struct { + Name string + Type Type + Indexed bool // indexed is only used by events +} + +type Arguments []Argument + +type ArgumentMarshaling struct { + Name string + Type string + InternalType string + Components []ArgumentMarshaling + Indexed bool +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (argument *Argument) UnmarshalJSON(data []byte) error { + var arg ArgumentMarshaling + err := json.Unmarshal(data, &arg) + if err != nil { + return fmt.Errorf("argument json err: %v", err) + } + + argument.Type, err = NewType(arg.Type, arg.InternalType, arg.Components) + if err != nil { + return err + } + argument.Name = arg.Name + argument.Indexed = arg.Indexed + + return nil +} + +// NonIndexed returns the arguments with indexed arguments filtered out. +func (arguments Arguments) NonIndexed() Arguments { + var ret []Argument + for _, arg := range arguments { + if !arg.Indexed { + ret = append(ret, arg) + } + } + return ret +} + +// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]. +func (arguments Arguments) isTuple() bool { + return len(arguments) > 1 +} + +// Unpack performs the operation hexdata -> Go format. +func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { + if len(data) == 0 { + if len(arguments.NonIndexed()) != 0 { + return nil, fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") + } + return make([]interface{}, 0), nil + } + return arguments.UnpackValues(data) +} + +// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value. +func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { + // Make sure map is not nil + if v == nil { + return fmt.Errorf("abi: cannot unpack into a nil map") + } + if len(data) == 0 { + if len(arguments.NonIndexed()) != 0 { + return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") + } + return nil // Nothing to unmarshal, return + } + marshalledValues, err := arguments.UnpackValues(data) + if err != nil { + return err + } + for i, arg := range arguments.NonIndexed() { + v[arg.Name] = marshalledValues[i] + } + return nil +} + +// Copy performs the operation go format -> provided struct. +func (arguments Arguments) Copy(v interface{}, values []interface{}) error { + // make sure the passed value is arguments pointer + if reflect.Ptr != reflect.ValueOf(v).Kind() { + return fmt.Errorf("abi: Unpack(non-pointer %T)", v) + } + if len(values) == 0 { + if len(arguments.NonIndexed()) != 0 { + return fmt.Errorf("abi: attempting to copy no values while arguments are expected") + } + return nil // Nothing to copy, return + } + if arguments.isTuple() { + return arguments.copyTuple(v, values) + } + return arguments.copyAtomic(v, values[0]) +} + +// unpackAtomic unpacks ( hexdata -> go ) a single value +func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error { + dst := reflect.ValueOf(v).Elem() + src := reflect.ValueOf(marshalledValues) + + if dst.Kind() == reflect.Struct { + return set(dst.Field(0), src) + } + return set(dst, src) +} + +// copyTuple copies a batch of values from marshalledValues to v. +func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface{}) error { + value := reflect.ValueOf(v).Elem() + nonIndexedArgs := arguments.NonIndexed() + + switch value.Kind() { + case reflect.Struct: + argNames := make([]string, len(nonIndexedArgs)) + for i, arg := range nonIndexedArgs { + argNames[i] = arg.Name + } + var err error + abi2struct, err := mapArgNamesToStructFields(argNames, value) + if err != nil { + return err + } + for i, arg := range nonIndexedArgs { + field := value.FieldByName(abi2struct[arg.Name]) + if !field.IsValid() { + return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name) + } + if err := set(field, reflect.ValueOf(marshalledValues[i])); err != nil { + return err + } + } + case reflect.Slice, reflect.Array: + if value.Len() < len(marshalledValues) { + return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len()) + } + for i := range nonIndexedArgs { + if err := set(value.Index(i), reflect.ValueOf(marshalledValues[i])); err != nil { + return err + } + } + default: + return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", value.Type()) + } + return nil +} + +// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, +// without supplying a struct to unpack into. Instead, this method returns a list containing the +// values. An atomic argument will be a list with one element. +func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { + nonIndexedArgs := arguments.NonIndexed() + retval := make([]interface{}, 0, len(nonIndexedArgs)) + virtualArgs := 0 + for index, arg := range nonIndexedArgs { + marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) + if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { + // If we have a static array, like [3]uint256, these are coded as + // just like uint256,uint256,uint256. + // This means that we need to add two 'virtual' arguments when + // we count the index from now on. + // + // Array values nested multiple levels deep are also encoded inline: + // [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256 + // + // Calculate the full array size to get the correct offset for the next argument. + // Decrement it by 1, as the normal index increment is still applied. + virtualArgs += getTypeSize(arg.Type)/32 - 1 + } else if arg.Type.T == TupleTy && !isDynamicType(arg.Type) { + // If we have a static tuple, like (uint256, bool, uint256), these are + // coded as just like uint256,bool,uint256 + virtualArgs += getTypeSize(arg.Type)/32 - 1 + } + if err != nil { + return nil, err + } + retval = append(retval, marshalledValue) + } + return retval, nil +} + +// PackValues performs the operation Go format -> Hexdata. +// It is the semantic opposite of UnpackValues. +func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) { + return arguments.Pack(args...) +} + +// Pack performs the operation Go format -> Hexdata. +func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { + // Make sure arguments match up and pack them + abiArgs := arguments + if len(args) != len(abiArgs) { + return nil, fmt.Errorf("argument count mismatch: got %d for %d", len(args), len(abiArgs)) + } + // variable input is the output appended at the end of packed + // output. This is used for strings and bytes types input. + var variableInput []byte + + // input offset is the bytes offset for packed output + inputOffset := 0 + for _, abiArg := range abiArgs { + inputOffset += getTypeSize(abiArg.Type) + } + var ret []byte + for i, a := range args { + input := abiArgs[i] + // pack the input + packed, err := input.Type.pack(reflect.ValueOf(a)) + if err != nil { + return nil, err + } + // check for dynamic types + if isDynamicType(input.Type) { + // set the offset + ret = append(ret, packNum(reflect.ValueOf(inputOffset))...) + // calculate next offset + inputOffset += len(packed) + // append to variable input + variableInput = append(variableInput, packed...) + } else { + // append the packed value to the input + ret = append(ret, packed...) + } + } + // append the variable input at the end of the packed input + ret = append(ret, variableInput...) + + return ret, nil +} + +// ToCamelCase converts an under-score string to a camel-case string +func ToCamelCase(input string) string { + parts := strings.Split(input, "_") + for i, s := range parts { + if len(s) > 0 { + parts[i] = strings.ToUpper(s[:1]) + s[1:] + } + } + return strings.Join(parts, "") +} diff --git a/abigen/abi/bind/backend.go b/abigen/abi/bind/backend.go new file mode 100644 index 0000000..56c3495 --- /dev/null +++ b/abigen/abi/bind/backend.go @@ -0,0 +1,124 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind + +import ( + "context" + "errors" + "math/big" + + "github.com/ChainSQL/go-chainsql-api/common" +) + +var ( + // ErrNoCode is returned by call and transact operations for which the requested + // recipient contract to operate on does not exist in the state db or does not + // have any code associated with it (i.e. suicided). + ErrNoCode = errors.New("no contract code at given address") + + // ErrNoPendingState is raised when attempting to perform a pending state action + // on a backend that doesn't implement PendingContractCaller. + ErrNoPendingState = errors.New("backend does not support pending state") + + // ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves + // an empty contract behind. + ErrNoCodeAfterDeploy = errors.New("no contract code after deployment") +) + +// ContractCaller defines the methods needed to allow operating with a contract on a read +// only basis. +type ContractCaller interface { + // CodeAt returns the code of the given account. This is needed to differentiate + // between contract internal errors and the local chain being out of sync. + CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) + + // CallContract executes an Ethereum contract call with the specified data as the + // input. + // CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) +} + +// PendingContractCaller defines methods to perform contract calls on the pending state. +// Call will try to discover this interface when access to the pending state is requested. +// If the backend does not support the pending state, Call returns ErrNoPendingState. +type PendingContractCaller interface { + // PendingCodeAt returns the code of the given account in the pending state. + PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) + + // PendingCallContract executes an Ethereum contract call against the pending state. + // PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) +} + +// ContractTransactor defines the methods needed to allow operating with a contract +// on a write only basis. Besides the transacting method, the remainder are helpers +// used when the user does not provide some needed values, but rather leaves it up +// to the transactor to decide. +type ContractTransactor interface { + // HeaderByNumber returns a block header from the current canonical chain. If + // number is nil, the latest known header is returned. + // HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) + + // PendingCodeAt returns the code of the given account in the pending state. + PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) + + // PendingNonceAt retrieves the current pending nonce associated with an account. + PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) + + // SuggestGasPrice retrieves the currently suggested gas price to allow a timely + // execution of a transaction. + SuggestGasPrice(ctx context.Context) (*big.Int, error) + + // SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow + // a timely execution of a transaction. + SuggestGasTipCap(ctx context.Context) (*big.Int, error) + + // EstimateGas tries to estimate the gas needed to execute a specific + // transaction based on the current pending state of the backend blockchain. + // There is no guarantee that this is the true gas limit requirement as other + // transactions may be added or removed by miners, but it should provide a basis + // for setting a reasonable default. + // EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) + + // SendTransaction injects the transaction into the pending pool for execution. + // SendTransaction(ctx context.Context, tx *types.Transaction) error +} + +// ContractFilterer defines the methods needed to access log events using one-off +// queries or continuous event subscriptions. +type ContractFilterer interface { + // FilterLogs executes a log filter operation, blocking during execution and + // returning all the results in one batch. + // + // TODO(karalabe): Deprecate when the subscription one can return past data too. + // FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) + + // SubscribeFilterLogs creates a background log filtering operation, returning + // a subscription immediately, which can be used to stream the found events. + // SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) +} + +// DeployBackend wraps the operations needed by WaitMined and WaitDeployed. +type DeployBackend interface { + // TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) +} + +// ContractBackend defines the methods needed to work with contracts on a read-write basis. +type ContractBackend interface { + ContractCaller + ContractTransactor + ContractFilterer +} diff --git a/abigen/abi/bind/bind.go b/abigen/abi/bind/bind.go new file mode 100644 index 0000000..a932ff9 --- /dev/null +++ b/abigen/abi/bind/bind.go @@ -0,0 +1,612 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package bind generates Ethereum contract Go bindings. +// +// Detailed usage document and tutorial available on the go-ethereum Wiki page: +// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts +package bind + +import ( + "bytes" + "errors" + "fmt" + "go/format" + "log" + "regexp" + "strings" + "text/template" + "unicode" + + "github.com/ChainSQL/go-chainsql-api/abigen/abi" +) + +// Lang is a target programming language selector to generate bindings for. +type Lang int + +const ( + LangGo Lang = iota + LangJava + LangObjC +) + +// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant +// to be used as is in client code, but rather as an intermediate struct which +// enforces compile time type safety and naming convention opposed to having to +// manually maintain hard coded strings that break on runtime. +func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) { + var ( + // contracts is the map of each individual contract requested binding + contracts = make(map[string]*tmplContract) + + // structs is the map of all redeclared structs shared by passed contracts. + structs = make(map[string]*tmplStruct) + + // isLib is the map used to flag each encountered library as such + isLib = make(map[string]struct{}) + ) + for i := 0; i < len(types); i++ { + // Parse the actual ABI to generate the binding for + evmABI, err := abi.JSON(strings.NewReader(abis[i])) + if err != nil { + return "", err + } + // Strip any whitespace from the JSON ABI + strippedABI := strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return -1 + } + return r + }, abis[i]) + + // Extract the call and transact methods; events, struct definitions; and sort them alphabetically + var ( + calls = make(map[string]*tmplMethod) + transacts = make(map[string]*tmplMethod) + events = make(map[string]*tmplEvent) + fallback *tmplMethod + receive *tmplMethod + + // identifiers are used to detect duplicated identifiers of functions + // and events. For all calls, transacts and events, abigen will generate + // corresponding bindings. However we have to ensure there is no + // identifier collisions in the bindings of these categories. + callIdentifiers = make(map[string]bool) + transactIdentifiers = make(map[string]bool) + eventIdentifiers = make(map[string]bool) + ) + + for _, input := range evmABI.Constructor.Inputs { + if hasStruct(input.Type) { + bindStructType[lang](input.Type, structs) + } + } + + for _, original := range evmABI.Methods { + // Normalize the method for capital cases and non-anonymous inputs/outputs + normalized := original + normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + + // Ensure there is no duplicated identifier + var identifiers = callIdentifiers + if !original.IsConstant() { + identifiers = transactIdentifiers + } + if identifiers[normalizedName] { + return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + } + identifiers[normalizedName] = true + + normalized.Name = normalizedName + normalized.Inputs = make([]abi.Argument, len(original.Inputs)) + copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { + if input.Name == "" { + normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) + } + if hasStruct(input.Type) { + bindStructType[lang](input.Type, structs) + } + } + normalized.Outputs = make([]abi.Argument, len(original.Outputs)) + copy(normalized.Outputs, original.Outputs) + for j, output := range normalized.Outputs { + if output.Name != "" { + normalized.Outputs[j].Name = capitalise(output.Name) + } + if hasStruct(output.Type) { + bindStructType[lang](output.Type, structs) + } + } + // Append the methods to the call or transact lists + if original.IsConstant() { + calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + } else { + transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + } + } + for _, original := range evmABI.Events { + // Skip anonymous events as they don't support explicit filtering + if original.Anonymous { + continue + } + // Normalize the event for capital cases and non-anonymous outputs + normalized := original + + // Ensure there is no duplicated identifier + normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + if eventIdentifiers[normalizedName] { + return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + } + eventIdentifiers[normalizedName] = true + normalized.Name = normalizedName + + used := make(map[string]bool) + normalized.Inputs = make([]abi.Argument, len(original.Inputs)) + copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { + if input.Name == "" { + normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) + } + // Event is a bit special, we need to define event struct in binding, + // ensure there is no camel-case-style name conflict. + for index := 0; ; index++ { + if !used[capitalise(normalized.Inputs[j].Name)] { + used[capitalise(normalized.Inputs[j].Name)] = true + break + } + normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index) + } + if hasStruct(input.Type) { + bindStructType[lang](input.Type, structs) + } + } + // Append the event to the accumulator list + events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} + } + // Add two special fallback functions if they exist + if evmABI.HasFallback() { + fallback = &tmplMethod{Original: evmABI.Fallback} + } + if evmABI.HasReceive() { + receive = &tmplMethod{Original: evmABI.Receive} + } + // 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{ + Type: capitalise(types[i]), + InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), + InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"), + Constructor: evmABI.Constructor, + Calls: calls, + Transacts: transacts, + Fallback: fallback, + Receive: receive, + Events: events, + Libraries: make(map[string]string), + } + // Function 4-byte signatures are stored in the same sequence + // as types, if available. + if len(fsigs) > i { + contracts[types[i]].FuncSigs = fsigs[i] + } + // Parse library references. + for pattern, name := range libs { + matched, err := regexp.Match("__\\$"+pattern+"\\$__", []byte(contracts[types[i]].InputBin)) + if err != nil { + log.Printf("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err) + } + if matched { + contracts[types[i]].Libraries[pattern] = name + // keep track that this type is a library + if _, ok := isLib[name]; !ok { + isLib[name] = struct{}{} + } + } + } + } + // Check if that type has already been identified as a library + for i := 0; i < len(types); i++ { + _, ok := isLib[types[i]] + contracts[types[i]].Library = ok + } + // Generate the contract template data content and render it + data := &tmplData{ + Package: pkg, + Contracts: contracts, + Libraries: libs, + Structs: structs, + } + buffer := new(bytes.Buffer) + + funcs := map[string]interface{}{ + "bindtype": bindType[lang], + "bindtopictype": bindTopicType[lang], + "namedtype": namedType[lang], + "capitalise": capitalise, + "decapitalise": decapitalise, + } + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) + if err := tmpl.Execute(buffer, data); err != nil { + return "", err + } + // For Go bindings pass the code through gofmt to clean it up + if lang == LangGo { + code, err := format.Source(buffer.Bytes()) + if err != nil { + return "", fmt.Errorf("%v\n%s", err, buffer) + } + return string(code), nil + } + // For all others just return as is for now + return buffer.String(), nil +} + +// bindType is a set of type binders that convert Solidity types to some supported +// programming language types. +var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ + LangGo: bindTypeGo, + LangJava: bindTypeJava, +} + +// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. +func bindBasicTypeGo(kind abi.Type) string { + switch kind.T { + case abi.AddressTy: + return "common.Address" + case abi.IntTy, abi.UintTy: + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) + switch parts[2] { + case "8", "16", "32", "64": + return fmt.Sprintf("%sint%s", parts[1], parts[2]) + } + return "*big.Int" + case abi.FixedBytesTy: + return fmt.Sprintf("[%d]byte", kind.Size) + case abi.BytesTy: + return "[]byte" + case abi.FunctionTy: + return "[24]byte" + default: + // string, bool types + return kind.String() + } +} + +// 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 +// mapped will use an upscaled type (e.g. BigDecimal). +func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { + switch kind.T { + case abi.TupleTy: + return structs[kind.TupleRawName+kind.String()].Name + case abi.ArrayTy: + return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs) + case abi.SliceTy: + return "[]" + bindTypeGo(*kind.Elem, structs) + default: + return bindBasicTypeGo(kind) + } +} + +// bindBasicTypeJava converts basic solidity types(except array, slice and tuple) to Java ones. +func bindBasicTypeJava(kind abi.Type) string { + switch kind.T { + case abi.AddressTy: + return "Address" + case abi.IntTy, abi.UintTy: + // Note that uint and int (without digits) are also matched, + // these are size 256, and will translate to BigInt (the default). + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) + if len(parts) != 3 { + return kind.String() + } + // All unsigned integers should be translated to BigInt since gomobile doesn't + // support them. + if parts[1] == "u" { + return "BigInt" + } + + namedSize := map[string]string{ + "8": "byte", + "16": "short", + "32": "int", + "64": "long", + }[parts[2]] + + // default to BigInt + if namedSize == "" { + namedSize = "BigInt" + } + return namedSize + case abi.FixedBytesTy, abi.BytesTy: + return "byte[]" + case abi.BoolTy: + return "boolean" + case abi.StringTy: + return "String" + case abi.FunctionTy: + return "byte[24]" + default: + return kind.String() + } +} + +// pluralizeJavaType explicitly converts multidimensional types to predefined +// types in go side. +func pluralizeJavaType(typ string) string { + switch typ { + case "boolean": + return "Bools" + case "String": + return "Strings" + case "Address": + return "Addresses" + case "byte[]": + return "Binaries" + case "BigInt": + return "BigInts" + } + 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.TupleRawName+kind.String()].Name + case abi.ArrayTy, abi.SliceTy: + return pluralizeJavaType(bindTypeJava(*kind.Elem, structs)) + default: + return bindBasicTypeJava(kind) + } +} + +// bindTopicType is a set of type binders that convert Solidity types to some +// supported programming language topic types. +var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ + LangGo: bindTopicTypeGo, + LangJava: bindTopicTypeJava, +} + +// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same +// functionality as for simple types, but dynamic types get converted to hashes. +func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { + bound := bindTypeGo(kind, structs) + + // todo(rjl493456442) according solidity documentation, indexed event + // parameters that are not value types i.e. arrays and structs are not + // stored directly but instead a keccak256-hash of an encoding is stored. + // + // We only convert stringS and bytes to hash, still need to deal with + // array(both fixed-size and dynamic-size) and struct. + if bound == "string" || bound == "[]byte" { + bound = "common.Hash" + } + return bound +} + +// bindTopicTypeJava converts a Solidity topic type to a Java one. It is almost the same +// functionality as for simple types, but dynamic types get converted to hashes. +func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { + bound := bindTypeJava(kind, structs) + + // todo(rjl493456442) according solidity documentation, indexed event + // parameters that are not value types i.e. arrays and structs are not + // stored directly but instead a keccak256-hash of an encoding is stored. + // + // We only convert strings and bytes to hash, still need to deal with + // array(both fixed-size and dynamic-size) and struct. + if bound == "String" || bound == "byte[]" { + bound = "Hash" + } + 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: + // We compose a raw struct name and a canonical parameter expression + // together here. The reason is before solidity v0.5.11, kind.TupleRawName + // is empty, so we use canonical parameter expression to distinguish + // different struct definition. From the consideration of backward + // compatibility, we concat these two together so that if kind.TupleRawName + // is not empty, it can have unique id. + id := kind.TupleRawName + kind.String() + if s, exist := structs[id]; exist { + return s.Name + } + var ( + names = make(map[string]bool) + fields []*tmplField + ) + for i, elem := range kind.TupleElems { + name := capitalise(kind.TupleRawNames[i]) + name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] }) + names[name] = true + fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem}) + } + name := kind.TupleRawName + if name == "" { + name = fmt.Sprintf("Struct%d", len(structs)) + } + name = capitalise(name) + + structs[id] = &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: + // We compose a raw struct name and a canonical parameter expression + // together here. The reason is before solidity v0.5.11, kind.TupleRawName + // is empty, so we use canonical parameter expression to distinguish + // different struct definition. From the consideration of backward + // compatibility, we concat these two together so that if kind.TupleRawName + // is not empty, it can have unique id. + id := kind.TupleRawName + kind.String() + if s, exist := structs[id]; 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 := kind.TupleRawName + if name == "" { + name = fmt.Sprintf("Class%d", len(structs)) + } + structs[id] = &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 +// named versions that may be used inside method names. +var namedType = map[Lang]func(string, abi.Type) string{ + LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, + LangJava: namedTypeJava, +} + +// namedTypeJava converts some primitive data types to named variants that can +// be used as parts of method names. +func namedTypeJava(javaKind string, solKind abi.Type) string { + switch javaKind { + case "byte[]": + return "Binary" + case "boolean": + return "Bool" + default: + parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String()) + if len(parts) != 4 { + return javaKind + } + switch parts[2] { + case "8", "16", "32", "64": + if parts[3] == "" { + return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2])) + } + return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2])) + + default: + return javaKind + } + } +} + +// alias returns an alias of the given string based on the aliasing rules +// or returns itself if no rule is matched. +func alias(aliases map[string]string, n string) string { + if alias, exist := aliases[n]; exist { + return alias + } + return n +} + +// methodNormalizer is a name transformer that modifies Solidity method names to +// conform to target language naming conventions. +var methodNormalizer = map[Lang]func(string) string{ + LangGo: abi.ToCamelCase, + LangJava: decapitalise, +} + +// capitalise makes a camel-case string which starts with an upper case character. +var capitalise = abi.ToCamelCase + +// decapitalise makes a camel-case string which starts with a lower case character. +func decapitalise(input string) string { + if len(input) == 0 { + return input + } + + goForm := abi.ToCamelCase(input) + return strings.ToLower(goForm[:1]) + goForm[1:] +} + +// structured checks whether a list of ABI data types has enough information to +// operate through a proper Go struct or if flat returns are needed. +func structured(args abi.Arguments) bool { + if len(args) < 2 { + return false + } + exists := make(map[string]bool) + for _, out := range args { + // If the name is anonymous, we can't organize into a struct + if out.Name == "" { + return false + } + // If the field name is empty when normalized or collides (var, Var, _var, _Var), + // we can't organize into a struct + field := capitalise(out.Name) + if field == "" || exists[field] { + return false + } + exists[field] = true + } + return true +} + +// hasStruct returns an indicator whether the given type is struct, struct slice +// or struct array. +func hasStruct(t abi.Type) bool { + switch t.T { + case abi.SliceTy: + return hasStruct(*t.Elem) + case abi.ArrayTy: + return hasStruct(*t.Elem) + case abi.TupleTy: + return true + default: + return false + } +} diff --git a/abigen/abi/bind/template.go b/abigen/abi/bind/template.go new file mode 100644 index 0000000..b53ea14 --- /dev/null +++ b/abigen/abi/bind/template.go @@ -0,0 +1,732 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bind + +import "github.com/ChainSQL/go-chainsql-api/abigen/abi" + +// tmplData is the data structure required to fill the binding template. +type tmplData struct { + Package string // Name of the package to place the generated file in + Contracts map[string]*tmplContract // List of contracts to generate into this file + Libraries map[string]string // Map the bytecode's link pattern to the library name + Structs map[string]*tmplStruct // Contract struct type definitions +} + +// tmplContract contains the data needed to generate an individual contract binding. +type tmplContract struct { + Type string // Type name of the main contract binding + InputABI string // JSON ABI used as the input to generate the binding from + InputBin string // Optional EVM bytecode used to generate deploy code from + FuncSigs map[string]string // Optional map: string signature -> 4-byte signature + Constructor abi.Method // Contract constructor for deploy parametrization + Calls map[string]*tmplMethod // Contract calls that only read state data + Transacts map[string]*tmplMethod // Contract calls that write state data + Fallback *tmplMethod // Additional special fallback function + Receive *tmplMethod // Additional special receive function + Events map[string]*tmplEvent // Contract events accessors + Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs + Library bool // Indicator whether the contract is a library +} + +// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed +// and cached data fields. +type tmplMethod struct { + Original abi.Method // Original method as parsed by the abi package + Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns) + Structured bool // Whether the returns should be accumulated into a struct +} + +// tmplEvent is a wrapper around an abi.Event that contains a few preprocessed +// and cached data fields. +type tmplEvent struct { + Original abi.Event // Original event as parsed by the abi package + 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 and contains an auto-generated +// struct name. +type tmplStruct struct { + Name string // Auto-generated struct name(before solidity v0.5.11) or raw name. + Fields []*tmplField // Struct fields definition depends on the binding language. +} + +// tmplSource is language to template mapping containing all the supported +// programming languages the package can generate to. +var tmplSource = map[Lang]string{ + LangGo: tmplSourceGo, + LangJava: tmplSourceJava, +} + +// tmplSourceGo is the Go source template that the generated Go contract binding +// is based on. +const tmplSourceGo = ` +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package {{.Package}} + +import ( + "errors" + "math/big" + "strings" + + "github.com/ChainSQL/go-chainsql-api/abigen/abi" + "github.com/ChainSQL/go-chainsql-api/abigen/abi/bind" + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/core" + "github.com/ChainSQL/go-chainsql-api/data" + "github.com/ChainSQL/go-chainsql-api/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = bind.Bind + _ = common.Big1 +) + +{{$structs := .Structs}} +{{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 $contract := .Contracts}} + // {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract. + var {{.Type}}MetaData = &core.CtrMetaData{ + ABI: "{{.InputABI}}", + {{if $contract.FuncSigs -}} + Sigs: map[string]string{ + {{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}", + {{end}} + }, + {{end -}} + {{if .InputBin -}} + Bin: "0x{{.InputBin}}", + {{end}} + } + // {{.Type}}ABI is the input ABI used to generate the binding from. + // Deprecated: Use {{.Type}}MetaData.ABI instead. + var {{.Type}}ABI = {{.Type}}MetaData.ABI + + {{if $contract.FuncSigs}} + // Deprecated: Use {{.Type}}MetaData.Sigs instead. + // {{.Type}}FuncSigs maps the 4-byte function signature to its string representation. + var {{.Type}}FuncSigs = {{.Type}}MetaData.Sigs + {{end}} + + {{if .InputBin}} + // {{.Type}}Bin is the compiled bytecode used for deploying new contracts. + // Deprecated: Use {{.Type}}MetaData.Bin instead. + var {{.Type}}Bin = {{.Type}}MetaData.Bin + + // Deploy{{.Type}} deploys a new ChainSQL contract, binding an instance of {{.Type}} to it. + func Deploy{{.Type}}(chainsql *core.Chainsql, auth *core.TransactOpts {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}}{{end}}) (*core.DeployTxRet, *{{.Type}}, error) { + parsed, err := {{.Type}}MetaData.GetAbi() + if err != nil { + return &core.DeployTxRet{}, nil, err + } + if parsed == nil { + return &core.DeployTxRet{}, nil, errors.New("GetABI returned nil") + } + {{range $pattern, $name := .Libraries}} + {{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(chainsql, auth) + {{$contract.Type}}Bin = strings.ReplaceAll({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:]) + {{end}} + deployRet, contract, err := core.DeployContract(chainsql, auth, *parsed, common.FromHex({{.Type}}Bin) {{range .Constructor.Inputs}}, {{.Name}}{{end}}) + if err != nil { + return &core.DeployTxRet{}, nil, err + } + return deployRet, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil + } + {{end}} + + // {{.Type}} is an auto generated Go binding around an ChainSQL contract. + type {{.Type}} struct { + {{.Type}}Caller // Read-only binding to the contract + {{.Type}}Transactor // Write-only binding to the contract + {{.Type}}Filterer // Log filterer for contract events + } + + // {{.Type}}Caller is an auto generated read-only Go binding around an ChainSQL contract. + type {{.Type}}Caller struct { + contract *core.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Transactor is an auto generated write-only Go binding around an ChainSQL contract. + type {{.Type}}Transactor struct { + contract *core.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Filterer is an auto generated log filtering Go binding around an ChainSQL contract events. + type {{.Type}}Filterer struct { + contract *core.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Session is an auto generated Go binding around an ChainSQL contract, + // with pre-set call and transact options. + type {{.Type}}Session struct { + Contract *{{.Type}} // Generic contract binding to set the session for + CallOpts core.CallOpts // Call options to use throughout this session + TransactOpts core.TransactOpts // Transaction auth options to use throughout this session + } + + // {{.Type}}CallerSession is an auto generated read-only Go binding around an ChainSQL contract, + // with pre-set call options. + type {{.Type}}CallerSession struct { + Contract *{{.Type}}Caller // Generic contract caller binding to set the session for + CallOpts core.CallOpts // Call options to use throughout this session + } + + // {{.Type}}TransactorSession is an auto generated write-only Go binding around an ChainSQL contract, + // with pre-set transact options. + type {{.Type}}TransactorSession struct { + Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for + TransactOpts core.TransactOpts // Transaction auth options to use throughout this session + } + + // {{.Type}}Raw is an auto generated low-level Go binding around an ChainSQL contract. + type {{.Type}}Raw struct { + Contract *{{.Type}} // Generic contract binding to access the raw methods on + } + + // {{.Type}}CallerRaw is an auto generated low-level read-only Go binding around an ChainSQL contract. + type {{.Type}}CallerRaw struct { + Contract *{{.Type}}Caller // Generic read-only contract binding to access the raw methods on + } + + // {{.Type}}TransactorRaw is an auto generated low-level write-only Go binding around an ChainSQL contract. + type {{.Type}}TransactorRaw struct { + Contract *{{.Type}}Transactor // Generic write-only contract binding to access the raw methods on + } + + // New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}(chainsql *core.Chainsql, address string) (*{{.Type}}, error) { + contract, err := bind{{.Type}}(chainsql, address) + if err != nil { + return nil, err + } + return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil + } + + // // New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract. + // func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) { + // contract, err := bind{{.Type}}(address, caller, nil, nil) + // if err != nil { + // return nil, err + // } + // return &{{.Type}}Caller{contract: contract}, nil + // } + + // // New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract. + // func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) { + // contract, err := bind{{.Type}}(address, nil, transactor, nil) + // if err != nil { + // return nil, err + // } + // return &{{.Type}}Transactor{contract: contract}, nil + // } + + // // New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract. + // func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) { + // contract, err := bind{{.Type}}(address, nil, nil, filterer) + // if err != nil { + // return nil, err + // } + // return &{{.Type}}Filterer{contract: contract}, nil + // } + + // bind{{.Type}} binds a generic wrapper to an already deployed contract. + func bind{{.Type}}(chainsql *core.Chainsql, address string) (*core.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI)) + if err != nil { + return nil, err + } + return core.NewBoundContract(chainsql, address, parsed), nil + } + + // Call invokes the (constant) contract method with params as input values and + // sets the output to result. The result type might be a single field for simple + // returns, a slice of interfaces for anonymous returns and a struct for named + // returns. + // func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + // return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...) + // } + + // Transfer initiates a plain transaction to move funds to the contract, calling + // its default method if one is available. + // func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + // return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transfer(opts) + // } + + // Transact invokes the (paid) contract method with params as input values. + // func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + // return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transact(opts, method, params...) + // } + + // Call invokes the (constant) contract method with params as input values and + // sets the output to result. The result type might be a single field for simple + // returns, a slice of interfaces for anonymous returns and a struct for named + // returns. + // func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *core.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + // return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...) + // } + + // Transfer initiates a plain transaction to move funds to the contract, calling + // its default method if one is available. + // func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transfer(opts *core.TransactOpts) (*types.Transaction, error) { + // return _{{$contract.Type}}.Contract.contract.Transfer(opts) + // } + + // Transact invokes the (paid) contract method with params as input values. + // func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transact(opts *core.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + // return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...) + // } + + {{range .Calls}} + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *core.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) { + var out []interface{} + err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + {{if .Structured}} + outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} }) + if err != nil { + return *outstruct, err + } + {{range $i, $t := .Normalized.Outputs}} + outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return *outstruct, err + {{else}} + if err != nil { + return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err + } + {{range $i, $t := .Normalized.Outputs}} + out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err + {{end}} + } + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + 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}}) + } + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + 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}}) + } + {{end}} + + {{range .Transacts}} + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *core.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) (*common.TxResult, error) { + 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}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*common.TxResult, error) { + 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}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*common.TxResult, error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) + } + {{end}} + + {{if .Fallback}} + // // Fallback is a paid mutator transaction binding the contract fallback function. + // // + // // Solidity: {{.Fallback.Original.String}} + // func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *core.TransactOpts, calldata []byte) (*types.Transaction, error) { + // return _{{$contract.Type}}.contract.RawTransact(opts, calldata) + // } + + // // Fallback is a paid mutator transaction binding the contract fallback function. + // // + // // Solidity: {{.Fallback.Original.String}} + // func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) { + // return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) + // } + + // // Fallback is a paid mutator transaction binding the contract fallback function. + // // + // // Solidity: {{.Fallback.Original.String}} + // func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + // return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) + // } + {{end}} + + {{if .Receive}} + // // Receive is a paid mutator transaction binding the contract receive function. + // // + // // Solidity: {{.Receive.Original.String}} + // func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *core.TransactOpts) (*types.Transaction, error) { + // return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function + // } + + // // Receive is a paid mutator transaction binding the contract receive function. + // // + // // Solidity: {{.Receive.Original.String}} + // func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) { + // return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) + // } + + // // Receive is a paid mutator transaction binding the contract receive function. + // // + // // Solidity: {{.Receive.Original.String}} + // func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) { + // return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) + // } + {{end}} + + {{range .Events}} + // {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract. + type {{$contract.Type}}{{.Normalized.Name}}Iterator struct { + Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log + + contract *core.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan data.Log // Log channel receiving the found contract events + sub event.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration + } + // Next advances the iterator to the subsequent event, returning whether there + // are any more events found. In case of a retrieval or parsing error, false is + // returned and Error() can be queried for the exact failure. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool { + // If the iterator failed, stop iterating + if (it.fail != nil) { + return false + } + // If the iterator completed, deliver directly whatever's available + if (it.done) { + select { + case log := <-it.logs: + it.Event = new({{$contract.Type}}{{.Normalized.Name}}) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new({{$contract.Type}}{{.Normalized.Name}}) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } + } + // Error returns any retrieval or parsing error occurred during filtering. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error { + return it.fail + } + // Close terminates the iteration process, releasing any pending underlying + // resources. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error { + it.sub.Unsubscribe() + return nil + } + + // {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract. + type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}} + {{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}} + Raw data.Log // Blockchain specific contextual infos + } + + // Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + // func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *core.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) { + // {{range .Normalized.Inputs}} + // {{if .Indexed}}var {{.Name}}Rule []interface{} + // for _, {{.Name}}Item := range {{.Name}} { + // {{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item) + // }{{end}}{{end}} + + // logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}}) + // if err != nil { + // return nil, err + // } + // return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil + // } + + // Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *core.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (event.Subscription, error) { + {{range .Normalized.Inputs}} + {{if .Indexed}}var {{.Name}}Rule []interface{} + for _, {{.Name}}Item := range {{.Name}} { + {{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item) + }{{end}}{{end}} + + sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}}) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.UnSubscribe() + for { + select { + case log := <-sub.EventMsgCh: + // New log arrived, parse the event and forward to the user + event := new({{$contract.Type}}{{.Normalized.Name}}) + if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", *log); err != nil { + return err + } + event.Raw = *log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil + } + + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Get{{.Normalized.Name}}PastEvent(txHash string, ContractLogs string) ([]*{{$contract.Type}}{{.Normalized.Name}}, error) { + var logRaws [] *data.Log + var err error + if ContractLogs != "" { + logRaws, err = _{{$contract.Type}}.contract.GetPastEventByCtrLog(ContractLogs) + } else if txHash != "" { + logRaws, err = _{{$contract.Type}}.contract.GetPastEventByTxHash(txHash) + } else { + return nil, errors.New("both txHash or ContractLogs is not provided for param") + } + + if err != nil { + return nil, err + } + var events []*{{$contract.Type}}{{.Normalized.Name}} + for _, logRaw := range logRaws { + event, err := _{{$contract.Type}}.Parse{{.Normalized.Name}}(*logRaw) + if err != nil && err.Error() != "event signature mismatch" { + return nil, err + } + if event != nil { + events = append(events, event) + } + } + return events, nil + } + + // Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log data.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) { + event := new({{$contract.Type}}{{.Normalized.Name}}) + if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil + } + + {{end}} +{{end}} +` + +// tmplSourceJava is the Java source template that the generated Java contract binding +// is based on. +const tmplSourceJava = ` +// This file is an automatically generated Java binding. Do not modify as any +// change will likely be lost upon the next re-generation! + +package {{.Package}}; + +import org.ChainSQL.geth.*; +import java.util.*; + +{{$structs := .Structs}} +{{range $contract := .Contracts}} +{{if not .Library}}public {{end}}class {{.Type}} { + // ABI is the input ABI used to generate the binding from. + public final static String ABI = "{{.InputABI}}"; + {{if $contract.FuncSigs}} + // {{.Type}}FuncSigs maps the 4-byte function signature to its string representation. + public final static Map {{.Type}}FuncSigs; + static { + Hashtable temp = new Hashtable(); + {{range $strsig, $binsig := .FuncSigs}}temp.put("{{$binsig}}", "{{$strsig}}"); + {{end}} + {{.Type}}FuncSigs = Collections.unmodifiableMap(temp); + } + {{end}} + {{if .InputBin}} + // BYTECODE is the compiled bytecode used for deploying new contracts. + public final static String BYTECODE = "0x{{.InputBin}}"; + + // deploy deploys a new ChainSQL contract, binding an instance of {{.Type}} to it. + public static {{.Type}} deploy(TransactOpts auth, ChainSQLClient client{{range .Constructor.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Constructor.Inputs)}}); + String bytecode = BYTECODE; + {{if .Libraries}} + + // "link" contract to dependent libraries by deploying them first. + {{range $pattern, $name := .Libraries}} + {{capitalise $name}} {{decapitalise $name}}Inst = {{capitalise $name}}.deploy(auth, client); + bytecode = bytecode.replace("__${{$pattern}}$__", {{decapitalise $name}}Inst.Address.getHex().substring(2)); + {{end}} + {{end}} + {{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}} + return new {{.Type}}(Geth.deployContract(auth, ABI, Geth.decodeFromHex(bytecode), client, args)); + } + + // Internal constructor used by contract deployment. + private {{.Type}}(BoundContract deployment) { + this.Address = deployment.getAddress(); + this.Deployer = deployment.getDeployer(); + this.Contract = deployment; + } + {{end}} + + // ChainSQL address where this contract is located at. + public final Address Address; + + // ChainSQL transaction in which this contract was deployed (if known!). + public final Transaction Deployer; + + // Contract instance bound to a blockchain address. + private final BoundContract Contract; + + // Creates a new instance of {{.Type}}, bound to a specific deployed contract. + public {{.Type}}(Address address, ChainSQLClient client) throws Exception { + this(Geth.bindContract(address, ABI, client)); + } + + {{range .Calls}} + {{if gt (len .Normalized.Outputs) 1}} + // {{capitalise .Normalized.Name}}Results is the output of a call to {{.Normalized.Name}}. + public class {{capitalise .Normalized.Name}}Results { + {{range $index, $item := .Normalized.Outputs}}public {{bindtype .Type $structs}} {{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}}; + {{end}} + } + {{end}} + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else if eq (len .Normalized.Outputs) 0}}void{{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)}}); + {{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}} + + Interfaces results = Geth.newInterfaces({{(len .Normalized.Outputs)}}); + {{range $index, $item := .Normalized.Outputs}}Interface result{{$index}} = Geth.newInterface(); result{{$index}}.setDefault{{namedtype (bindtype .Type $structs) .Type}}(); results.set({{$index}}, result{{$index}}); + {{end}} + + if (opts == null) { + opts = Geth.newCallOpts(); + } + this.Contract.call(opts, results, "{{.Original.Name}}", args); + {{if gt (len .Normalized.Outputs) 1}} + {{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 $structs) .Type}}(); + {{end}} + return result; + {{else}}{{range .Normalized.Outputs}}return results.get(0).get{{namedtype (bindtype .Type $structs) .Type}}();{{end}} + {{end}} + } + {{end}} + + {{range .Transacts}} + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + public Transaction {{.Normalized.Name}}(TransactOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); + {{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}} + return this.Contract.transact(opts, "{{.Original.Name}}" , args); + } + {{end}} + + {{if .Fallback}} + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{.Fallback.Original.String}} + public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception { + return this.Contract.rawTransact(opts, calldata); + } + {{end}} + + {{if .Receive}} + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{.Receive.Original.String}} + public Transaction Receive(TransactOpts opts) throws Exception { + return this.Contract.rawTransact(opts, null); + } + {{end}} +} +{{end}} +` diff --git a/abigen/abi/error.go b/abigen/abi/error.go new file mode 100644 index 0000000..1a204c2 --- /dev/null +++ b/abigen/abi/error.go @@ -0,0 +1,93 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "bytes" + "errors" + "fmt" + "strings" + + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/crypto" +) + +type Error struct { + Name string + Inputs Arguments + str string + + // Sig contains the string signature according to the ABI spec. + // e.g. error foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + + // ID returns the canonical representation of the error's signature used by the + // abi definition to identify event names and types. + ID common.Hash +} + +func NewError(name string, inputs Arguments) Error { + // sanitize inputs to remove inputs without names + // and precompute string and sig representation. + names := make([]string, len(inputs)) + types := make([]string, len(inputs)) + for i, input := range inputs { + if input.Name == "" { + inputs[i] = Argument{ + Name: fmt.Sprintf("arg%d", i), + Indexed: input.Indexed, + Type: input.Type, + } + } else { + inputs[i] = input + } + // string representation + names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name) + if input.Indexed { + names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name) + } + // sig representation + types[i] = input.Type.String() + } + + str := fmt.Sprintf("error %v(%v)", name, strings.Join(names, ", ")) + sig := fmt.Sprintf("%v(%v)", name, strings.Join(types, ",")) + id := common.BytesToHash(crypto.Keccak256([]byte(sig))) + + return Error{ + Name: name, + Inputs: inputs, + str: str, + Sig: sig, + ID: id, + } +} + +func (e *Error) String() string { + return e.str +} + +func (e *Error) Unpack(data []byte) (interface{}, error) { + if len(data) < 4 { + return "", errors.New("invalid data for unpacking") + } + if !bytes.Equal(data[:4], e.ID[:4]) { + return "", errors.New("invalid data for unpacking") + } + return e.Inputs.Unpack(data[4:]) +} diff --git a/abigen/abi/error_handling.go b/abigen/abi/error_handling.go new file mode 100644 index 0000000..f0f71b6 --- /dev/null +++ b/abigen/abi/error_handling.go @@ -0,0 +1,82 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "errors" + "fmt" + "reflect" +) + +var ( + errBadBool = errors.New("abi: improperly encoded boolean value") +) + +// formatSliceString formats the reflection kind with the given slice size +// and returns a formatted string representation. +func formatSliceString(kind reflect.Kind, sliceSize int) string { + if sliceSize == -1 { + return fmt.Sprintf("[]%v", kind) + } + return fmt.Sprintf("[%d]%v", sliceSize, kind) +} + +// sliceTypeCheck checks that the given slice can by assigned to the reflection +// type in t. +func sliceTypeCheck(t Type, val reflect.Value) error { + if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { + return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type()) + } + + if t.T == ArrayTy && val.Len() != t.Size { + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) + } + + if t.Elem.T == SliceTy || t.Elem.T == ArrayTy { + if val.Len() > 0 { + return sliceTypeCheck(*t.Elem, val.Index(0)) + } + } + + if val.Type().Elem().Kind() != t.Elem.GetType().Kind() { + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type()) + } + return nil +} + +// typeCheck checks that the given reflection value can be assigned to the reflection +// type in t. +func typeCheck(t Type, value reflect.Value) error { + if t.T == SliceTy || t.T == ArrayTy { + return sliceTypeCheck(t, value) + } + + // Check base type validity. Element types will be checked later on. + if t.GetType().Kind() != value.Kind() { + return typeErr(t.GetType().Kind(), value.Kind()) + } else if t.T == FixedBytesTy && t.Size != value.Len() { + return typeErr(t.GetType(), value.Type()) + } else { + return nil + } + +} + +// typeErr returns a formatted type casting error. +func typeErr(expected, got interface{}) error { + return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected) +} diff --git a/abigen/abi/event.go b/abigen/abi/event.go new file mode 100644 index 0000000..20aaf82 --- /dev/null +++ b/abigen/abi/event.go @@ -0,0 +1,103 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "fmt" + "strings" + + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/crypto" +) + +// Event is an event potentially triggered by the EVM's LOG mechanism. The Event +// holds type information (inputs) about the yielded output. Anonymous events +// don't get the signature canonical representation as the first LOG topic. +type Event struct { + // Name is the event name used for internal representation. It's derived from + // the raw name and a suffix will be added in the case of event overloading. + // + // e.g. + // These are two events that have the same name: + // * foo(int,int) + // * foo(uint,uint) + // The event name of the first one will be resolved as foo while the second one + // will be resolved as foo0. + Name string + + // RawName is the raw event name parsed from ABI. + RawName string + Anonymous bool + Inputs Arguments + str string + + // Sig contains the string signature according to the ABI spec. + // e.g. event foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + + // ID returns the canonical representation of the event's signature used by the + // abi definition to identify event names and types. + ID common.Hash +} + +// NewEvent creates a new Event. +// It sanitizes the input arguments to remove unnamed arguments. +// It also precomputes the id, signature and string representation +// of the event. +func NewEvent(name, rawName string, anonymous bool, inputs Arguments) Event { + // sanitize inputs to remove inputs without names + // and precompute string and sig representation. + names := make([]string, len(inputs)) + types := make([]string, len(inputs)) + for i, input := range inputs { + if input.Name == "" { + inputs[i] = Argument{ + Name: fmt.Sprintf("arg%d", i), + Indexed: input.Indexed, + Type: input.Type, + } + } else { + inputs[i] = input + } + // string representation + names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name) + if input.Indexed { + names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name) + } + // sig representation + types[i] = input.Type.String() + } + + str := fmt.Sprintf("event %v(%v)", rawName, strings.Join(names, ", ")) + sig := fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ",")) + id := common.BytesToHash(crypto.Keccak256([]byte(sig))) + + return Event{ + Name: name, + RawName: rawName, + Anonymous: anonymous, + Inputs: inputs, + str: str, + Sig: sig, + ID: id, + } +} + +func (e Event) String() string { + return e.str +} diff --git a/abigen/abi/method.go b/abigen/abi/method.go new file mode 100644 index 0000000..8969efb --- /dev/null +++ b/abigen/abi/method.go @@ -0,0 +1,167 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "fmt" + "strings" + + "github.com/ChainSQL/go-chainsql-api/crypto" +) + +// FunctionType represents different types of functions a contract might have. +type FunctionType int + +const ( + // Constructor represents the constructor of the contract. + // The constructor function is called while deploying a contract. + Constructor FunctionType = iota + // Fallback represents the fallback function. + // This function is executed if no other function matches the given function + // signature and no receive function is specified. + Fallback + // Receive represents the receive function. + // This function is executed on plain Ether transfers. + Receive + // Function represents a normal function. + Function +) + +// Method represents a callable given a `Name` and whether the method is a constant. +// If the method is `Const` no transaction needs to be created for this +// particular Method call. It can easily be simulated using a local VM. +// For example a `Balance()` method only needs to retrieve something +// from the storage and therefore requires no Tx to be sent to the +// network. A method such as `Transact` does require a Tx and thus will +// be flagged `false`. +// Input specifies the required input parameters for this gives method. +type Method struct { + // Name is the method name used for internal representation. It's derived from + // the raw name and a suffix will be added in the case of a function overload. + // + // e.g. + // These are two functions that have the same name: + // * foo(int,int) + // * foo(uint,uint) + // The method name of the first one will be resolved as foo while the second one + // will be resolved as foo0. + Name string + RawName string // RawName is the raw method name parsed from ABI + + // Type indicates whether the method is a + // special fallback introduced in solidity v0.6.0 + Type FunctionType + + // StateMutability indicates the mutability state of method, + // the default value is nonpayable. It can be empty if the abi + // is generated by legacy compiler. + StateMutability string + + // Legacy indicators generated by compiler before v0.6.0 + Constant bool + Payable bool + + Inputs Arguments + Outputs Arguments + str string + // Sig returns the methods string signature according to the ABI spec. + // e.g. function foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + // ID returns the canonical representation of the method's signature used by the + // abi definition to identify method names and types. + ID []byte +} + +// NewMethod creates a new Method. +// A method should always be created using NewMethod. +// It also precomputes the sig representation and the string representation +// of the method. +func NewMethod(name string, rawName string, funType FunctionType, mutability string, isConst, isPayable bool, inputs Arguments, outputs Arguments) Method { + var ( + types = make([]string, len(inputs)) + inputNames = make([]string, len(inputs)) + outputNames = make([]string, len(outputs)) + ) + for i, input := range inputs { + inputNames[i] = fmt.Sprintf("%v %v", input.Type, input.Name) + types[i] = input.Type.String() + } + for i, output := range outputs { + outputNames[i] = output.Type.String() + if len(output.Name) > 0 { + outputNames[i] += fmt.Sprintf(" %v", output.Name) + } + } + // calculate the signature and method id. Note only function + // has meaningful signature and id. + var ( + sig string + id []byte + ) + if funType == Function { + sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ",")) + id = crypto.Keccak256([]byte(sig))[:4] + } + // Extract meaningful state mutability of solidity method. + // If it's default value, never print it. + state := mutability + if state == "nonpayable" { + state = "" + } + if state != "" { + state = state + " " + } + identity := fmt.Sprintf("function %v", rawName) + if funType == Fallback { + identity = "fallback" + } else if funType == Receive { + identity = "receive" + } else if funType == Constructor { + identity = "constructor" + } + str := fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputNames, ", "), state, strings.Join(outputNames, ", ")) + + return Method{ + Name: name, + RawName: rawName, + Type: funType, + StateMutability: mutability, + Constant: isConst, + Payable: isPayable, + Inputs: inputs, + Outputs: outputs, + str: str, + Sig: sig, + ID: id, + } +} + +func (method Method) String() string { + return method.str +} + +// IsConstant returns the indicator whether the method is read-only. +func (method Method) IsConstant() bool { + return method.StateMutability == "view" || method.StateMutability == "pure" || method.Constant +} + +// IsPayable returns the indicator whether the method can process +// plain ether transfers. +func (method Method) IsPayable() bool { + return method.StateMutability == "payable" || method.Payable +} diff --git a/abigen/abi/pack.go b/abigen/abi/pack.go new file mode 100644 index 0000000..c9fe511 --- /dev/null +++ b/abigen/abi/pack.go @@ -0,0 +1,85 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "errors" + "fmt" + "math/big" + "reflect" + + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/common/math" +) + +// packBytesSlice packs the given bytes as [L, V] as the canonical representation +// bytes slice. +func packBytesSlice(bytes []byte, l int) []byte { + len := packNum(reflect.ValueOf(l)) + return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...) +} + +// packElement packs the given reflect value according to the abi specification in +// t. +func packElement(t Type, reflectValue reflect.Value) ([]byte, error) { + switch t.T { + case IntTy, UintTy: + return packNum(reflectValue), nil + case StringTy: + return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()), nil + case AddressTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + + return common.LeftPadBytes(reflectValue.Bytes(), 32), nil + case BoolTy: + if reflectValue.Bool() { + return math.PaddedBigBytes(common.Big1, 32), nil + } + return math.PaddedBigBytes(common.Big0, 32), nil + case BytesTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + if reflectValue.Type() != reflect.TypeOf([]byte{}) { + return []byte{}, errors.New("Bytes type is neither slice nor array") + } + return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil + case FixedBytesTy, FunctionTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + return common.RightPadBytes(reflectValue.Bytes(), 32), nil + default: + return []byte{}, fmt.Errorf("Could not pack element, unknown type: %v", t.T) + } +} + +// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation. +func packNum(value reflect.Value) []byte { + switch kind := value.Kind(); kind { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return math.U256Bytes(new(big.Int).SetUint64(value.Uint())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return math.U256Bytes(big.NewInt(value.Int())) + case reflect.Ptr: + return math.U256Bytes(new(big.Int).Set(value.Interface().(*big.Int))) + default: + panic("abi: fatal error") + } +} diff --git a/abigen/abi/reflect.go b/abigen/abi/reflect.go new file mode 100644 index 0000000..35e5556 --- /dev/null +++ b/abigen/abi/reflect.go @@ -0,0 +1,260 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "errors" + "fmt" + "math/big" + "reflect" + "strings" +) + +// ConvertType converts an interface of a runtime type into a interface of the +// given type +// e.g. turn +// var fields []reflect.StructField +// fields = append(fields, reflect.StructField{ +// Name: "X", +// Type: reflect.TypeOf(new(big.Int)), +// Tag: reflect.StructTag("json:\"" + "x" + "\""), +// } +// into +// type TupleT struct { X *big.Int } +func ConvertType(in interface{}, proto interface{}) interface{} { + protoType := reflect.TypeOf(proto) + if reflect.TypeOf(in).ConvertibleTo(protoType) { + return reflect.ValueOf(in).Convert(protoType).Interface() + } + // Use set as a last ditch effort + if err := set(reflect.ValueOf(proto), reflect.ValueOf(in)); err != nil { + panic(err) + } + return proto +} + +// indirect recursively dereferences the value until it either gets the value +// or finds a big.Int +func indirect(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Ptr && v.Elem().Type() != reflect.TypeOf(big.Int{}) { + return indirect(v.Elem()) + } + return v +} + +// reflectIntType returns the reflect using the given size and +// unsignedness. +func reflectIntType(unsigned bool, size int) reflect.Type { + if unsigned { + switch size { + case 8: + return reflect.TypeOf(uint8(0)) + case 16: + return reflect.TypeOf(uint16(0)) + case 32: + return reflect.TypeOf(uint32(0)) + case 64: + return reflect.TypeOf(uint64(0)) + } + } + switch size { + case 8: + return reflect.TypeOf(int8(0)) + case 16: + return reflect.TypeOf(int16(0)) + case 32: + return reflect.TypeOf(int32(0)) + case 64: + return reflect.TypeOf(int64(0)) + } + return reflect.TypeOf(&big.Int{}) +} + +// mustArrayToByteSlice creates a new byte slice with the exact same size as value +// and copies the bytes in value to the new slice. +func mustArrayToByteSlice(value reflect.Value) reflect.Value { + slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len()) + reflect.Copy(slice, value) + return slice +} + +// set attempts to assign src to dst by either setting, copying or otherwise. +// +// set is a bit more lenient when it comes to assignment and doesn't force an as +// strict ruleset as bare `reflect` does. +func set(dst, src reflect.Value) error { + dstType, srcType := dst.Type(), src.Type() + switch { + case dstType.Kind() == reflect.Interface && dst.Elem().IsValid(): + return set(dst.Elem(), src) + case dstType.Kind() == reflect.Ptr && dstType.Elem() != reflect.TypeOf(big.Int{}): + return set(dst.Elem(), src) + case srcType.AssignableTo(dstType) && dst.CanSet(): + dst.Set(src) + case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice && dst.CanSet(): + return setSlice(dst, src) + case dstType.Kind() == reflect.Array: + return setArray(dst, src) + case dstType.Kind() == reflect.Struct: + return setStruct(dst, src) + default: + return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type()) + } + return nil +} + +// setSlice attempts to assign src to dst when slices are not assignable by default +// e.g. src: [][]byte -> dst: [][15]byte +// setSlice ignores if we cannot copy all of src' elements. +func setSlice(dst, src reflect.Value) error { + slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len()) + for i := 0; i < src.Len(); i++ { + if err := set(slice.Index(i), src.Index(i)); err != nil { + return err + } + } + if dst.CanSet() { + dst.Set(slice) + return nil + } + return errors.New("Cannot set slice, destination not settable") +} + +func setArray(dst, src reflect.Value) error { + if src.Kind() == reflect.Ptr { + return set(dst, indirect(src)) + } + array := reflect.New(dst.Type()).Elem() + min := src.Len() + if src.Len() > dst.Len() { + min = dst.Len() + } + for i := 0; i < min; i++ { + if err := set(array.Index(i), src.Index(i)); err != nil { + return err + } + } + if dst.CanSet() { + dst.Set(array) + return nil + } + return errors.New("Cannot set array, destination not settable") +} + +func setStruct(dst, src reflect.Value) error { + for i := 0; i < src.NumField(); i++ { + srcField := src.Field(i) + dstField := dst.Field(i) + if !dstField.IsValid() || !srcField.IsValid() { + return fmt.Errorf("Could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField) + } + if err := set(dstField, srcField); err != nil { + return err + } + } + return nil +} + +// mapArgNamesToStructFields maps a slice of argument names to struct fields. +// first round: for each Exportable field that contains a `abi:""` tag +// and this field name exists in the given argument name list, pair them together. +// second round: for each argument name that has not been already linked, +// find what variable is expected to be mapped into, if it exists and has not been +// used, pair them. +// Note this function assumes the given value is a struct value. +func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[string]string, error) { + typ := value.Type() + + abi2struct := make(map[string]string) + struct2abi := make(map[string]string) + + // first round ~~~ + for i := 0; i < typ.NumField(); i++ { + structFieldName := typ.Field(i).Name + + // skip private struct fields. + if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) { + continue + } + // skip fields that have no abi:"" tag. + tagName, ok := typ.Field(i).Tag.Lookup("abi") + if !ok { + continue + } + // check if tag is empty. + if tagName == "" { + return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName) + } + // check which argument field matches with the abi tag. + found := false + for _, arg := range argNames { + if arg == tagName { + if abi2struct[arg] != "" { + return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName) + } + // pair them + abi2struct[arg] = structFieldName + struct2abi[structFieldName] = arg + found = true + } + } + // check if this tag has been mapped. + if !found { + return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName) + } + } + + // second round ~~~ + for _, argName := range argNames { + + structFieldName := ToCamelCase(argName) + + if structFieldName == "" { + return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct") + } + + // this abi has already been paired, skip it... unless there exists another, yet unassigned + // struct field with the same field name. If so, raise an error: + // abi: [ { "name": "value" } ] + // struct { Value *big.Int , Value1 *big.Int `abi:"value"`} + if abi2struct[argName] != "" { + if abi2struct[argName] != structFieldName && + struct2abi[structFieldName] == "" && + value.FieldByName(structFieldName).IsValid() { + return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", argName) + } + continue + } + + // return an error if this struct field has already been paired. + if struct2abi[structFieldName] != "" { + return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName) + } + + if value.FieldByName(structFieldName).IsValid() { + // pair them + abi2struct[argName] = structFieldName + struct2abi[structFieldName] = argName + } else { + // not paired, but annotate as used, to detect cases like + // abi : [ { "name": "value" }, { "name": "_value" } ] + // struct { Value *big.Int } + struct2abi[structFieldName] = argName + } + } + return abi2struct, nil +} diff --git a/abigen/abi/topics.go b/abigen/abi/topics.go new file mode 100644 index 0000000..19feabb --- /dev/null +++ b/abigen/abi/topics.go @@ -0,0 +1,173 @@ +// Copyright 2018 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 . + +package abi + +import ( + "encoding/binary" + "errors" + "fmt" + "math/big" + "reflect" + + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/crypto" +) + +// MakeTopics converts a filter query argument list into a filter topic set. +func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) { + topics := make([][]common.Hash, len(query)) + for i, filter := range query { + for _, rule := range filter { + var topic common.Hash + + // Try to generate the topic based on simple types + switch rule := rule.(type) { + case common.Hash: + copy(topic[:], rule[:]) + case common.Address: + copy(topic[common.HashLength-common.AddressLength:], rule[:]) + case *big.Int: + blob := rule.Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case bool: + if rule { + topic[common.HashLength-1] = 1 + } + case int8: + copy(topic[:], genIntType(int64(rule), 1)) + case int16: + copy(topic[:], genIntType(int64(rule), 2)) + case int32: + copy(topic[:], genIntType(int64(rule), 4)) + case int64: + copy(topic[:], genIntType(rule, 8)) + case uint8: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case uint16: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case uint32: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case uint64: + blob := new(big.Int).SetUint64(rule).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case string: + hash := crypto.Keccak256Hash([]byte(rule)) + copy(topic[:], hash[:]) + case []byte: + hash := crypto.Keccak256Hash(rule) + copy(topic[:], hash[:]) + + default: + // todo(rjl493456442) according solidity documentation, indexed event + // parameters that are not value types i.e. arrays and structs are not + // stored directly but instead a keccak256-hash of an encoding is stored. + // + // We only convert stringS and bytes to hash, still need to deal with + // array(both fixed-size and dynamic-size) and struct. + + // Attempt to generate the topic from funky types + val := reflect.ValueOf(rule) + switch { + // static byte array + case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8: + reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val) + default: + return nil, fmt.Errorf("unsupported indexed type: %T", rule) + } + } + topics[i] = append(topics[i], topic) + } + } + return topics, nil +} + +func genIntType(rule int64, size uint) []byte { + var topic [common.HashLength]byte + if rule < 0 { + // if a rule is negative, we need to put it into two's complement. + // extended to common.HashLength bytes. + topic = [common.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255} + } + for i := uint(0); i < size; i++ { + topic[common.HashLength-i-1] = byte(rule >> (i * 8)) + } + return topic[:] +} + +// ParseTopics converts the indexed topic fields into actual log field values. +func ParseTopics(out interface{}, fields Arguments, topics []common.Hash) error { + return parseTopicWithSetter(fields, topics, + func(arg Argument, reconstr interface{}) { + field := reflect.ValueOf(out).Elem().FieldByName(ToCamelCase(arg.Name)) + field.Set(reflect.ValueOf(reconstr)) + }) +} + +// ParseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs. +func ParseTopicsIntoMap(out map[string]interface{}, fields Arguments, topics []common.Hash) error { + return parseTopicWithSetter(fields, topics, + func(arg Argument, reconstr interface{}) { + out[arg.Name] = reconstr + }) +} + +// parseTopicWithSetter converts the indexed topic field-value pairs and stores them using the +// provided set function. +// +// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256 +// hashes as the topic value! +func parseTopicWithSetter(fields Arguments, topics []common.Hash, setter func(Argument, interface{})) error { + // Sanity check that the fields and topics match up + if len(fields) != len(topics) { + return errors.New("topic/field count mismatch") + } + // Iterate over all the fields and reconstruct them from topics + for i, arg := range fields { + if !arg.Indexed { + return errors.New("non-indexed field in topic reconstruction") + } + var reconstr interface{} + switch arg.Type.T { + case TupleTy: + return errors.New("tuple type in topic reconstruction") + case StringTy, BytesTy, SliceTy, ArrayTy: + // Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash + // whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash + reconstr = topics[i] + case FunctionTy: + if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 { + return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes()) + } + var tmp [24]byte + copy(tmp[:], topics[i][8:32]) + reconstr = tmp + default: + var err error + reconstr, err = toGoType(0, arg.Type, topics[i].Bytes()) + if err != nil { + return err + } + } + // Use the setter function to store the value + setter(arg, reconstr) + } + + return nil +} diff --git a/abigen/abi/type.go b/abigen/abi/type.go new file mode 100644 index 0000000..e3b99bb --- /dev/null +++ b/abigen/abi/type.go @@ -0,0 +1,423 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "errors" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "github.com/ChainSQL/go-chainsql-api/common" +) + +// Type enumerator +const ( + IntTy byte = iota + UintTy + BoolTy + StringTy + SliceTy + ArrayTy + TupleTy + AddressTy + FixedBytesTy + BytesTy + HashTy + FixedPointTy + FunctionTy +) + +// Type is the reflection of the supported argument type. +type Type struct { + Elem *Type + Size int + T byte // Our own type checking + + stringKind string // holds the unparsed string for deriving signatures + + // Tuple relative fields + TupleRawName string // Raw struct name defined in source code, may be empty. + TupleElems []*Type // Type information of all tuple fields + TupleRawNames []string // Raw field name of all tuple fields + TupleType reflect.Type // Underlying struct of the tuple +} + +var ( + // typeRegex parses the abi sub types + typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?") +) + +// NewType creates a new reflection type of abi type given in t. +func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) { + // check that array brackets are equal if they exist + if strings.Count(t, "[") != strings.Count(t, "]") { + return Type{}, fmt.Errorf("invalid arg type in abi") + } + typ.stringKind = t + + // if there are brackets, get ready to go into slice/array mode and + // recursively create the type + if strings.Count(t, "[") != 0 { + // Note internalType can be empty here. + subInternal := internalType + if i := strings.LastIndex(internalType, "["); i != -1 { + subInternal = subInternal[:i] + } + // recursively embed the type + i := strings.LastIndex(t, "[") + embeddedType, err := NewType(t[:i], subInternal, components) + if err != nil { + return Type{}, err + } + // grab the last cell and create a type from there + sliced := t[i:] + // grab the slice size with regexp + re := regexp.MustCompile("[0-9]+") + intz := re.FindAllString(sliced, -1) + + if len(intz) == 0 { + // is a slice + typ.T = SliceTy + typ.Elem = &embeddedType + typ.stringKind = embeddedType.stringKind + sliced + } else if len(intz) == 1 { + // is an array + typ.T = ArrayTy + typ.Elem = &embeddedType + typ.Size, err = strconv.Atoi(intz[0]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } + typ.stringKind = embeddedType.stringKind + sliced + } else { + return Type{}, fmt.Errorf("invalid formatting of array type") + } + return typ, err + } + // parse the type and size of the abi-type. + matches := typeRegex.FindAllStringSubmatch(t, -1) + if len(matches) == 0 { + return Type{}, fmt.Errorf("invalid type '%v'", t) + } + parsedType := matches[0] + + // varSize is the size of the variable + var varSize int + if len(parsedType[3]) > 0 { + var err error + varSize, err = strconv.Atoi(parsedType[2]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } + } else { + if parsedType[0] == "uint" || parsedType[0] == "int" { + // this should fail because it means that there's something wrong with + // the abi type (the compiler should always format it to the size...always) + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + } + // varType is the parsed abi type + switch varType := parsedType[1]; varType { + case "int": + typ.Size = varSize + typ.T = IntTy + case "uint": + typ.Size = varSize + typ.T = UintTy + case "bool": + typ.T = BoolTy + case "address": + typ.Size = 20 + typ.T = AddressTy + case "string": + typ.T = StringTy + case "bytes": + if varSize == 0 { + typ.T = BytesTy + } else { + typ.T = FixedBytesTy + typ.Size = varSize + } + case "tuple": + var ( + fields []reflect.StructField + elems []*Type + names []string + expression string // canonical parameter expression + used = make(map[string]bool) + ) + expression += "(" + for idx, c := range components { + cType, err := NewType(c.Type, c.InternalType, c.Components) + if err != nil { + return Type{}, err + } + name := ToCamelCase(c.Name) + if name == "" { + return Type{}, errors.New("abi: purely anonymous or underscored field is not supported") + } + fieldName := ResolveNameConflict(name, func(s string) bool { return used[s] }) + if err != nil { + return Type{}, err + } + used[fieldName] = true + if !isValidFieldName(fieldName) { + return Type{}, fmt.Errorf("field %d has invalid name", idx) + } + fields = append(fields, reflect.StructField{ + Name: fieldName, // reflect.StructOf will panic for any exported field. + Type: cType.GetType(), + Tag: reflect.StructTag("json:\"" + c.Name + "\""), + }) + elems = append(elems, &cType) + names = append(names, c.Name) + expression += cType.stringKind + if idx != len(components)-1 { + expression += "," + } + } + expression += ")" + + typ.TupleType = reflect.StructOf(fields) + typ.TupleElems = elems + typ.TupleRawNames = names + typ.T = TupleTy + typ.stringKind = expression + + const structPrefix = "struct " + // After solidity 0.5.10, a new field of abi "internalType" + // is introduced. From that we can obtain the struct name + // user defined in the source code. + if internalType != "" && strings.HasPrefix(internalType, structPrefix) { + // Foo.Bar type definition is not allowed in golang, + // convert the format to FooBar + typ.TupleRawName = strings.ReplaceAll(internalType[len(structPrefix):], ".", "") + } + + case "function": + typ.T = FunctionTy + typ.Size = 24 + default: + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + + return +} + +// GetType returns the reflection type of the ABI type. +func (t Type) GetType() reflect.Type { + switch t.T { + case IntTy: + return reflectIntType(false, t.Size) + case UintTy: + return reflectIntType(true, t.Size) + case BoolTy: + return reflect.TypeOf(false) + case StringTy: + return reflect.TypeOf("") + case SliceTy: + return reflect.SliceOf(t.Elem.GetType()) + case ArrayTy: + return reflect.ArrayOf(t.Size, t.Elem.GetType()) + case TupleTy: + return t.TupleType + case AddressTy: + return reflect.TypeOf(common.Address{}) + case FixedBytesTy: + return reflect.ArrayOf(t.Size, reflect.TypeOf(byte(0))) + case BytesTy: + return reflect.SliceOf(reflect.TypeOf(byte(0))) + case HashTy: + // hashtype currently not used + return reflect.ArrayOf(32, reflect.TypeOf(byte(0))) + case FixedPointTy: + // fixedpoint type currently not used + return reflect.ArrayOf(32, reflect.TypeOf(byte(0))) + case FunctionTy: + return reflect.ArrayOf(24, reflect.TypeOf(byte(0))) + default: + panic("Invalid type") + } +} + +// String implements Stringer. +func (t Type) String() (out string) { + return t.stringKind +} + +func (t Type) pack(v reflect.Value) ([]byte, error) { + // dereference pointer first if it's a pointer + v = indirect(v) + if err := typeCheck(t, v); err != nil { + return nil, err + } + + switch t.T { + case SliceTy, ArrayTy: + var ret []byte + + if t.requiresLengthPrefix() { + // append length + ret = append(ret, packNum(reflect.ValueOf(v.Len()))...) + } + + // calculate offset if any + offset := 0 + offsetReq := isDynamicType(*t.Elem) + if offsetReq { + offset = getTypeSize(*t.Elem) * v.Len() + } + var tail []byte + for i := 0; i < v.Len(); i++ { + val, err := t.Elem.pack(v.Index(i)) + if err != nil { + return nil, err + } + if !offsetReq { + ret = append(ret, val...) + continue + } + ret = append(ret, packNum(reflect.ValueOf(offset))...) + offset += len(val) + tail = append(tail, val...) + } + return append(ret, tail...), nil + case TupleTy: + // (T1,...,Tk) for k >= 0 and any types T1, …, Tk + // enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k)) + // where X = (X(1), ..., X(k)) and head and tail are defined for Ti being a static + // type as + // head(X(i)) = enc(X(i)) and tail(X(i)) = "" (the empty string) + // and as + // head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1)))) + // tail(X(i)) = enc(X(i)) + // otherwise, i.e. if Ti is a dynamic type. + fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, v) + if err != nil { + return nil, err + } + // Calculate prefix occupied size. + offset := 0 + for _, elem := range t.TupleElems { + offset += getTypeSize(*elem) + } + var ret, tail []byte + for i, elem := range t.TupleElems { + field := v.FieldByName(fieldmap[t.TupleRawNames[i]]) + if !field.IsValid() { + return nil, fmt.Errorf("field %s for tuple not found in the given struct", t.TupleRawNames[i]) + } + val, err := elem.pack(field) + if err != nil { + return nil, err + } + if isDynamicType(*elem) { + ret = append(ret, packNum(reflect.ValueOf(offset))...) + tail = append(tail, val...) + offset += len(val) + } else { + ret = append(ret, val...) + } + } + return append(ret, tail...), nil + + default: + return packElement(t, v) + } +} + +// requireLengthPrefix returns whether the type requires any sort of length +// prefixing. +func (t Type) requiresLengthPrefix() bool { + return t.T == StringTy || t.T == BytesTy || t.T == SliceTy +} + +// isDynamicType returns true if the type is dynamic. +// The following types are called “dynamic”: +// * bytes +// * string +// * T[] for any T +// * T[k] for any dynamic T and any k >= 0 +// * (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k +func isDynamicType(t Type) bool { + if t.T == TupleTy { + for _, elem := range t.TupleElems { + if isDynamicType(*elem) { + return true + } + } + return false + } + return t.T == StringTy || t.T == BytesTy || t.T == SliceTy || (t.T == ArrayTy && isDynamicType(*t.Elem)) +} + +// getTypeSize returns the size that this type needs to occupy. +// We distinguish static and dynamic types. Static types are encoded in-place +// and dynamic types are encoded at a separately allocated location after the +// current block. +// So for a static variable, the size returned represents the size that the +// variable actually occupies. +// For a dynamic variable, the returned size is fixed 32 bytes, which is used +// to store the location reference for actual value storage. +func getTypeSize(t Type) int { + if t.T == ArrayTy && !isDynamicType(*t.Elem) { + // Recursively calculate type size if it is a nested array + if t.Elem.T == ArrayTy || t.Elem.T == TupleTy { + return t.Size * getTypeSize(*t.Elem) + } + return t.Size * 32 + } else if t.T == TupleTy && !isDynamicType(t) { + total := 0 + for _, elem := range t.TupleElems { + total += getTypeSize(*elem) + } + return total + } + return 32 +} + +// isLetter reports whether a given 'rune' is classified as a Letter. +// This method is copied from reflect/type.go +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) +} + +// isValidFieldName checks if a string is a valid (struct) field name or not. +// +// According to the language spec, a field name should be an identifier. +// +// identifier = letter { letter | unicode_digit } . +// letter = unicode_letter | "_" . +// This method is copied from reflect/type.go +func isValidFieldName(fieldName string) bool { + for i, c := range fieldName { + if i == 0 && !isLetter(c) { + return false + } + + if !(isLetter(c) || unicode.IsDigit(c)) { + return false + } + } + + return len(fieldName) > 0 +} diff --git a/abigen/abi/unpack.go b/abigen/abi/unpack.go new file mode 100644 index 0000000..9153e25 --- /dev/null +++ b/abigen/abi/unpack.go @@ -0,0 +1,298 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "encoding/binary" + "fmt" + "math/big" + "reflect" + + "github.com/ChainSQL/go-chainsql-api/common" +) + +var ( + // MaxUint256 is the maximum value that can be represented by a uint256. + MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1) + // MaxInt256 is the maximum value that can be represented by a int256. + MaxInt256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 255), common.Big1) +) + +// ReadInteger reads the integer based on its kind and returns the appropriate value. +func ReadInteger(typ Type, b []byte) interface{} { + if typ.T == UintTy { + switch typ.Size { + case 8: + return b[len(b)-1] + case 16: + return binary.BigEndian.Uint16(b[len(b)-2:]) + case 32: + return binary.BigEndian.Uint32(b[len(b)-4:]) + case 64: + return binary.BigEndian.Uint64(b[len(b)-8:]) + default: + // the only case left for unsigned integer is uint256. + return new(big.Int).SetBytes(b) + } + } + switch typ.Size { + case 8: + return int8(b[len(b)-1]) + case 16: + return int16(binary.BigEndian.Uint16(b[len(b)-2:])) + case 32: + return int32(binary.BigEndian.Uint32(b[len(b)-4:])) + case 64: + return int64(binary.BigEndian.Uint64(b[len(b)-8:])) + default: + // the only case left for integer is int256 + // big.SetBytes can't tell if a number is negative or positive in itself. + // On EVM, if the returned number > max int256, it is negative. + // A number is > max int256 if the bit at position 255 is set. + ret := new(big.Int).SetBytes(b) + if ret.Bit(255) == 1 { + ret.Add(MaxUint256, new(big.Int).Neg(ret)) + ret.Add(ret, common.Big1) + ret.Neg(ret) + } + return ret + } +} + +// readBool reads a bool. +func readBool(word []byte) (bool, error) { + for _, b := range word[:31] { + if b != 0 { + return false, errBadBool + } + } + switch word[31] { + case 0: + return false, nil + case 1: + return true, nil + default: + return false, errBadBool + } +} + +// A function type is simply the address with the function selection signature at the end. +// +// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) +func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { + if t.T != FunctionTy { + return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array") + } + if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 { + err = fmt.Errorf("abi: got improperly encoded function type, got %v", word) + } else { + copy(funcTy[:], word[0:24]) + } + return +} + +// ReadFixedBytes uses reflection to create a fixed array to be read from. +func ReadFixedBytes(t Type, word []byte) (interface{}, error) { + if t.T != FixedBytesTy { + return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array") + } + // convert + array := reflect.New(t.GetType()).Elem() + + reflect.Copy(array, reflect.ValueOf(word[0:t.Size])) + return array.Interface(), nil + +} + +// forEachUnpack iteratively unpack elements. +func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { + if size < 0 { + return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size) + } + if start+32*size > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size) + } + + // this value will become our slice or our array, depending on the type + var refSlice reflect.Value + + if t.T == SliceTy { + // declare our slice + refSlice = reflect.MakeSlice(t.GetType(), size, size) + } else if t.T == ArrayTy { + // declare our array + refSlice = reflect.New(t.GetType()).Elem() + } else { + return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage") + } + + // Arrays have packed elements, resulting in longer unpack steps. + // Slices have just 32 bytes per element (pointing to the contents). + elemSize := getTypeSize(*t.Elem) + + for i, j := start, 0; j < size; i, j = i+elemSize, j+1 { + inter, err := toGoType(i, *t.Elem, output) + if err != nil { + return nil, err + } + + // append the item to our reflect slice + refSlice.Index(j).Set(reflect.ValueOf(inter)) + } + + // return the interface + return refSlice.Interface(), nil +} + +func forTupleUnpack(t Type, output []byte) (interface{}, error) { + retval := reflect.New(t.GetType()).Elem() + virtualArgs := 0 + for index, elem := range t.TupleElems { + marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) + if elem.T == ArrayTy && !isDynamicType(*elem) { + // If we have a static array, like [3]uint256, these are coded as + // just like uint256,uint256,uint256. + // This means that we need to add two 'virtual' arguments when + // we count the index from now on. + // + // Array values nested multiple levels deep are also encoded inline: + // [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256 + // + // Calculate the full array size to get the correct offset for the next argument. + // Decrement it by 1, as the normal index increment is still applied. + virtualArgs += getTypeSize(*elem)/32 - 1 + } else if elem.T == TupleTy && !isDynamicType(*elem) { + // If we have a static tuple, like (uint256, bool, uint256), these are + // coded as just like uint256,bool,uint256 + virtualArgs += getTypeSize(*elem)/32 - 1 + } + if err != nil { + return nil, err + } + retval.Field(index).Set(reflect.ValueOf(marshalledValue)) + } + return retval.Interface(), nil +} + +// toGoType parses the output bytes and recursively assigns the value of these bytes +// into a go type with accordance with the ABI spec. +func toGoType(index int, t Type, output []byte) (interface{}, error) { + if index+32 > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) + } + + var ( + returnOutput []byte + begin, length int + err error + ) + + // if we require a length prefix, find the beginning word and size returned. + if t.requiresLengthPrefix() { + begin, length, err = lengthPrefixPointsTo(index, output) + if err != nil { + return nil, err + } + } else { + returnOutput = output[index : index+32] + } + + switch t.T { + case TupleTy: + if isDynamicType(t) { + begin, err := tuplePointsTo(index, output) + if err != nil { + return nil, err + } + return forTupleUnpack(t, output[begin:]) + } + return forTupleUnpack(t, output[index:]) + case SliceTy: + return forEachUnpack(t, output[begin:], 0, length) + case ArrayTy: + if isDynamicType(*t.Elem) { + offset := binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]) + if offset > uint64(len(output)) { + return nil, fmt.Errorf("abi: toGoType offset greater than output length: offset: %d, len(output): %d", offset, len(output)) + } + return forEachUnpack(t, output[offset:], 0, t.Size) + } + return forEachUnpack(t, output[index:], 0, t.Size) + case StringTy: // variable arrays are written at the end of the return bytes + return string(output[begin : begin+length]), nil + case IntTy, UintTy: + return ReadInteger(t, returnOutput), nil + case BoolTy: + return readBool(returnOutput) + case AddressTy: + return common.BytesToAddress(returnOutput), nil + case HashTy: + return common.BytesToHash(returnOutput), nil + case BytesTy: + return output[begin : begin+length], nil + case FixedBytesTy: + return ReadFixedBytes(t, returnOutput) + case FunctionTy: + return readFunctionType(t, returnOutput) + default: + return nil, fmt.Errorf("abi: unknown type %v", t.T) + } +} + +// lengthPrefixPointsTo interprets a 32 byte slice as an offset and then determines which indices to look to decode the type. +func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) { + bigOffsetEnd := new(big.Int).SetBytes(output[index : index+32]) + bigOffsetEnd.Add(bigOffsetEnd, common.Big32) + outputLength := big.NewInt(int64(len(output))) + + if bigOffsetEnd.Cmp(outputLength) > 0 { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", bigOffsetEnd, outputLength) + } + + if bigOffsetEnd.BitLen() > 63 { + return 0, 0, fmt.Errorf("abi offset larger than int64: %v", bigOffsetEnd) + } + + offsetEnd := int(bigOffsetEnd.Uint64()) + lengthBig := new(big.Int).SetBytes(output[offsetEnd-32 : offsetEnd]) + + totalSize := new(big.Int).Add(bigOffsetEnd, lengthBig) + if totalSize.BitLen() > 63 { + return 0, 0, fmt.Errorf("abi: length larger than int64: %v", totalSize) + } + + if totalSize.Cmp(outputLength) > 0 { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %v require %v", outputLength, totalSize) + } + start = int(bigOffsetEnd.Uint64()) + length = int(lengthBig.Uint64()) + return +} + +// tuplePointsTo resolves the location reference for dynamic tuple. +func tuplePointsTo(index int, output []byte) (start int, err error) { + offset := new(big.Int).SetBytes(output[index : index+32]) + outputLen := big.NewInt(int64(len(output))) + + if offset.Cmp(outputLen) > 0 { + return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen) + } + if offset.BitLen() > 63 { + return 0, fmt.Errorf("abi offset larger than int64: %v", offset) + } + return int(offset.Uint64()), nil +} diff --git a/abigen/abi/utils.go b/abigen/abi/utils.go new file mode 100644 index 0000000..e24df5b --- /dev/null +++ b/abigen/abi/utils.go @@ -0,0 +1,41 @@ +// Copyright 2022 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 . + +package abi + +import "fmt" + +// ResolveNameConflict returns the next available name for a given thing. +// This helper can be used for lots of purposes: +// +// - In solidity function overloading is supported, this function can fix +// the name conflicts of overloaded functions. +// - In golang binding generation, the parameter(in function, event, error, +// and struct definition) name will be converted to camelcase style which +// may eventually lead to name conflicts. +// +// Name conflicts are mostly resolved by adding number suffix. +// e.g. if the abi contains Methods send, send1 +// ResolveNameConflict would return send2 for input send. +func ResolveNameConflict(rawName string, used func(string) bool) string { + name := rawName + ok := used(name) + for idx := 0; ok; idx++ { + name = fmt.Sprintf("%s%d", rawName, idx) + ok = used(name) + } + return name +} diff --git a/abigen/compiler/helpers.go b/abigen/compiler/helpers.go new file mode 100644 index 0000000..063fc10 --- /dev/null +++ b/abigen/compiler/helpers.go @@ -0,0 +1,45 @@ +// 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 . + +// Package compiler wraps the Solidity and Vyper compiler executables (solc; vyper). +package compiler + +// Contract contains information about a compiled contract, alongside its code and runtime code. +type Contract struct { + Code string `json:"code"` + RuntimeCode string `json:"runtime-code"` + Info ContractInfo `json:"info"` + Hashes map[string]string `json:"hashes"` +} + +// ContractInfo contains information about a compiled contract, including access +// to the ABI definition, source mapping, user and developer docs, and metadata. +// +// Depending on the source, language version, compiler version, and compiler +// options will provide information about how the contract was compiled. +type ContractInfo struct { + Source string `json:"source"` + Language string `json:"language"` + LanguageVersion string `json:"languageVersion"` + CompilerVersion string `json:"compilerVersion"` + CompilerOptions string `json:"compilerOptions"` + SrcMap interface{} `json:"srcMap"` + SrcMapRuntime string `json:"srcMapRuntime"` + AbiDefinition interface{} `json:"abiDefinition"` + UserDoc interface{} `json:"userDoc"` + DeveloperDoc interface{} `json:"developerDoc"` + Metadata string `json:"metadata"` +} diff --git a/abigen/compiler/solidity.go b/abigen/compiler/solidity.go new file mode 100644 index 0000000..ad8a44a --- /dev/null +++ b/abigen/compiler/solidity.go @@ -0,0 +1,129 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package compiler wraps the ABI compilation outputs. +package compiler + +import ( + "encoding/json" + "fmt" +) + +// --combined-output format +type solcOutput struct { + Contracts map[string]struct { + BinRuntime string `json:"bin-runtime"` + SrcMapRuntime string `json:"srcmap-runtime"` + Bin, SrcMap, Abi, Devdoc, Userdoc, Metadata string + Hashes map[string]string + } + Version string +} + +// solidity v.0.8 changes the way ABI, Devdoc and Userdoc are serialized +type solcOutputV8 struct { + Contracts map[string]struct { + BinRuntime string `json:"bin-runtime"` + SrcMapRuntime string `json:"srcmap-runtime"` + Bin, SrcMap, Metadata string + Abi interface{} + Devdoc interface{} + Userdoc interface{} + Hashes map[string]string + } + Version string +} + +// ParseCombinedJSON takes the direct output of a solc --combined-output run and +// parses it into a map of string contract name to Contract structs. The +// provided source, language and compiler version, and compiler options are all +// passed through into the Contract structs. +// +// The solc output is expected to contain ABI, source mapping, user docs, and dev docs. +// +// Returns an error if the JSON is malformed or missing data, or if the JSON +// embedded within the JSON is malformed. +func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { + var output solcOutput + if err := json.Unmarshal(combinedJSON, &output); err != nil { + // Try to parse the output with the new solidity v.0.8.0 rules + return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions) + } + // Compilation succeeded, assemble and return the contracts. + contracts := make(map[string]*Contract) + for name, info := range output.Contracts { + // Parse the individual compilation results. + var abi interface{} + if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil { + return nil, fmt.Errorf("solc: error reading abi definition (%v)", err) + } + var userdoc, devdoc interface{} + json.Unmarshal([]byte(info.Userdoc), &userdoc) + json.Unmarshal([]byte(info.Devdoc), &devdoc) + + contracts[name] = &Contract{ + Code: "0x" + info.Bin, + RuntimeCode: "0x" + info.BinRuntime, + Hashes: info.Hashes, + Info: ContractInfo{ + Source: source, + Language: "Solidity", + LanguageVersion: languageVersion, + CompilerVersion: compilerVersion, + CompilerOptions: compilerOptions, + SrcMap: info.SrcMap, + SrcMapRuntime: info.SrcMapRuntime, + AbiDefinition: abi, + UserDoc: userdoc, + DeveloperDoc: devdoc, + Metadata: info.Metadata, + }, + } + } + return contracts, nil +} + +// parseCombinedJSONV8 parses the direct output of solc --combined-output +// and parses it using the rules from solidity v.0.8.0 and later. +func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { + var output solcOutputV8 + if err := json.Unmarshal(combinedJSON, &output); err != nil { + return nil, err + } + // Compilation succeeded, assemble and return the contracts. + contracts := make(map[string]*Contract) + for name, info := range output.Contracts { + contracts[name] = &Contract{ + Code: "0x" + info.Bin, + RuntimeCode: "0x" + info.BinRuntime, + Hashes: info.Hashes, + Info: ContractInfo{ + Source: source, + Language: "Solidity", + LanguageVersion: languageVersion, + CompilerVersion: compilerVersion, + CompilerOptions: compilerOptions, + SrcMap: info.SrcMap, + SrcMapRuntime: info.SrcMapRuntime, + AbiDefinition: info.Abi, + UserDoc: info.Userdoc, + DeveloperDoc: info.Devdoc, + Metadata: info.Metadata, + }, + } + } + return contracts, nil +} diff --git a/abigen/flags/helpers.go b/abigen/flags/helpers.go new file mode 100644 index 0000000..5a0326e --- /dev/null +++ b/abigen/flags/helpers.go @@ -0,0 +1,176 @@ +// Copyright 2020 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 . + +package flags + +import ( + "os" + "path/filepath" + + "gopkg.in/urfave/cli.v1" +) + +const ( + Version = "1.10.20" + VersionMeta = "stable" +) + +var ( + CommandHelpTemplate = `{{.cmd.Name}}{{if .cmd.Subcommands}} command{{end}}{{if .cmd.Flags}} [command options]{{end}} {{.cmd.ArgsUsage}} +{{if .cmd.Description}}{{.cmd.Description}} +{{end}}{{if .cmd.Subcommands}} +SUBCOMMANDS: + {{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .categorizedFlags}} +{{range $idx, $categorized := .categorizedFlags}}{{$categorized.Name}} OPTIONS: +{{range $categorized.Flags}}{{"\t"}}{{.}} +{{end}} +{{end}}{{end}}` + + OriginCommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} {{.ArgsUsage}} +{{if .Description}}{{.Description}} +{{end}}{{if .Subcommands}} +SUBCOMMANDS: + {{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .Flags}} +OPTIONS: +{{range $.Flags}} {{.}} +{{end}} +{{end}}` + + // AppHelpTemplate is the test template for the default, global app help topic. + AppHelpTemplate = `NAME: + {{.App.Name}} - {{.App.Usage}} + + Copyright 2013-2022 The go-ethereum Authors + +USAGE: + {{.App.HelpName}} [options]{{if .App.Commands}} [command] [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if .App.Version}} +VERSION: + {{.App.Version}} + {{end}}{{if len .App.Authors}} +AUTHOR(S): + {{range .App.Authors}}{{ . }}{{end}} + {{end}}{{if .App.Commands}} +COMMANDS: + {{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .FlagGroups}} +{{range .FlagGroups}}{{.Name}} OPTIONS: + {{range .Flags}}{{.}} + {{end}} +{{end}}{{end}}{{if .App.Copyright }} +COPYRIGHT: + {{.App.Copyright}} + {{end}} +` + // ClefAppHelpTemplate is the template for the default, global app help topic. + ClefAppHelpTemplate = `NAME: + {{.App.Name}} - {{.App.Usage}} + + Copyright 2013-2022 The go-ethereum Authors + +USAGE: + {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if .App.Version}} +COMMANDS: + {{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .FlagGroups}} +{{range .FlagGroups}}{{.Name}} OPTIONS: + {{range .Flags}}{{.}} + {{end}} +{{end}}{{end}}{{if .App.Copyright }} +COPYRIGHT: + {{.App.Copyright}} + {{end}} +` +) + +// HelpData is a one shot struct to pass to the usage template +type HelpData struct { + App interface{} + FlagGroups []FlagGroup +} + +// FlagGroup is a collection of flags belonging to a single topic. +type FlagGroup struct { + Name string + Flags []cli.Flag +} + +// ByCategory sorts an array of FlagGroup by Name in the order +// defined in AppHelpFlagGroups. +type ByCategory []FlagGroup + +func (a ByCategory) Len() int { return len(a) } +func (a ByCategory) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByCategory) Less(i, j int) bool { + iCat, jCat := a[i].Name, a[j].Name + iIdx, jIdx := len(a), len(a) // ensure non categorized flags come last + + for i, group := range a { + if iCat == group.Name { + iIdx = i + } + if jCat == group.Name { + jIdx = i + } + } + + return iIdx < jIdx +} + +func FlagCategory(flag cli.Flag, flagGroups []FlagGroup) string { + for _, category := range flagGroups { + for _, flg := range category.Flags { + if flg.GetName() == flag.GetName() { + return category.Name + } + } + } + return "MISC" +} + +// NewApp creates an app with sane defaults. +func NewApp(gitCommit, gitDate, usage string) *cli.App { + app := cli.NewApp() + app.EnableBashCompletion = true + app.Name = filepath.Base(os.Args[0]) + app.Author = "" + app.Email = "" + app.Version = VersionWithCommit(gitCommit, gitDate) + app.Usage = usage + return app +} + +var VersionWithMeta = func() string { + v := Version + if VersionMeta != "" { + v += "-" + VersionMeta + } + return v +}() + +func VersionWithCommit(gitCommit, gitDate string) string { + vsn := VersionWithMeta + if len(gitCommit) >= 8 { + vsn += "-" + gitCommit[:8] + } + if (VersionMeta != "stable") && (gitDate != "") { + vsn += "-" + gitDate + } + return vsn +} diff --git a/abigen/main.go b/abigen/main.go new file mode 100644 index 0000000..4de336f --- /dev/null +++ b/abigen/main.go @@ -0,0 +1,244 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "os" + "regexp" + "strings" + + "github.com/ChainSQL/go-chainsql-api/abigen/abi/bind" + "github.com/ChainSQL/go-chainsql-api/abigen/compiler" + "github.com/ChainSQL/go-chainsql-api/abigen/flags" + "github.com/ChainSQL/go-chainsql-api/abigen/utils" + "github.com/ChainSQL/go-chainsql-api/crypto" + "gopkg.in/urfave/cli.v1" +) + +var ( + // Git SHA1 commit hash of the release (set via linker flags) + gitCommit = "" + gitDate = "" + + app *cli.App + + // Flags needed by abigen + abiFlag = cli.StringFlag{ + Name: "abi", + Usage: "Path to the Ethereum contract ABI json to bind, - for STDIN", + } + binFlag = cli.StringFlag{ + Name: "bin", + Usage: "Path to the Ethereum contract bytecode (generate deploy method)", + } + typeFlag = cli.StringFlag{ + Name: "type", + Usage: "Struct name for the binding (default = package name)", + } + jsonFlag = cli.StringFlag{ + Name: "combined-json", + Usage: "Path to the combined-json file generated by compiler, - for STDIN", + } + excFlag = cli.StringFlag{ + Name: "exc", + Usage: "Comma separated types to exclude from binding", + } + pkgFlag = cli.StringFlag{ + Name: "pkg", + Usage: "Package name to generate the binding into", + } + outFlag = cli.StringFlag{ + Name: "out", + Usage: "Output file for the generated binding (default = stdout)", + } + langFlag = cli.StringFlag{ + Name: "lang", + Usage: "Destination language for the bindings (go, java, objc)", + Value: "go", + } + aliasFlag = cli.StringFlag{ + Name: "alias", + Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2", + } +) + +func init() { + app = flags.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") + app.Flags = []cli.Flag{ + abiFlag, + binFlag, + typeFlag, + jsonFlag, + excFlag, + pkgFlag, + outFlag, + langFlag, + aliasFlag, + } + app.Action = utils.MigrateFlags(abigen) + cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate +} + +func abigen(c *cli.Context) error { + utils.CheckExclusive(c, abiFlag, jsonFlag) // Only one source can be selected. + if c.GlobalString(pkgFlag.Name) == "" { + log.Printf("No destination package specified (--pkg)") + } + var lang bind.Lang + switch c.GlobalString(langFlag.Name) { + case "go": + lang = bind.LangGo + case "java": + lang = bind.LangJava + case "objc": + lang = bind.LangObjC + log.Printf("Objc binding generation is uncompleted") + default: + log.Printf("Unsupported destination language \"%s\" (--lang)", c.GlobalString(langFlag.Name)) + } + // If the entire solidity code was specified, build and bind based on that + var ( + abis []string + bins []string + types []string + sigs []map[string]string + libs = make(map[string]string) + aliases = make(map[string]string) + ) + if c.GlobalString(abiFlag.Name) != "" { + // Load up the ABI, optional bytecode and type name from the parameters + var ( + abi []byte + err error + ) + input := c.GlobalString(abiFlag.Name) + if input == "-" { + abi, err = io.ReadAll(os.Stdin) + } else { + abi, err = os.ReadFile(input) + } + if err != nil { + log.Printf("Failed to read input ABI: %v", err) + } + abis = append(abis, string(abi)) + + var bin []byte + if binFile := c.GlobalString(binFlag.Name); binFile != "" { + if bin, err = os.ReadFile(binFile); err != nil { + log.Printf("Failed to read input bytecode: %v", err) + } + if strings.Contains(string(bin), "//") { + log.Printf("Contract has additional library references, please use other mode(e.g. --combined-json) to catch library infos") + } + } + bins = append(bins, string(bin)) + + kind := c.GlobalString(typeFlag.Name) + if kind == "" { + kind = c.GlobalString(pkgFlag.Name) + } + types = append(types, kind) + } else { + // Generate the list of types to exclude from binding + exclude := make(map[string]bool) + for _, kind := range strings.Split(c.GlobalString(excFlag.Name), ",") { + exclude[strings.ToLower(kind)] = true + } + var contracts map[string]*compiler.Contract + + if c.GlobalIsSet(jsonFlag.Name) { + var ( + input = c.GlobalString(jsonFlag.Name) + jsonOutput []byte + err error + ) + if input == "-" { + jsonOutput, err = io.ReadAll(os.Stdin) + } else { + jsonOutput, err = os.ReadFile(input) + } + if err != nil { + log.Printf("Failed to read combined-json: %v", err) + } + contracts, err = compiler.ParseCombinedJSON(jsonOutput, "", "", "", "") + if err != nil { + log.Printf("Failed to read contract information from json output: %v", err) + } + } + // Gather all non-excluded contract for binding + for name, contract := range contracts { + if exclude[strings.ToLower(name)] { + continue + } + abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse + if err != nil { + log.Printf("Failed to parse ABIs from compiler output: %v", err) + } + abis = append(abis, string(abi)) + bins = append(bins, contract.Code) + sigs = append(sigs, contract.Hashes) + nameParts := strings.Split(name, ":") + types = append(types, nameParts[len(nameParts)-1]) + + // Derive the library placeholder which is a 34 character prefix of the + // hex encoding of the keccak256 hash of the fully qualified library name. + // Note that the fully qualified library name is the path of its source + // file and the library name separated by ":". + libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] // the first 2 chars are 0x + libs[libPattern] = nameParts[len(nameParts)-1] + } + } + // Extract all aliases from the flags + if c.GlobalIsSet(aliasFlag.Name) { + // We support multi-versions for aliasing + // e.g. + // foo=bar,foo2=bar2 + // foo:bar,foo2:bar2 + re := regexp.MustCompile(`(?:(\w+)[:=](\w+))`) + submatches := re.FindAllStringSubmatch(c.GlobalString(aliasFlag.Name), -1) + for _, match := range submatches { + aliases[match[1]] = match[2] + } + } + // Generate the contract binding + code, err := bind.Bind(types, abis, bins, sigs, c.GlobalString(pkgFlag.Name), lang, libs, aliases) + if err != nil { + log.Printf("Failed to generate ABI binding: %v", err) + } + // Either flush it out to a file or display on the standard output + if !c.GlobalIsSet(outFlag.Name) { + fmt.Printf("%s\n", code) + return nil + } + if err := os.WriteFile(c.GlobalString(outFlag.Name), []byte(code), 0600); err != nil { + log.Printf("Failed to write ABI binding: %v", err) + } + return nil +} + +func main() { + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/abigen/utils/util.go b/abigen/utils/util.go new file mode 100644 index 0000000..b212494 --- /dev/null +++ b/abigen/utils/util.go @@ -0,0 +1,61 @@ +package utils + +import ( + "fmt" + "log" + "strings" + + "gopkg.in/urfave/cli.v1" +) + +// CheckExclusive verifies that only a single instance of the provided flags was +// set by the user. Each flag might optionally be followed by a string type to +// specialize it further. +func CheckExclusive(ctx *cli.Context, args ...interface{}) { + set := make([]string, 0, 1) + for i := 0; i < len(args); i++ { + // Make sure the next argument is a flag and skip if not set + flag, ok := args[i].(cli.Flag) + if !ok { + panic(fmt.Sprintf("invalid argument, not cli.Flag type: %T", args[i])) + } + // Check if next arg extends current and expand its name if so + name := flag.GetName() + + if i+1 < len(args) { + switch option := args[i+1].(type) { + case string: + // Extended flag check, make sure value set doesn't conflict with passed in option + if ctx.GlobalString(flag.GetName()) == option { + name += "=" + option + set = append(set, "--"+name) + } + // shift arguments and continue + i++ + continue + + case cli.Flag: + default: + panic(fmt.Sprintf("invalid argument, not cli.Flag or string extension: %T", args[i+1])) + } + } + // Mark the flag if it's set + if ctx.GlobalIsSet(flag.GetName()) { + set = append(set, "--"+name) + } + } + if len(set) > 1 { + log.Printf("Flags %v can't be used at the same time", strings.Join(set, ", ")) + } +} + +func MigrateFlags(action func(ctx *cli.Context) error) func(*cli.Context) error { + return func(ctx *cli.Context) error { + for _, name := range ctx.FlagNames() { + if ctx.IsSet(name) { + ctx.GlobalSet(name, ctx.String(name)) + } + } + return action(ctx) + } +} diff --git a/common/big.go b/common/big.go new file mode 100644 index 0000000..65d4377 --- /dev/null +++ b/common/big.go @@ -0,0 +1,30 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package common + +import "math/big" + +// Common big integers often used +var ( + Big1 = big.NewInt(1) + Big2 = big.NewInt(2) + Big3 = big.NewInt(3) + Big0 = big.NewInt(0) + Big32 = big.NewInt(32) + Big256 = big.NewInt(256) + Big257 = big.NewInt(257) +) diff --git a/common/bytes.go b/common/bytes.go new file mode 100644 index 0000000..288d9ea --- /dev/null +++ b/common/bytes.go @@ -0,0 +1,151 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package common contains various helper functions. +package common + +import ( + "encoding/hex" + "errors" + + "github.com/ChainSQL/go-chainsql-api/common/hexutil" +) + +// FromHex returns the bytes represented by the hexadecimal string s. +// s may be prefixed with "0x". +func FromHex(s string) []byte { + if has0xPrefix(s) { + s = s[2:] + } + if len(s)%2 == 1 { + s = "0" + s + } + return Hex2Bytes(s) +} + +// CopyBytes returns an exact copy of the provided bytes. +func CopyBytes(b []byte) (copiedBytes []byte) { + if b == nil { + return nil + } + copiedBytes = make([]byte, len(b)) + copy(copiedBytes, b) + + return +} + +// has0xPrefix validates str begins with '0x' or '0X'. +func has0xPrefix(str string) bool { + return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') +} + +// isHexCharacter returns bool of c being a valid hexadecimal. +func isHexCharacter(c byte) bool { + return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F') +} + +// isHex validates whether each byte is valid hexadecimal string. +func isHex(str string) bool { + if len(str)%2 != 0 { + return false + } + for _, c := range []byte(str) { + if !isHexCharacter(c) { + return false + } + } + return true +} + +// Bytes2Hex returns the hexadecimal encoding of d. +func Bytes2Hex(d []byte) string { + return hex.EncodeToString(d) +} + +// Hex2Bytes returns the bytes represented by the hexadecimal string str. +func Hex2Bytes(str string) []byte { + h, _ := hex.DecodeString(str) + return h +} + +// Hex2BytesFixed returns bytes of a specified fixed length flen. +func Hex2BytesFixed(str string, flen int) []byte { + h, _ := hex.DecodeString(str) + if len(h) == flen { + return h + } + if len(h) > flen { + return h[len(h)-flen:] + } + hh := make([]byte, flen) + copy(hh[flen-len(h):flen], h) + return hh +} + +// ParseHexOrString tries to hexdecode b, but if the prefix is missing, it instead just returns the raw bytes +func ParseHexOrString(str string) ([]byte, error) { + b, err := hexutil.Decode(str) + if errors.Is(err, hexutil.ErrMissingPrefix) { + return []byte(str), nil + } + return b, err +} + +// RightPadBytes zero-pads slice to the right up to length l. +func RightPadBytes(slice []byte, l int) []byte { + if l <= len(slice) { + return slice + } + + padded := make([]byte, l) + copy(padded, slice) + + return padded +} + +// LeftPadBytes zero-pads slice to the left up to length l. +func LeftPadBytes(slice []byte, l int) []byte { + if l <= len(slice) { + return slice + } + + padded := make([]byte, l) + copy(padded[l-len(slice):], slice) + + return padded +} + +// TrimLeftZeroes returns a subslice of s without leading zeroes +func TrimLeftZeroes(s []byte) []byte { + idx := 0 + for ; idx < len(s); idx++ { + if s[idx] != 0 { + break + } + } + return s[idx:] +} + +// TrimRightZeroes returns a subslice of s without trailing zeroes +func TrimRightZeroes(s []byte) []byte { + idx := len(s) + for ; idx > 0; idx-- { + if s[idx-1] != 0 { + break + } + } + return s[:idx] +} diff --git a/common/common.go b/common/common.go index dacae18..2111e47 100644 --- a/common/common.go +++ b/common/common.go @@ -1,5 +1,29 @@ package common +type KeyType int + +const ( + ECDSA KeyType = 0 + Ed25519 KeyType = 1 + SoftGMAlg KeyType = 2 +) + +/*func (keyType KeyType) String() string { + switch keyType { + case secp256k1: + return "secp256k1" + case Ed25519: + return "Ed25519" + default: + return "unknown key type" + } +}*/ + +/*func (keyType KeyType) MarshalText() ([]byte, error) { + + return []byte(keyType.String()), nil +}*/ + // Auth is the type with ws connection infomations type Auth struct { Address string @@ -7,18 +31,38 @@ type Auth struct { Owner string } +// TxResult is tx submit response +type TxResult struct { + Status string `json:"status"` + TxHash string `json:"hash"` + ErrorCode string `json:"error,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` +} + //IRequest define interface for request type IRequest interface { GetID() int64 + SetID(inputID int64) + SetSchemaID(schemaID string) *RequestBase } //RequestBase contains fields that all requests will have type RequestBase struct { - Command string `json:"command"` - ID int64 `json:"id,omitempty"` + Command string `json:"command"` + ID int64 `json:"id,omitempty"` + SchemaID string `json:"schema_id"` } // GetID return id for request func (r *RequestBase) GetID() int64 { return r.ID } + +func (r *RequestBase) SetID(inputId int64) { + r.ID = inputId +} + +func (r *RequestBase) SetSchemaID(schemaID string) *RequestBase { + r.SchemaID = schemaID + return r +} diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go new file mode 100644 index 0000000..e0241f5 --- /dev/null +++ b/common/hexutil/hexutil.go @@ -0,0 +1,241 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +/* +Package hexutil implements hex encoding with 0x prefix. +This encoding is used by the Ethereum RPC API to transport binary data in JSON payloads. + +Encoding Rules + +All hex data must have prefix "0x". + +For byte slices, the hex data must be of even length. An empty byte slice +encodes as "0x". + +Integers are encoded using the least amount of digits (no leading zero digits). Their +encoding may be of uneven length. The number zero encodes as "0x0". +*/ +package hexutil + +import ( + "encoding/hex" + "fmt" + "math/big" + "strconv" +) + +const uintBits = 32 << (uint64(^uint(0)) >> 63) + +// Errors +var ( + ErrEmptyString = &decError{"empty hex string"} + ErrSyntax = &decError{"invalid hex string"} + ErrMissingPrefix = &decError{"hex string without 0x prefix"} + ErrOddLength = &decError{"hex string of odd length"} + ErrEmptyNumber = &decError{"hex string \"0x\""} + ErrLeadingZero = &decError{"hex number with leading zero digits"} + ErrUint64Range = &decError{"hex number > 64 bits"} + ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)} + ErrBig256Range = &decError{"hex number > 256 bits"} +) + +type decError struct{ msg string } + +func (err decError) Error() string { return err.msg } + +// Decode decodes a hex string with 0x prefix. +func Decode(input string) ([]byte, error) { + if len(input) == 0 { + return nil, ErrEmptyString + } + if !has0xPrefix(input) { + return nil, ErrMissingPrefix + } + b, err := hex.DecodeString(input[2:]) + if err != nil { + err = mapError(err) + } + return b, err +} + +// MustDecode decodes a hex string with 0x prefix. It panics for invalid input. +func MustDecode(input string) []byte { + dec, err := Decode(input) + if err != nil { + panic(err) + } + return dec +} + +// Encode encodes b as a hex string with 0x prefix. +func Encode(b []byte) string { + enc := make([]byte, len(b)*2+2) + copy(enc, "0x") + hex.Encode(enc[2:], b) + return string(enc) +} + +// DecodeUint64 decodes a hex string with 0x prefix as a quantity. +func DecodeUint64(input string) (uint64, error) { + raw, err := checkNumber(input) + if err != nil { + return 0, err + } + dec, err := strconv.ParseUint(raw, 16, 64) + if err != nil { + err = mapError(err) + } + return dec, err +} + +// MustDecodeUint64 decodes a hex string with 0x prefix as a quantity. +// It panics for invalid input. +func MustDecodeUint64(input string) uint64 { + dec, err := DecodeUint64(input) + if err != nil { + panic(err) + } + return dec +} + +// EncodeUint64 encodes i as a hex string with 0x prefix. +func EncodeUint64(i uint64) string { + enc := make([]byte, 2, 10) + copy(enc, "0x") + return string(strconv.AppendUint(enc, i, 16)) +} + +var bigWordNibbles int + +func init() { + // This is a weird way to compute the number of nibbles required for big.Word. + // The usual way would be to use constant arithmetic but go vet can't handle that. + b, _ := new(big.Int).SetString("FFFFFFFFFF", 16) + switch len(b.Bits()) { + case 1: + bigWordNibbles = 16 + case 2: + bigWordNibbles = 8 + default: + panic("weird big.Word size") + } +} + +// DecodeBig decodes a hex string with 0x prefix as a quantity. +// Numbers larger than 256 bits are not accepted. +func DecodeBig(input string) (*big.Int, error) { + raw, err := checkNumber(input) + if err != nil { + return nil, err + } + if len(raw) > 64 { + return nil, ErrBig256Range + } + words := make([]big.Word, len(raw)/bigWordNibbles+1) + end := len(raw) + for i := range words { + start := end - bigWordNibbles + if start < 0 { + start = 0 + } + for ri := start; ri < end; ri++ { + nib := decodeNibble(raw[ri]) + if nib == badNibble { + return nil, ErrSyntax + } + words[i] *= 16 + words[i] += big.Word(nib) + } + end = start + } + dec := new(big.Int).SetBits(words) + return dec, nil +} + +// MustDecodeBig decodes a hex string with 0x prefix as a quantity. +// It panics for invalid input. +func MustDecodeBig(input string) *big.Int { + dec, err := DecodeBig(input) + if err != nil { + panic(err) + } + return dec +} + +// EncodeBig encodes bigint as a hex string with 0x prefix. +func EncodeBig(bigint *big.Int) string { + if sign := bigint.Sign(); sign == 0 { + return "0x0" + } else if sign > 0 { + return "0x" + bigint.Text(16) + } else { + return "-0x" + bigint.Text(16)[1:] + } +} + +func has0xPrefix(input string) bool { + return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') +} + +func checkNumber(input string) (raw string, err error) { + if len(input) == 0 { + return "", ErrEmptyString + } + if !has0xPrefix(input) { + return "", ErrMissingPrefix + } + input = input[2:] + if len(input) == 0 { + return "", ErrEmptyNumber + } + if len(input) > 1 && input[0] == '0' { + return "", ErrLeadingZero + } + return input, nil +} + +const badNibble = ^uint64(0) + +func decodeNibble(in byte) uint64 { + switch { + case in >= '0' && in <= '9': + return uint64(in - '0') + case in >= 'A' && in <= 'F': + return uint64(in - 'A' + 10) + case in >= 'a' && in <= 'f': + return uint64(in - 'a' + 10) + default: + return badNibble + } +} + +func mapError(err error) error { + if err, ok := err.(*strconv.NumError); ok { + switch err.Err { + case strconv.ErrRange: + return ErrUint64Range + case strconv.ErrSyntax: + return ErrSyntax + } + } + if _, ok := err.(hex.InvalidByteError); ok { + return ErrSyntax + } + if err == hex.ErrLength { + return ErrOddLength + } + return err +} diff --git a/common/hexutil/json.go b/common/hexutil/json.go new file mode 100644 index 0000000..50db208 --- /dev/null +++ b/common/hexutil/json.go @@ -0,0 +1,376 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package hexutil + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "reflect" + "strconv" +) + +var ( + bytesT = reflect.TypeOf(Bytes(nil)) + bigT = reflect.TypeOf((*Big)(nil)) + uintT = reflect.TypeOf(Uint(0)) + uint64T = reflect.TypeOf(Uint64(0)) +) + +// Bytes marshals/unmarshals as a JSON string with 0x prefix. +// The empty slice marshals as "0x". +type Bytes []byte + +// MarshalText implements encoding.TextMarshaler +func (b Bytes) MarshalText() ([]byte, error) { + result := make([]byte, len(b)*2+2) + copy(result, `0x`) + hex.Encode(result[2:], b) + return result, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Bytes) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(bytesT) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bytesT) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (b *Bytes) UnmarshalText(input []byte) error { + raw, err := checkText(input, true) + if err != nil { + return err + } + dec := make([]byte, len(raw)/2) + if _, err = hex.Decode(dec, raw); err != nil { + err = mapError(err) + } else { + *b = dec + } + return err +} + +// String returns the hex encoding of b. +func (b Bytes) String() string { + return Encode(b) +} + +// ImplementsGraphQLType returns true if Bytes implements the specified GraphQL type. +func (b Bytes) ImplementsGraphQLType(name string) bool { return name == "Bytes" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Bytes) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + data, err := Decode(input) + if err != nil { + return err + } + *b = data + default: + err = fmt.Errorf("unexpected type %T for Bytes", input) + } + return err +} + +// UnmarshalFixedJSON decodes the input as a string with 0x prefix. The length of out +// determines the required input length. This function is commonly used to implement the +// UnmarshalJSON method for fixed-size types. +func UnmarshalFixedJSON(typ reflect.Type, input, out []byte) error { + if !isString(input) { + return errNonString(typ) + } + return wrapTypeError(UnmarshalFixedText(typ.String(), input[1:len(input)-1], out), typ) +} + +// UnmarshalFixedText decodes the input as a string with 0x prefix. The length of out +// determines the required input length. This function is commonly used to implement the +// UnmarshalText method for fixed-size types. +func UnmarshalFixedText(typname string, input, out []byte) error { + raw, err := checkText(input, true) + if err != nil { + return err + } + if len(raw)/2 != len(out) { + return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname) + } + // Pre-verify syntax before modifying out. + for _, b := range raw { + if decodeNibble(b) == badNibble { + return ErrSyntax + } + } + hex.Decode(out, raw) + return nil +} + +// UnmarshalFixedUnprefixedText decodes the input as a string with optional 0x prefix. The +// length of out determines the required input length. This function is commonly used to +// implement the UnmarshalText method for fixed-size types. +func UnmarshalFixedUnprefixedText(typname string, input, out []byte) error { + raw, err := checkText(input, false) + if err != nil { + return err + } + if len(raw)/2 != len(out) { + return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname) + } + // Pre-verify syntax before modifying out. + for _, b := range raw { + if decodeNibble(b) == badNibble { + return ErrSyntax + } + } + hex.Decode(out, raw) + return nil +} + +// Big marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +// +// Negative integers are not supported at this time. Attempting to marshal them will +// return an error. Values larger than 256bits are rejected by Unmarshal but will be +// marshaled without error. +type Big big.Int + +// MarshalText implements encoding.TextMarshaler +func (b Big) MarshalText() ([]byte, error) { + return []byte(EncodeBig((*big.Int)(&b))), nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Big) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(bigT) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bigT) +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (b *Big) UnmarshalText(input []byte) error { + raw, err := checkNumberText(input) + if err != nil { + return err + } + if len(raw) > 64 { + return ErrBig256Range + } + words := make([]big.Word, len(raw)/bigWordNibbles+1) + end := len(raw) + for i := range words { + start := end - bigWordNibbles + if start < 0 { + start = 0 + } + for ri := start; ri < end; ri++ { + nib := decodeNibble(raw[ri]) + if nib == badNibble { + return ErrSyntax + } + words[i] *= 16 + words[i] += big.Word(nib) + } + end = start + } + var dec big.Int + dec.SetBits(words) + *b = (Big)(dec) + return nil +} + +// ToInt converts b to a big.Int. +func (b *Big) ToInt() *big.Int { + return (*big.Int)(b) +} + +// String returns the hex encoding of b. +func (b *Big) String() string { + return EncodeBig(b.ToInt()) +} + +// ImplementsGraphQLType returns true if Big implements the provided GraphQL type. +func (b Big) ImplementsGraphQLType(name string) bool { return name == "BigInt" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Big) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + return b.UnmarshalText([]byte(input)) + case int32: + var num big.Int + num.SetInt64(int64(input)) + *b = Big(num) + default: + err = fmt.Errorf("unexpected type %T for BigInt", input) + } + return err +} + +// Uint64 marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +type Uint64 uint64 + +// MarshalText implements encoding.TextMarshaler. +func (b Uint64) MarshalText() ([]byte, error) { + buf := make([]byte, 2, 10) + copy(buf, `0x`) + buf = strconv.AppendUint(buf, uint64(b), 16) + return buf, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Uint64) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(uint64T) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uint64T) +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (b *Uint64) UnmarshalText(input []byte) error { + raw, err := checkNumberText(input) + if err != nil { + return err + } + if len(raw) > 16 { + return ErrUint64Range + } + var dec uint64 + for _, byte := range raw { + nib := decodeNibble(byte) + if nib == badNibble { + return ErrSyntax + } + dec *= 16 + dec += nib + } + *b = Uint64(dec) + return nil +} + +// String returns the hex encoding of b. +func (b Uint64) String() string { + return EncodeUint64(uint64(b)) +} + +// ImplementsGraphQLType returns true if Uint64 implements the provided GraphQL type. +func (b Uint64) ImplementsGraphQLType(name string) bool { return name == "Long" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Uint64) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + return b.UnmarshalText([]byte(input)) + case int32: + *b = Uint64(input) + default: + err = fmt.Errorf("unexpected type %T for Long", input) + } + return err +} + +// Uint marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +type Uint uint + +// MarshalText implements encoding.TextMarshaler. +func (b Uint) MarshalText() ([]byte, error) { + return Uint64(b).MarshalText() +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Uint) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(uintT) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uintT) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (b *Uint) UnmarshalText(input []byte) error { + var u64 Uint64 + err := u64.UnmarshalText(input) + if u64 > Uint64(^uint(0)) || err == ErrUint64Range { + return ErrUintRange + } else if err != nil { + return err + } + *b = Uint(u64) + return nil +} + +// String returns the hex encoding of b. +func (b Uint) String() string { + return EncodeUint64(uint64(b)) +} + +func isString(input []byte) bool { + return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' +} + +func bytesHave0xPrefix(input []byte) bool { + return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') +} + +func checkText(input []byte, wantPrefix bool) ([]byte, error) { + if len(input) == 0 { + return nil, nil // empty strings are allowed + } + if bytesHave0xPrefix(input) { + input = input[2:] + } else if wantPrefix { + return nil, ErrMissingPrefix + } + if len(input)%2 != 0 { + return nil, ErrOddLength + } + return input, nil +} + +func checkNumberText(input []byte) (raw []byte, err error) { + if len(input) == 0 { + return nil, nil // empty strings are allowed + } + if !bytesHave0xPrefix(input) { + return nil, ErrMissingPrefix + } + input = input[2:] + if len(input) == 0 { + return nil, ErrEmptyNumber + } + if len(input) > 1 && input[0] == '0' { + return nil, ErrLeadingZero + } + return input, nil +} + +func wrapTypeError(err error, typ reflect.Type) error { + if _, ok := err.(*decError); ok { + return &json.UnmarshalTypeError{Value: err.Error(), Type: typ} + } + return err +} + +func errNonString(typ reflect.Type) error { + return &json.UnmarshalTypeError{Value: "non-string", Type: typ} +} diff --git a/common/math/big.go b/common/math/big.go new file mode 100644 index 0000000..1af5b4d --- /dev/null +++ b/common/math/big.go @@ -0,0 +1,259 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package math provides integer math utilities. +package math + +import ( + "fmt" + "math/big" +) + +// Various big integer limit values. +var ( + tt255 = BigPow(2, 255) + tt256 = BigPow(2, 256) + tt256m1 = new(big.Int).Sub(tt256, big.NewInt(1)) + tt63 = BigPow(2, 63) + MaxBig256 = new(big.Int).Set(tt256m1) + MaxBig63 = new(big.Int).Sub(tt63, big.NewInt(1)) +) + +const ( + // number of bits in a big.Word + wordBits = 32 << (uint64(^big.Word(0)) >> 63) + // number of bytes in a big.Word + wordBytes = wordBits / 8 +) + +// HexOrDecimal256 marshals big.Int as hex or decimal. +type HexOrDecimal256 big.Int + +// NewHexOrDecimal256 creates a new HexOrDecimal256 +func NewHexOrDecimal256(x int64) *HexOrDecimal256 { + b := big.NewInt(x) + h := HexOrDecimal256(*b) + return &h +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (i *HexOrDecimal256) UnmarshalText(input []byte) error { + bigint, ok := ParseBig256(string(input)) + if !ok { + return fmt.Errorf("invalid hex or decimal integer %q", input) + } + *i = HexOrDecimal256(*bigint) + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (i *HexOrDecimal256) MarshalText() ([]byte, error) { + if i == nil { + return []byte("0x0"), nil + } + return []byte(fmt.Sprintf("%#x", (*big.Int)(i))), nil +} + +// Decimal256 unmarshals big.Int as a decimal string. When unmarshalling, +// it however accepts either "0x"-prefixed (hex encoded) or non-prefixed (decimal) +type Decimal256 big.Int + +// NewHexOrDecimal256 creates a new Decimal256 +func NewDecimal256(x int64) *Decimal256 { + b := big.NewInt(x) + d := Decimal256(*b) + return &d +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (i *Decimal256) UnmarshalText(input []byte) error { + bigint, ok := ParseBig256(string(input)) + if !ok { + return fmt.Errorf("invalid hex or decimal integer %q", input) + } + *i = Decimal256(*bigint) + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (i *Decimal256) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// String implements Stringer. +func (i *Decimal256) String() string { + if i == nil { + return "0" + } + return fmt.Sprintf("%#d", (*big.Int)(i)) +} + +// ParseBig256 parses s as a 256 bit integer in decimal or hexadecimal syntax. +// Leading zeros are accepted. The empty string parses as zero. +func ParseBig256(s string) (*big.Int, bool) { + if s == "" { + return new(big.Int), true + } + var bigint *big.Int + var ok bool + if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { + bigint, ok = new(big.Int).SetString(s[2:], 16) + } else { + bigint, ok = new(big.Int).SetString(s, 10) + } + if ok && bigint.BitLen() > 256 { + bigint, ok = nil, false + } + return bigint, ok +} + +// MustParseBig256 parses s as a 256 bit big integer and panics if the string is invalid. +func MustParseBig256(s string) *big.Int { + v, ok := ParseBig256(s) + if !ok { + panic("invalid 256 bit integer: " + s) + } + return v +} + +// BigPow returns a ** b as a big integer. +func BigPow(a, b int64) *big.Int { + r := big.NewInt(a) + return r.Exp(r, big.NewInt(b), nil) +} + +// BigMax returns the larger of x or y. +func BigMax(x, y *big.Int) *big.Int { + if x.Cmp(y) < 0 { + return y + } + return x +} + +// BigMin returns the smaller of x or y. +func BigMin(x, y *big.Int) *big.Int { + if x.Cmp(y) > 0 { + return y + } + return x +} + +// FirstBitSet returns the index of the first 1 bit in v, counting from LSB. +func FirstBitSet(v *big.Int) int { + for i := 0; i < v.BitLen(); i++ { + if v.Bit(i) > 0 { + return i + } + } + return v.BitLen() +} + +// PaddedBigBytes encodes a big integer as a big-endian byte slice. The length +// of the slice is at least n bytes. +func PaddedBigBytes(bigint *big.Int, n int) []byte { + if bigint.BitLen()/8 >= n { + return bigint.Bytes() + } + ret := make([]byte, n) + ReadBits(bigint, ret) + return ret +} + +// bigEndianByteAt returns the byte at position n, +// in Big-Endian encoding +// So n==0 returns the least significant byte +func bigEndianByteAt(bigint *big.Int, n int) byte { + words := bigint.Bits() + // Check word-bucket the byte will reside in + i := n / wordBytes + if i >= len(words) { + return byte(0) + } + word := words[i] + // Offset of the byte + shift := 8 * uint(n%wordBytes) + + return byte(word >> shift) +} + +// Byte returns the byte at position n, +// with the supplied padlength in Little-Endian encoding. +// n==0 returns the MSB +// Example: bigint '5', padlength 32, n=31 => 5 +func Byte(bigint *big.Int, padlength, n int) byte { + if n >= padlength { + return byte(0) + } + return bigEndianByteAt(bigint, padlength-1-n) +} + +// ReadBits encodes the absolute value of bigint as big-endian bytes. Callers must ensure +// that buf has enough space. If buf is too short the result will be incomplete. +func ReadBits(bigint *big.Int, buf []byte) { + i := len(buf) + for _, d := range bigint.Bits() { + for j := 0; j < wordBytes && i > 0; j++ { + i-- + buf[i] = byte(d) + d >>= 8 + } + } +} + +// U256 encodes as a 256 bit two's complement number. This operation is destructive. +func U256(x *big.Int) *big.Int { + return x.And(x, tt256m1) +} + +// U256Bytes converts a big Int into a 256bit EVM number. +// This operation is destructive. +func U256Bytes(n *big.Int) []byte { + return PaddedBigBytes(U256(n), 32) +} + +// S256 interprets x as a two's complement number. +// x must not exceed 256 bits (the result is undefined if it does) and is not modified. +// +// S256(0) = 0 +// S256(1) = 1 +// S256(2**255) = -2**255 +// S256(2**256-1) = -1 +func S256(x *big.Int) *big.Int { + if x.Cmp(tt255) < 0 { + return x + } + return new(big.Int).Sub(x, tt256) +} + +// Exp implements exponentiation by squaring. +// Exp returns a newly-allocated big integer and does not change +// base or exponent. The result is truncated to 256 bits. +// +// Courtesy @karalabe and @chfast +func Exp(base, exponent *big.Int) *big.Int { + result := big.NewInt(1) + + for _, word := range exponent.Bits() { + for i := 0; i < wordBits; i++ { + if word&1 == 1 { + U256(result.Mul(result, base)) + } + U256(base.Mul(base, base)) + word >>= 1 + } + } + return result +} diff --git a/common/types.go b/common/types.go new file mode 100644 index 0000000..fc6d9c3 --- /dev/null +++ b/common/types.go @@ -0,0 +1,428 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package common + +import ( + "bytes" + "database/sql/driver" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "math/big" + "math/rand" + "reflect" + "strings" + + "github.com/ChainSQL/go-chainsql-api/common/hexutil" + "golang.org/x/crypto/sha3" +) + +// Lengths of hashes and addresses in bytes. +const ( + // HashLength is the expected length of the hash + HashLength = 32 + // AddressLength is the expected length of the address + AddressLength = 20 +) + +var ( + hashT = reflect.TypeOf(Hash{}) + addressT = reflect.TypeOf(Address{}) +) + +// Hash represents the 32 byte Keccak256 hash of arbitrary data. +type Hash [HashLength]byte + +// BytesToHash sets b to hash. +// If b is larger than len(h), b will be cropped from the left. +func BytesToHash(b []byte) Hash { + var h Hash + h.SetBytes(b) + return h +} + +// BigToHash sets byte representation of b to hash. +// If b is larger than len(h), b will be cropped from the left. +func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } + +// HexToHash sets byte representation of s to hash. +// If b is larger than len(h), b will be cropped from the left. +func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } + +// Bytes gets the byte representation of the underlying hash. +func (h Hash) Bytes() []byte { return h[:] } + +// Big converts a hash to a big integer. +func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) } + +// Hex converts a hash to a hex string. +func (h Hash) Hex() string { return hexutil.Encode(h[:]) } + +// TerminalString implements log.TerminalStringer, formatting a string for console +// output during logging. +func (h Hash) TerminalString() string { + return fmt.Sprintf("%x..%x", h[:3], h[29:]) +} + +// String implements the stringer interface and is used also by the logger when +// doing full logging into a file. +func (h Hash) String() string { + return h.Hex() +} + +// Format implements fmt.Formatter. +// Hash supports the %v, %s, %q, %x, %X and %d format verbs. +func (h Hash) Format(s fmt.State, c rune) { + hexb := make([]byte, 2+len(h)*2) + copy(hexb, "0x") + hex.Encode(hexb[2:], h[:]) + + switch c { + case 'x', 'X': + if !s.Flag('#') { + hexb = hexb[2:] + } + if c == 'X' { + hexb = bytes.ToUpper(hexb) + } + fallthrough + case 'v', 's': + s.Write(hexb) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(hexb) + s.Write(q) + case 'd': + fmt.Fprint(s, ([len(h)]byte)(h)) + default: + fmt.Fprintf(s, "%%!%c(hash=%x)", c, h) + } +} + +// UnmarshalText parses a hash in hex syntax. +func (h *Hash) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("Hash", input, h[:]) +} + +// UnmarshalJSON parses a hash in hex syntax. +func (h *Hash) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(hashT, input, h[:]) +} + +// MarshalText returns the hex representation of h. +func (h Hash) MarshalText() ([]byte, error) { + return hexutil.Bytes(h[:]).MarshalText() +} + +// SetBytes sets the hash to the value of b. +// If b is larger than len(h), b will be cropped from the left. +func (h *Hash) SetBytes(b []byte) { + if len(b) > len(h) { + b = b[len(b)-HashLength:] + } + + copy(h[HashLength-len(b):], b) +} + +// Generate implements testing/quick.Generator. +func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { + m := rand.Intn(len(h)) + for i := len(h) - 1; i > m; i-- { + h[i] = byte(rand.Uint32()) + } + return reflect.ValueOf(h) +} + +// Scan implements Scanner for database/sql. +func (h *Hash) Scan(src interface{}) error { + srcB, ok := src.([]byte) + if !ok { + return fmt.Errorf("can't scan %T into Hash", src) + } + if len(srcB) != HashLength { + return fmt.Errorf("can't scan []byte of len %d into Hash, want %d", len(srcB), HashLength) + } + copy(h[:], srcB) + return nil +} + +// Value implements valuer for database/sql. +func (h Hash) Value() (driver.Value, error) { + return h[:], nil +} + +// ImplementsGraphQLType returns true if Hash implements the specified GraphQL type. +func (Hash) ImplementsGraphQLType(name string) bool { return name == "Bytes32" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (h *Hash) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + err = h.UnmarshalText([]byte(input)) + default: + err = fmt.Errorf("unexpected type %T for Hash", input) + } + return err +} + +// UnprefixedHash allows marshaling a Hash without 0x prefix. +type UnprefixedHash Hash + +// UnmarshalText decodes the hash from hex. The 0x prefix is optional. +func (h *UnprefixedHash) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedUnprefixedText("UnprefixedHash", input, h[:]) +} + +// MarshalText encodes the hash as hex. +func (h UnprefixedHash) MarshalText() ([]byte, error) { + return []byte(hex.EncodeToString(h[:])), nil +} + +/////////// Address + +// Address represents the 20 byte address of an Ethereum account. +type Address [AddressLength]byte + +// BytesToAddress returns Address with value b. +// If b is larger than len(h), b will be cropped from the left. +func BytesToAddress(b []byte) Address { + var a Address + a.SetBytes(b) + return a +} + +// BigToAddress returns Address with byte values of b. +// If b is larger than len(h), b will be cropped from the left. +func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } + +// HexToAddress returns Address with byte values of s. +// If s is larger than len(h), s will be cropped from the left. +func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } + +// IsHexAddress verifies whether a string can represent a valid hex-encoded +// Ethereum address or not. +func IsHexAddress(s string) bool { + if has0xPrefix(s) { + s = s[2:] + } + return len(s) == 2*AddressLength && isHex(s) +} + +// Bytes gets the string representation of the underlying address. +func (a Address) Bytes() []byte { return a[:] } + +// Hash converts an address to a hash by left-padding it with zeros. +func (a Address) Hash() Hash { return BytesToHash(a[:]) } + +// Hex returns an EIP55-compliant hex string representation of the address. +func (a Address) Hex() string { + return string(a.checksumHex()) +} + +// String implements fmt.Stringer. +func (a Address) String() string { + return a.Hex() +} + +func (a *Address) checksumHex() []byte { + buf := a.hex() + + // compute checksum + sha := sha3.NewLegacyKeccak256() + sha.Write(buf[2:]) + hash := sha.Sum(nil) + for i := 2; i < len(buf); i++ { + hashByte := hash[(i-2)/2] + if i%2 == 0 { + hashByte = hashByte >> 4 + } else { + hashByte &= 0xf + } + if buf[i] > '9' && hashByte > 7 { + buf[i] -= 32 + } + } + return buf[:] +} + +func (a Address) hex() []byte { + var buf [len(a)*2 + 2]byte + copy(buf[:2], "0x") + hex.Encode(buf[2:], a[:]) + return buf[:] +} + +// Format implements fmt.Formatter. +// Address supports the %v, %s, %q, %x, %X and %d format verbs. +func (a Address) Format(s fmt.State, c rune) { + switch c { + case 'v', 's': + s.Write(a.checksumHex()) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(a.checksumHex()) + s.Write(q) + case 'x', 'X': + // %x disables the checksum. + hex := a.hex() + if !s.Flag('#') { + hex = hex[2:] + } + if c == 'X' { + hex = bytes.ToUpper(hex) + } + s.Write(hex) + case 'd': + fmt.Fprint(s, ([len(a)]byte)(a)) + default: + fmt.Fprintf(s, "%%!%c(address=%x)", c, a) + } +} + +// SetBytes sets the address to the value of b. +// If b is larger than len(a), b will be cropped from the left. +func (a *Address) SetBytes(b []byte) { + if len(b) > len(a) { + b = b[len(b)-AddressLength:] + } + copy(a[AddressLength-len(b):], b) +} + +// MarshalText returns the hex representation of a. +func (a Address) MarshalText() ([]byte, error) { + return hexutil.Bytes(a[:]).MarshalText() +} + +// UnmarshalText parses a hash in hex syntax. +func (a *Address) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("Address", input, a[:]) +} + +// UnmarshalJSON parses a hash in hex syntax. +func (a *Address) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(addressT, input, a[:]) +} + +// Scan implements Scanner for database/sql. +func (a *Address) Scan(src interface{}) error { + srcB, ok := src.([]byte) + if !ok { + return fmt.Errorf("can't scan %T into Address", src) + } + if len(srcB) != AddressLength { + return fmt.Errorf("can't scan []byte of len %d into Address, want %d", len(srcB), AddressLength) + } + copy(a[:], srcB) + return nil +} + +// Value implements valuer for database/sql. +func (a Address) Value() (driver.Value, error) { + return a[:], nil +} + +// ImplementsGraphQLType returns true if Hash implements the specified GraphQL type. +func (a Address) ImplementsGraphQLType(name string) bool { return name == "Address" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (a *Address) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + err = a.UnmarshalText([]byte(input)) + default: + err = fmt.Errorf("unexpected type %T for Address", input) + } + return err +} + +// UnprefixedAddress allows marshaling an Address without 0x prefix. +type UnprefixedAddress Address + +// UnmarshalText decodes the address from hex. The 0x prefix is optional. +func (a *UnprefixedAddress) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedUnprefixedText("UnprefixedAddress", input, a[:]) +} + +// MarshalText encodes the address as hex. +func (a UnprefixedAddress) MarshalText() ([]byte, error) { + return []byte(hex.EncodeToString(a[:])), nil +} + +// MixedcaseAddress retains the original string, which may or may not be +// correctly checksummed +type MixedcaseAddress struct { + addr Address + original string +} + +// NewMixedcaseAddress constructor (mainly for testing) +func NewMixedcaseAddress(addr Address) MixedcaseAddress { + return MixedcaseAddress{addr: addr, original: addr.Hex()} +} + +// NewMixedcaseAddressFromString is mainly meant for unit-testing +func NewMixedcaseAddressFromString(hexaddr string) (*MixedcaseAddress, error) { + if !IsHexAddress(hexaddr) { + return nil, errors.New("invalid address") + } + a := FromHex(hexaddr) + return &MixedcaseAddress{addr: BytesToAddress(a), original: hexaddr}, nil +} + +// UnmarshalJSON parses MixedcaseAddress +func (ma *MixedcaseAddress) UnmarshalJSON(input []byte) error { + if err := hexutil.UnmarshalFixedJSON(addressT, input, ma.addr[:]); err != nil { + return err + } + return json.Unmarshal(input, &ma.original) +} + +// MarshalJSON marshals the original value +func (ma *MixedcaseAddress) MarshalJSON() ([]byte, error) { + if strings.HasPrefix(ma.original, "0x") || strings.HasPrefix(ma.original, "0X") { + return json.Marshal(fmt.Sprintf("0x%s", ma.original[2:])) + } + return json.Marshal(fmt.Sprintf("0x%s", ma.original)) +} + +// Address returns the address +func (ma *MixedcaseAddress) Address() Address { + return ma.addr +} + +// String implements fmt.Stringer +func (ma *MixedcaseAddress) String() string { + if ma.ValidChecksum() { + return fmt.Sprintf("%s [chksum ok]", ma.original) + } + return fmt.Sprintf("%s [chksum INVALID]", ma.original) +} + +// ValidChecksum returns true if the address has valid checksum +func (ma *MixedcaseAddress) ValidChecksum() bool { + return ma.original == ma.addr.Hex() +} + +// Original returns the mixed-case input string +func (ma *MixedcaseAddress) Original() string { + return ma.original +} diff --git a/core/chainsql.go b/core/chainsql.go index 34a4aa3..f5766d7 100644 --- a/core/chainsql.go +++ b/core/chainsql.go @@ -1,19 +1,32 @@ package core import ( - "log" + "encoding/hex" + "encoding/json" + "fmt" + "strings" "github.com/ChainSQL/go-chainsql-api/crypto" . "github.com/ChainSQL/go-chainsql-api/data" "github.com/ChainSQL/go-chainsql-api/export" "github.com/ChainSQL/go-chainsql-api/net" "github.com/ChainSQL/go-chainsql-api/util" + "github.com/buger/jsonparser" ) // Chainsql is the interface struct for this package type Chainsql struct { - client *net.Client + // client *net.Client SubmitBase + op *ChainsqlTxInfo +} + +//TxInfo is the opearting details +type ChainsqlTxInfo struct { + //Signer Signer + Raw string + TxType TransactionType + Query []interface{} } type TableGetSqlJSON struct { @@ -25,9 +38,12 @@ type TableGetSqlJSON struct { // NewChainsql is a function that create a chainsql object func NewChainsql() *Chainsql { chainsql := &Chainsql{ - client: net.NewClient(), + // client: net.NewClient(), + op: &ChainsqlTxInfo{ + Query: make([]interface{}, 0), + }, } - chainsql.SubmitBase.client = chainsql.client + chainsql.client = net.NewClient() chainsql.SubmitBase.IPrepare = chainsql return chainsql } @@ -47,22 +63,14 @@ func (c *Chainsql) Use(owner string) { c.client.Auth.Owner = owner } -// PrepareTx prepare tx json for submit -func (c *Chainsql) PrepareTx() (Signer, error) { - - log.Println("Chainsql prepareTx") - tx := &TableListSet{} - return tx, nil -} - //Table create a new table object func (c *Chainsql) Table(name string) *Table { return NewTable(name, c.client) } //Connect is used to create a websocket connection -func (c *Chainsql) Connect(url string) error { - return c.client.Connect(url) +func (c *Chainsql) Connect(url, tlsRootCertPath, tlsClientCertPath, tlsClientKeyPath, serverName string) error { + return c.client.Connect(url, tlsRootCertPath, tlsClientCertPath, tlsClientKeyPath, serverName) } // GetLedger request a ledger @@ -70,6 +78,11 @@ func (c *Chainsql) GetLedger(seq int) string { return c.client.GetLedger(seq) } +// GetLedgerTransactions request a ledger +func (c *Chainsql) GetLedgerTransactions(seq int, expand bool) string { + return c.client.GetLedgerTransactions(seq, expand) +} + //OnLedgerClosed reponses in callback functor func (c *Chainsql) OnLedgerClosed(callback export.Callback) { c.client.Event.SubscribeLedger(callback) @@ -82,12 +95,16 @@ func (c *Chainsql) OnLedgerClosed(callback export.Callback) { // "publicKeyHex":"02EA30B2A25844D4AFBAF6020DA9C9FED573AA0058791BFC8642E69888693CF8EA", // "privateKey":"xniMQKhxZTMbfWb8scjRPXa5Zv6HB", // } -func (c *Chainsql) GenerateAccount(args ...string) (string, error) { +/*func (c *Chainsql) GenerateAccount(args ...string) (string, error) { if len(args) == 0 { return crypto.GenerateAccount() } else { return crypto.GenerateAccount(args[0]) } +}*/ + +func (c *Chainsql) GenerateAddress(options string) (string, error) { + return crypto.GenerateAddress(options) } //SignPlainData sign a plain text and return the signature @@ -109,7 +126,7 @@ func (c *Chainsql) GetBySqlUser(sql string) (string, error) { if c.client.ServerInfo.Updated { data.LedgerIndex = c.client.ServerInfo.LedgerIndex } else { - ledgerIndex, err := c.client.GetLedgerVersion() + ledgerIndex, err := c.client.GetLedgerCurrent() if err != nil { return "", err } @@ -127,6 +144,7 @@ func (c *Chainsql) IsConnected() bool { func (c *Chainsql) Disconnect() { if c.client.GetWebocketManager() != nil { + c.client.Unsubscribe() c.client.GetWebocketManager().Disconnect() } } @@ -136,32 +154,270 @@ func (c *Chainsql) ValidationCreate() (string, error) { } func (c *Chainsql) GetServerInfo() (string, error) { - return "", nil + return c.client.GetServerInfo() } func (c *Chainsql) GetAccountInfo(address string) (string, error) { - return crypto.GetAccountInfo(address) + return c.client.GetAccountInfo(address) } -func (c *Chainsql) Pay(accountId string, value string) *Ripple { - r := NewRipple() +func (c *Chainsql) Pay(accountId string, value int64) *Ripple { + r := NewRipple(c.client) return r.Pay(accountId, value) } func (c *Chainsql) CreateSchema(schemaInfo string) *Chainsql { + c.op.TxType = SCHEMA_CREATE + c.op.Raw = schemaInfo + return c +} + +func (c *Chainsql) createSchema() (Signer, error) { + var schemaInfo = c.op.Raw + isValid := strings.Contains(schemaInfo, "SchemaName") && strings.Contains(schemaInfo, "WithState") && + strings.Contains(schemaInfo, "Validators") && strings.Contains(schemaInfo, "PeerList") + + if !isValid { + return nil, fmt.Errorf("Invalid schemaInfo parameter") + } + createSchema := &SchemaCreate{TxBase: TxBase{TransactionType: SCHEMA_CREATE}} + var jsonObj CreateSchema + err := json.Unmarshal([]byte(schemaInfo), &jsonObj) + if err != nil { + return nil, err + } + + createSchema.SchemaName = VariableLength(jsonObj.SchemaName) + if strings.Contains(schemaInfo, "SchemaAdmin") { + account, err := NewAccountFromAddress(jsonObj.SchemaAdmin) + if err != nil { + return nil, fmt.Errorf("Invalid schemaInfo parameter: SchemaAdmin") + } + if account != nil { + createSchema.SchemaAdmin = account + } + } + + if jsonObj.WithState { + //继承主链的节点状态 + if strings.Contains(schemaInfo, "AnchorLedgerHash") { + leadgerHash, errHash := NewHash256(jsonObj.AnchorLedgerHash) + if errHash != nil { + return nil, fmt.Errorf("Invalid schemaInfo parameter: AnchorLedgerHash") + } + if leadgerHash != nil { + createSchema.AnchorLedgerHash = leadgerHash + } + } + createSchema.SchemaStrategy = 2 + } else { + // 不继承主链的节点状态 + createSchema.SchemaStrategy = 1 + if strings.Contains(schemaInfo, "AnchorLedgerHash") { + return nil, fmt.Errorf("Field 'AnchorLedgerHash' is unnecessary") + } + } + validatorSlice := make([]ValidatorFormat, len(jsonObj.Validators)) + for i := 0; i < len(jsonObj.Validators); i++ { + publicKeyHex := jsonObj.Validators[i].Validator.PublicKey + publicKey, _ := hex.DecodeString(publicKeyHex) + validatorSlice[i].Validator.PublicKey = VariableLength(publicKey) + } + createSchema.Validators = validatorSlice + peerSlice := make([]PeerFormat, len(jsonObj.PeerList)) + for i := 0; i < len(jsonObj.PeerList); i++ { + endpoint := jsonObj.PeerList[i].Peer.Endpoint + peerSlice[i].Peer.Endpoint = VariableLength(endpoint) + } + + createSchema.PeerList = peerSlice + var signer Signer = createSchema + return signer, nil +} +func (c *Chainsql) ModifySchema(schemaType string, schemaInfo string) *Chainsql { + c.op.TxType = SCHEMA_MODIFY + c.op.Raw = "{\"SchemaType\": \"" + schemaType + "\", \"SchemaInfo\":" + schemaInfo + "}" return c } -func (c *Chainsql) ModifySchema(schemaInfo string) *Chainsql { +func (c *Chainsql) modifySchema() (Signer, error) { + schemaType, _ := jsonparser.GetString([]byte(c.op.Raw), "SchemaType") + result, _, _, _ := jsonparser.Get([]byte(c.op.Raw), "SchemaInfo") + schemaInfo := string(result) + isValid := strings.Contains(schemaInfo, "SchemaID") && strings.Contains(schemaInfo, "Validators") && strings.Contains(schemaInfo, "PeerList") + + if !isValid { + return nil, fmt.Errorf("Invalid schemaInfo parameter") + } + var jsonObj ModifySchema + errUnmarshal := json.Unmarshal([]byte(schemaInfo), &jsonObj) + if errUnmarshal != nil { + return nil, errUnmarshal + } + schemaModify := &SchemaModify{TxBase: TxBase{TransactionType: SCHEMA_MODIFY}} + if schemaType == util.SchemaDel { + schemaModify.OpType = util.OpTypeSchemaDel + } else { + schemaModify.OpType = util.OpTypeSchemaAdd + } + validatorSlice := make([]ValidatorFormat, len(jsonObj.Validators)) + for i := 0; i < len(jsonObj.Validators); i++ { + publicKeyHex := jsonObj.Validators[i].Validator.PublicKey + publicKey, _ := hex.DecodeString(publicKeyHex) + validatorSlice[i].Validator.PublicKey = VariableLength(publicKey) + } + schemaModify.Validators = validatorSlice + + peerSlice := make([]PeerFormat, len(jsonObj.PeerList)) + for i := 0; i < len(jsonObj.PeerList); i++ { + endpoint := jsonObj.PeerList[i].Peer.Endpoint + peerSlice[i].Peer.Endpoint = VariableLength(endpoint) + } + schemaModify.PeerList = peerSlice + schemaIdHash, errHash := NewHash256(jsonObj.SchemaID) + if errHash != nil { + return nil, errHash + } + if schemaIdHash != nil { + schemaModify.SchemaID = *schemaIdHash + } + //schemaModify.TransactionType = SCHEMA_MODIFY + var signer Signer = schemaModify + return signer, nil +} +func (c *Chainsql) DeleteSchema(schemaID string) *Chainsql { + c.op.TxType = SCHEMA_DELETE + c.op.Raw = schemaID return c } +func (c *Chainsql) deleteSchema() (Signer, error) { + var schemaID = c.op.Raw + if schemaID == "" { + return nil, fmt.Errorf("Invalid parameter") + } + schemaDelete := &SchemaDelete{TxBase: TxBase{TransactionType: SCHEMA_DELETE}} + schemaIdHash, errHash := NewHash256(schemaID) + if errHash != nil { + return nil, errHash + } + if schemaIdHash != nil { + schemaDelete.SchemaID = *schemaIdHash + } + var signer Signer = schemaDelete + return signer, nil +} + func (c *Chainsql) GetSchemaList(params string) (string, error) { - return "", nil + return c.client.GetSchemaList(params) } -func (c *Chainsql) UpdateSchemaConfig(params string) *Chainsql { - return c +func (c *Chainsql) GetSchemaInfo(schemaID string) (string, error) { + return c.client.GetSchemaInfo(schemaID) +} + +func (c *Chainsql) StopSchema(schemaID string) (string, error) { + return c.client.StopSchema(schemaID) +} + +func (c *Chainsql) StartSchema(schemaID string) (string, error) { + return c.client.StartSchema(schemaID) +} + +func (c *Chainsql) SetSchema(schemaId string) { + if c.client.SchemaID != schemaId { + c.client.Unsubscribe() + c.client.SchemaID = schemaId + c.client.InitSubscription() + } +} +func (c *Chainsql) GetSchemaId(hash string) (string, error) { + response, _ := c.client.GetTransaction(hash) + if response == "" { + return "", fmt.Errorf("Transaction does not exist ") + } + schemaID := "" + flag := false + //LedgerEntryType, err := jsonparser.GetString([]byte(response), "result", "meta", "AffectedNodes", "[0]", "CreatedNode", "LedgerEntryType") + jsonparser.ArrayEach([]byte(response), func(value []byte, dataType jsonparser.ValueType, offset int, err error) { + LedgerEntryType, err := jsonparser.GetString(value, "CreatedNode", "LedgerEntryType") + if err == nil { + if LedgerEntryType == "Schema" { + schemaID, _ = jsonparser.GetString([]byte(value), "CreatedNode", "LedgerIndex") + flag = true + } + } + + }, "result", "meta", "AffectedNodes") + if flag { + return schemaID, nil + } + return "", fmt.Errorf("Invalid parameter") +} +func (c *Chainsql) GetTransaction(hash string) (string, error) { + return c.client.GetTransaction(hash) +} + +func (c *Chainsql) GetTransactionResult(hash string) (string, error) { + return c.client.GetTransactionResult(hash) +} + +// PrepareTx prepare tx json for submit +func (c *Chainsql) PrepareTx() (Signer, error) { + var tx Signer + var err error + switch c.op.TxType { + case SCHEMA_CREATE: + tx, err = c.createSchema() + break + case SCHEMA_MODIFY: + tx, err = c.modifySchema() + break + case SCHEMA_DELETE: + tx, err = c.deleteSchema() + break + default: + } + if err != nil { + return nil, err + } + + return c.prepareTxBase(tx) +} + +func (c *Chainsql) prepareTxBase(tx Signer) (Signer, error) { + + //tx := c.op.Signer + seq, err := net.PrepareRipple(c.client) + if err != nil { + return nil, err + } + + fee, lastLedgerSeq, err := net.PrepareLastLedgerSeqAndFee(c.client) + if err != nil { + return nil, err + } + + if tx.GetRaw() != "" { + fee += util.GetExtraFee(tx.GetRaw(), c.client.ServerInfo.DropsPerByte) + } else if tx.GetStatements() != "" { + fee += util.GetExtraFee(tx.GetStatements(), c.client.ServerInfo.DropsPerByte) + } + + finalFee, err := NewNativeValue(fee) + if err != nil { + return nil, err + } + account, err := NewAccountFromAddress(c.client.Auth.Address) + if err != nil { + return nil, err + } + tx.SetTxBase(seq, *finalFee, &lastLedgerSeq, *account) + return tx, nil +} + +func (c *Chainsql) GetLedgerVersion() (int, error) { + return c.client.GetLedgerVersion() } diff --git a/core/contract.go b/core/contract.go new file mode 100644 index 0000000..6f5fe2a --- /dev/null +++ b/core/contract.go @@ -0,0 +1,625 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + "log" + "regexp" + "strings" + "sync" + + "github.com/ChainSQL/go-chainsql-api/abigen/abi" + . "github.com/ChainSQL/go-chainsql-api/abigen/abi/bind" + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/crypto" + "github.com/ChainSQL/go-chainsql-api/data" + . "github.com/ChainSQL/go-chainsql-api/data" + "github.com/ChainSQL/go-chainsql-api/export" + "github.com/ChainSQL/go-chainsql-api/net" + "github.com/ChainSQL/go-chainsql-api/util" + "github.com/buger/jsonparser" +) + +// SignerFn is a signer function callback when a contract requires a method to +// sign the transaction before submission. +// type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) + +// CallOpts is the collection of options to fine tune a contract call request. +type CallOpts struct { + LedgerIndex int64 // Optional the block number on which the call should be performed +} + +type CallReq struct { + common.RequestBase + Account string `json:"account"` + ContractAddress string `json:"contract_address"` + ContractData string `json:"contract_data"` + LedgerIndex uint32 `json:"ledger_index"` +} + +// TransactOpts is the collection of authorization data required to create a +// valid ChainSQL transaction. +type TransactOpts struct { + ContractValue int64 // Funds to transfer along the transaction (nil = 0 = no funds) + Gas uint32 + Expectation string +} + +type DeployTxRet struct { + common.TxResult + ContractAddress string `json:"contractAddress"` +} + +// FilterOpts is the collection of options to fine tune filtering for events +// within a bound contract. +type FilterOpts struct { + Start uint64 // Start of the queried range + End *uint64 // End of the range (nil = latest) + + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// WatchOpts is the collection of options to fine tune subscribing for events +// within a bound contract. +type WatchOpts struct { + Start *uint64 // Start of the queried range (nil = latest) + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// CtrMetaData collects all metadata for a bound contract. +type CtrMetaData struct { + mu sync.Mutex + Sigs map[string]string + Bin string + ABI string + ab *abi.ABI +} + +func (m *CtrMetaData) GetAbi() (*abi.ABI, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.ab != nil { + return m.ab, nil + } + if parsed, err := abi.JSON(strings.NewReader(m.ABI)); err != nil { + return nil, err + } else { + m.ab = &parsed + } + return m.ab, nil +} + +// BoundContract is the base wrapper object that reflects a contract on the +// Ethereum network. It contains a collection of methods that are used by the +// higher level contract bindings to operate. +type BoundContract struct { + SubmitBase + address string // Deployment address of the contract on the ChainSQL blockchain + abi abi.ABI // Reflect based ABI to access the correct ChainSQL methods + caller ContractCaller // Read interface to interact with the blockchain + transactor ContractTransactor // Write interface to interact with the blockchain + filterer ContractFilterer // Event filtering to interact with the blockchain + TransactOpts + ContractOpType uint16 + ContractData []byte + isFirstSubscribe bool + ctrEventCache map[string]export.Callback +} + +// NewBoundContract creates a low level contract interface through which calls +// and transactions may be made through. +// func NewBoundContract(chainsql *Chainsql, address string, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContract { +func NewBoundContract(chainsql *Chainsql, address string, abi abi.ABI) *BoundContract { + // bCtr := &BoundContract{ + // address: address, + // abi: abi, + // caller: caller, + // transactor: transactor, + // filterer: filterer, + // } + bCtr := &BoundContract{ + address: address, + abi: abi, + ContractOpType: 2, + isFirstSubscribe: true, + } + bCtr.client = chainsql.client + bCtr.IPrepare = bCtr + return bCtr +} + +// DeployContract deploys a contract onto the ChainSQL blockchain and binds the +// deployment address with a Go wrapper. +// func DeployContract(chainsql *Chainsql, opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (*DeployTxRet, *BoundContract, error) { +func DeployContract(chainsql *Chainsql, opts *TransactOpts, abi abi.ABI, bytecode []byte, params ...interface{}) (*DeployTxRet, *BoundContract, error) { + // Otherwise try to deploy the contract + // c := NewBoundContract(chainsql, "", abi, backend, backend, backend) + c := NewBoundContract(chainsql, "", abi) + c.ContractOpType = 1 + + input, err := c.abi.Pack("", params...) + if err != nil { + return nil, nil, err + } + if opts.Expectation == "" { + opts.Expectation = util.ValidateSuccess + } else if opts.Expectation == util.SendSuccess { + return nil, nil, errors.New("contract deploy tx expect must be validate_success or db_success") + } + txRet, err := c.transact(opts, append(bytecode, input...)) + if err != nil { + return nil, nil, err + } + + deployTxRet := &DeployTxRet{} + deployTxRet.Status = txRet.Status + deployTxRet.TxHash = txRet.TxHash + + ret, err := c.client.GetTransaction(deployTxRet.TxHash) + if err == nil { + txAddr, err := jsonparser.GetString([]byte(ret), "result", "Account") + if err != nil { + return deployTxRet, c, err + } + txAddrSeq, err := jsonparser.GetInt([]byte(ret), "result", "Sequence") + if err != nil { + return deployTxRet, c, err + } + txRetMeta, _, _, err := jsonparser.Get([]byte(ret), "result", "meta", "AffectedNodes") + if err != nil { + return deployTxRet, c, err + } + + var txDetailCtrAddr string + _, _ = jsonparser.ArrayEach(txRetMeta, func(value []byte, dataType jsonparser.ValueType, offset int, errin error) { + ctrAddr, err := jsonparser.GetString(value, "CreatedNode", "NewFields", "Account") + if err != nil { + return + } + txDetailCtrAddr = ctrAddr + // return + }) + c.address, err = crypto.CreateContractAddr(txAddr, uint32(txAddrSeq)) + if err != nil { + deployTxRet.ErrorMessage = err.Error() + return deployTxRet, c, err + } + if txDetailCtrAddr == "" || c.address != txDetailCtrAddr { + err := errors.New("mismatch, can't find correct contract address") + deployTxRet.ErrorMessage = err.Error() + return deployTxRet, c, err + } + deployTxRet.ContractAddress = c.address + } else { + err := errors.New("Get Tx detail failed, can't find correct contract address") + deployTxRet.ErrorMessage = err.Error() + return deployTxRet, c, err + } + + return deployTxRet, c, nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error { + // Don't crash on a lazy user + if opts == nil { + opts = new(CallOpts) + } + if results == nil { + results = new([]interface{}) + } + // Pack the input, call and unpack the results + input, err := c.abi.Pack(method, params...) + if err != nil { + return err + } + inputHexStr := fmt.Sprintf("%x", input) + callReq := &CallReq{ + Account: c.client.Auth.Address, + ContractAddress: c.address, + ContractData: inputHexStr, + } + if opts.LedgerIndex == 0 { + _, lastLedgerSeq, _ := net.PrepareLastLedgerSeqAndFee(c.client) + + callReq.LedgerIndex = lastLedgerSeq - 20 + } + + callReq.Command = "contract_call" + request := c.client.SyncRequest(callReq) + + err = c.client.ParseResponseError(request) + if err != nil { + return err + } + + contractCallRet, err := jsonparser.GetString([]byte(request.Response.Value), "result", "contract_call_result") + if err != nil { + return err + } + ctrCallRetHex, err := hex.DecodeString(contractCallRet[2:]) + if err != nil { + return err + } + if len(*results) == 0 { + res, err := c.abi.Unpack(method, ctrCallRetHex) + *results = res + return err + } + res := *results + return c.abi.UnpackIntoInterface(res[0], method, ctrCallRetHex) +} + +// Transact invokes the (paid) contract method with params as input values. +// func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { +func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*common.TxResult, error) { + // Otherwise pack up the parameters and invoke the contract + input, err := c.abi.Pack(method, params...) + if err != nil { + return nil, err + } + // todo(rjl493456442) check the method is payable or not, + // reject invalid transaction at the first place + // return c.transact(opts, &c.address, input) + return c.transact(opts, input) +} + +// // RawTransact initiates a transaction with the given raw calldata as the input. +// // It's usually used to initiate transactions for invoking **Fallback** function. +// func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { +// // todo(rjl493456442) check the method is payable or not, +// // reject invalid transaction at the first place +// return c.transact(opts, &c.address, calldata) +// } + +// // Transfer initiates a plain transaction to move funds to the contract, calling +// // its default method if one is available. +// func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { +// // todo(rjl493456442) check the payable fallback or receive is defined +// // or not, reject invalid transaction at the first place +// return c.transact(opts, &c.address, nil) +// } + +// func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int) (uint64, error) { +// if contract != nil { +// // Gas estimation cannot succeed without code for method invocations. +// if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil { +// return 0, err +// } else if len(code) == 0 { +// return 0, ErrNoCode +// } +// } +// msg := ethereum.CallMsg{ +// From: opts.From, +// To: contract, +// GasPrice: gasPrice, +// GasTipCap: gasTipCap, +// GasFeeCap: gasFeeCap, +// Value: value, +// Data: input, +// } +// return c.transactor.EstimateGas(ensureContext(opts.Context), msg) +// } + +// func (c *BoundContract) getNonce(opts *TransactOpts) (uint64, error) { +// if opts.Nonce == nil { +// return c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From) +// } else { +// return opts.Nonce.Uint64(), nil +// } +// } + +func (c *BoundContract) PrepareTx() (Signer, error) { + contractTxObj := &ContractTx{} + + contractTxObj.TransactionType = CONTRACT + account, err := NewAccountFromAddress(c.client.Auth.Address) + if err != nil { + return nil, err + } + contractTxObj.Account = *account + seq, err := net.PrepareRipple(c.client) + if err != nil { + return nil, err + } + contractTxObj.Sequence = seq + contractTxObj.ContractOpType = c.ContractOpType + + // if contractTxObj.ContractOpType == 1 { + // var zeroAccount Account + // contractTxObj.ContractAddress = zeroAccount + // } else { + if contractTxObj.ContractOpType == 2 { + contracAddress, err := NewAccountFromAddress(c.address) + if err != nil { + return nil, err + } + contractTxObj.ContractAddress = *contracAddress + } + + contractTxObj.ContractData = c.ContractData + + contractValue, _ := NewNativeValue(c.ContractValue) + currencyZxc, _ := NewCurrency("ZXC") + contractAmount := Amount{ + Value: contractValue, + Currency: currencyZxc, + } + contractTxObj.ContractValue = contractAmount + contractTxObj.Gas = c.Gas + + fee, lastLedgerSeq, err := net.PrepareLastLedgerSeqAndFee(c.client) + if err != nil { + return nil, err + } + + contractTxObj.LastLedgerSequence = &lastLedgerSeq + finalFee, err := NewNativeValue(fee) + if err != nil { + return nil, err + } + contractTxObj.Fee = *finalFee + + return contractTxObj, nil +} + +// transact executes an actual transaction invocation, first deriving any missing +// authorization fields, and then scheduling the transaction for execution. +// func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*common.TxResult, error) { +func (c *BoundContract) transact(opts *TransactOpts, input []byte) (*common.TxResult, error) { + // Create the transaction + c.ContractValue = opts.ContractValue + c.Gas = opts.Gas + c.ContractData = input + + return c.Submit(opts.Expectation), nil +} + +// FilterLogs filters contract logs for past blocks, returning the necessary +// channels to construct a strongly typed bound iterator on top of them. +// func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) { +// // Don't crash on a lazy user +// if opts == nil { +// opts = new(FilterOpts) +// } +// // Append the event selector to the query parameters and construct the topic set +// query = append([][]interface{}{{c.abi.Events[name].ID}}, query...) + +// topics, err := abi.MakeTopics(query...) +// if err != nil { +// return nil, nil, err +// } +// // Start the background filtering +// logs := make(chan types.Log, 128) + +// config := ethereum.FilterQuery{ +// Addresses: []common.Address{c.address}, +// Topics: topics, +// FromBlock: new(big.Int).SetUint64(opts.Start), +// } +// if opts.End != nil { +// config.ToBlock = new(big.Int).SetUint64(*opts.End) +// } +// /* TODO(karalabe): Replace the rest of the method below with this when supported +// sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) +// */ +// buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config) +// if err != nil { +// return nil, nil, err +// } +// sub, err := event.NewSubscription(func(quit <-chan struct{}) error { +// for _, log := range buff { +// select { +// case logs <- log: +// case <-quit: +// return nil +// } +// } +// return nil +// }), nil + +// if err != nil { +// return nil, nil, err +// } +// return logs, sub, nil +// } +type EventSub struct { + eventSign string + EventMsgCh chan *data.Log + err chan error + ctr *BoundContract +} + +func (e *EventSub) UnSubscribe() { + e.ctr.client.UnRegisterCtrEvent(e.eventSign, e.EventMsgCh) +} + +func (e *EventSub) Err() chan error { + return e.err +} + +// WatchLogs filters subscribes to contract logs for future blocks, returning a +// subscription object that can be used to tear down the watcher. +// func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan data.Log, event.Subscription, error) { +func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (*EventSub, error) { + // Don't crash on a lazy user + if opts == nil { + opts = new(WatchOpts) + } + // Append the event selector to the query parameters and construct the topic set + query = append([][]interface{}{{c.abi.Events[name].ID}}, query...) + + topics, err := abi.MakeTopics(query...) + if err != nil { + return nil, err + } + log.Println(topics[0][0]) + + // Start the background filtering + // logs := make(chan data.Log, 128) + + if c.isFirstSubscribe { + c.client.SubscribeCtrAddr(c.address, true) + c.isFirstSubscribe = false + } + + eventMsgCh := make(chan *data.Log) + errCh := make(chan error) + eventSig := topics[0][0].String() + c.client.RegisterCtrEvent(eventSig, eventMsgCh) + + sub := &EventSub{ + eventSign: eventSig, + EventMsgCh: eventMsgCh, + ctr: c, + err: errCh, + } + + return sub, nil +} + +func (c *BoundContract) GetPastEventByCtrLog(ContractLogs string) ([]*data.Log, error) { + contractLogsHexBytes, _ := hex.DecodeString(ContractLogs) + contractLogsStr := string(contractLogsHexBytes) + + reg1 := regexp.MustCompile(`^\[|\n|\s+|]$`) + contractLogsStr1 := reg1.ReplaceAllString(contractLogsStr, "") + // log.Println(contractLogsStr1) + reg2 := regexp.MustCompile(`,{/`) + contractLogsStr2 := reg2.ReplaceAllString(contractLogsStr1, "-{") + + ctrLogsArray := strings.Split(contractLogsStr2, `-`) + + var newCtrLogs []*data.Log + for _, str := range ctrLogsArray { + logRaw := &data.Log{ + Address: c.address, + } + + contractData, err := jsonparser.GetString([]byte(str), "contract_data") + if err != nil { + return nil, err + } + ctrEventDataHex, err := hex.DecodeString(contractData) + if err != nil { + return nil, err + } + logRaw.Data = ctrEventDataHex + contractTopics, _, _, err := jsonparser.Get([]byte(str), "contract_topics") + if err != nil { + return nil, err + } + _, _ = jsonparser.ArrayEach(contractTopics, func(value []byte, dataType jsonparser.ValueType, offset int, errin error) { + valueStr := string(value) + valueHex, err := hex.DecodeString(valueStr) + if err != nil { + return + } + logRaw.Topics = append(logRaw.Topics, common.BytesToHash(valueHex)) + }) + newCtrLogs = append(newCtrLogs, logRaw) + } + + return newCtrLogs, nil +} + +func (c *BoundContract) GetPastEventByTxHash(txHash string) ([]*data.Log, error) { + if txHash == "" { + return nil, errors.New("txHash is empty") + } + + txDetailStr, err := c.client.GetTransaction(txHash) + if err != nil { + return nil, err + } + // log.Println(txDetailStr) + + txType, err := jsonparser.GetString([]byte(txDetailStr), "result", "TransactionType") + if err != nil { + return nil, err + } + if txType != "Contract" { + return nil, errors.New("not a contract tx") + } + // ctrAddr, err := jsonparser.GetString([]byte(txDetailStr), "result", "ContractAddress") + // if err != nil { + // return nil, err + // } + // if ctrAddr != c.address { + // return nil, errors.New("the event is not belong to current contract") + // } + ContractLogs, err := jsonparser.GetString([]byte(txDetailStr), "result", "meta", "ContractLogs") + if err != nil { + return nil, err + } + + return c.GetPastEventByCtrLog(ContractLogs) +} + +// UnpackLog unpacks a retrieved log into the provided output structure. +func (c *BoundContract) UnpackLog(out interface{}, event string, log data.Log) error { + if log.Topics[0] != c.abi.Events[event].ID { + return fmt.Errorf("event signature mismatch") + } + if len(log.Data) > 0 { + if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return abi.ParseTopics(out, indexed, log.Topics[1:]) +} + +// // UnpackLogIntoMap unpacks a retrieved log into the provided map. +// func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error { +// if log.Topics[0] != c.abi.Events[event].ID { +// return fmt.Errorf("event signature mismatch") +// } +// if len(log.Data) > 0 { +// if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { +// return err +// } +// } +// var indexed abi.Arguments +// for _, arg := range c.abi.Events[event].Inputs { +// if arg.Indexed { +// indexed = append(indexed, arg) +// } +// } +// return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:]) +// } + +// // ensureContext is a helper method to ensure a context is not nil, even if the +// // user specified it as such. +// func ensureContext(ctx context.Context) context.Context { +// if ctx == nil { +// return context.Background() +// } +// return ctx +// } diff --git a/core/ripple.go b/core/ripple.go index f34e1ae..c053571 100644 --- a/core/ripple.go +++ b/core/ripple.go @@ -2,7 +2,11 @@ package core import ( "fmt" + "strconv" + "github.com/buger/jsonparser" + + . "github.com/ChainSQL/go-chainsql-api/data" "github.com/ChainSQL/go-chainsql-api/net" ) @@ -10,6 +14,27 @@ import ( type Base struct { } +//OpInfo is the opearting details +// type TransactionRequest struct { +// TransactionType string +// Amount Amount +// Destination string +// Query []interface{} +// } + +//TxInfo is the opearting details +type TxInfo struct { + Raw string + TxType TransactionType + Query []interface{} +} + +// type Amount struct { +// Value string `json:"value"` +// Currency string `json:"currency"` +// Account string `json:"account"` +// } + func (b *Base) Say() { fmt.Println("base") } @@ -22,6 +47,7 @@ func (b *Base) Say2() { type Ripple struct { *Base client *net.Client + op *TxInfo SubmitBase } @@ -29,17 +55,116 @@ func (r *Ripple) Say() { fmt.Println("Ripple") } -func NewRipple() *Ripple { +func NewRipple(client *net.Client) *Ripple { ripple := &Ripple{ Base: &Base{}, - client: net.NewClient(), + client: client, + op: &TxInfo{ + Query: make([]interface{}, 0), + }, } ripple.SubmitBase.client = ripple.client ripple.SubmitBase.IPrepare = ripple return ripple } -func (r *Ripple) Pay(accountId string, value string) *Ripple { +func (r *Ripple) Pay(accountId string, value int64) *Ripple { + r.op.TxType = PAYMENT + r.op.Raw = "{\"AccountId\": \"" + accountId + "\", \"Value\": \"" + strconv.FormatInt(value, 10) + "\"}" return r } + +func (r *Ripple) pay(raw string) (Signer, error) { + accountId, _ := jsonparser.GetString([]byte(raw), "AccountId") + strValue, _ := jsonparser.GetString([]byte(raw), "Value") + value, _ := strconv.ParseInt(strValue, 10, 64) + + valueTemp, _ := NewNativeValue(value) + currency_zxc, _ := NewCurrency("ZXC") + amount := Amount{ + Value: valueTemp, + Currency: currency_zxc, + } + return r.PayToNode(accountId, amount) +} + +func (r *Ripple) PayToNode(accountId string, amount Amount) (Signer, error) { + + // if !amount.Currency.IsNative() { + // accountData, err := r.client.GetAccountInfo(string(amount.Issuer)) + // if err != nil { + // log.Println("get issuer %s", err) + // } + + // if accountData != "" { + // //var feeMin, feeMax = "", "" + // //var lFeeRate = Value(0) + // var mapObj map[string]interface{} + // va := amount.Value + // json.Unmarshal([]byte(accountData), &mapObj) + // feeMin := mapObj["TransferFeeMin"].(Value) + // feeMax := mapObj["TransferFeeMax"].(Value) + // lFeeRate := mapObj["TransferRate"].(Value) + // fee := Value() + // if feeMin.IsZero() || feeMax.IsZero() || lFeeRate.IsZero() { + // if feeMin == feeMax { + // fee = feeMin.Float() + // } else if !lFeeRate.IsZero() { + // // fee = FloatOperation.accMul(parseFloat(value), data.rate - 1); + // fee = va.Multiply(lFeeRate) + // if !feeMin.IsZero() { + // if + // fee = + // } + // if feeMax.IsZero() { + // fee = Math.min(fee, parseFloat(data.max)) + // } + // // + // value = value.add(fee) + // } + // } + // } + // } + destination, err := NewAccountFromAddress(accountId) + if err != nil { + return nil, err + } + payment := &Payment{ + //SendMax: nil, + Destination: *destination, + Amount: amount, + } + payment.TransactionType = PAYMENT + account, err := NewAccountFromAddress(r.client.Auth.Address) + if err != nil { + return nil, err + } + payment.Account = *account + seq, err := net.PrepareRipple(r.client) + if err != nil { + return nil, err + } + payment.Sequence = seq + valueTemp, _ := NewNativeValue(10) + payment.Fee = *valueTemp + var sign Signer = payment + return sign, nil +} + +//PrepareTx prepare tx json for submit +func (r *Ripple) PrepareTx() (Signer, error) { + var tx Signer + var err error + switch r.op.TxType { + case PAYMENT: + tx, err = r.pay(r.op.Raw) + break + default: + } + if err != nil { + return nil, err + } + return tx, nil + +} diff --git a/core/submit.go b/core/submit.go index 4fad1ba..811f2fd 100644 --- a/core/submit.go +++ b/core/submit.go @@ -1,12 +1,12 @@ package core import ( - "encoding/json" "fmt" "log" "strings" "sync" + . "github.com/ChainSQL/go-chainsql-api/common" "github.com/ChainSQL/go-chainsql-api/crypto" . "github.com/ChainSQL/go-chainsql-api/data" "github.com/ChainSQL/go-chainsql-api/export" @@ -25,14 +25,6 @@ type TxSigned struct { hash string } -// TxResult is tx submit response -type TxResult struct { - Status string `json:"status"` - TxHash string `json:"hash"` - ErrorCode string `json:"error,omitempty"` - ErrorMessage string `json:"errorMessage,omitempty"` -} - // IPrepare is an interface that a struct call submit() method must implment type IPrepare interface { PrepareTx() (Signer, error) @@ -46,12 +38,21 @@ type SubmitBase struct { IPrepare } +// // Submit submit a tx with a cocurrent expect +// func (s *SubmitBase) Submit(cond string) string { +// s.expect = cond +// ret := s.doSubmit() +// jsonRet, _ := json.Marshal(ret) +// return string(jsonRet) +// } // Submit submit a tx with a cocurrent expect -func (s *SubmitBase) Submit(cond string) string { - s.expect = cond - ret := s.doSubmit() - jsonRet, _ := json.Marshal(ret) - return string(jsonRet) +func (s *SubmitBase) Submit(cond string) (txRet *TxResult) { + if cond == "" { + s.expect = util.SendSuccess + } else { + s.expect = cond + } + return s.doSubmit() } //SubmitAsync submit a transaction and got response asynchronously @@ -83,8 +84,15 @@ func (s *SubmitBase) doSubmit() *TxResult { ErrorMessage: err.Error(), } } + /*var hasher hash.Hash + if key.Type() == common.SoftGMAlg { + hasher = sm3.New() + }else { + hasher = sha512.New() + } + */ sequenceZero := uint32(0) - err = Sign(tx, key, &sequenceZero) + err = Sign(tx, key, &sequenceZero, key.Type()) if err != nil { log.Printf("doSubmit error:%s\n", err) return &TxResult{ @@ -93,16 +101,8 @@ func (s *SubmitBase) doSubmit() *TxResult { } } - _, blob, err := Raw(tx) - if err != nil { - log.Printf("doSubmit error:%s\n", err) - return &TxResult{ - ErrorCode: "errSerialize", - ErrorMessage: err.Error(), - } - } txSigned := &TxSigned{ - blob: fmt.Sprintf("%X", blob), + blob: fmt.Sprintf("%X", *tx.GetBlob()), hash: string(crypto.B2H32(*tx.GetHash())), } diff --git a/core/table.go b/core/table.go index 41c9bc9..8e4399e 100644 --- a/core/table.go +++ b/core/table.go @@ -142,7 +142,7 @@ func (t *Table) Request() (string, error) { if t.client.ServerInfo.Updated { data.LedgerIndex = t.client.ServerInfo.LedgerIndex } else { - ledgerIndex, err := t.client.GetLedgerVersion() + ledgerIndex, err := t.client.GetLedgerCurrent() if err != nil { return "", err } @@ -181,22 +181,11 @@ func (t *Table) PrepareTx() (Signer, error) { tx.Account = *account tx.Owner = *owner tx.Sequence = seq - var fee int64 = 10 - if t.client.ServerInfo.Updated { - last := uint32(t.client.ServerInfo.LedgerIndex + 20) - tx.LastLedgerSequence = &last - fee = int64(t.client.ServerInfo.ComputeFee()) - } else { - ledgerIndex, err := t.client.GetLedgerVersion() - if err != nil { - return nil, err - } - last := uint32(ledgerIndex + 20) - tx.LastLedgerSequence = &last - - fee = 50 + fee, lastLedgerSeq, err := net.PrepareLastLedgerSeqAndFee(t.client) + if err != nil { + return nil, err } - + tx.LastLedgerSequence = &lastLedgerSeq fee += util.GetExtraFee(t.op.Raw, t.client.ServerInfo.DropsPerByte) finalFee, err := NewNativeValue(fee) if err != nil { diff --git a/crypto/account.go b/crypto/account.go index e7e5a9a..2367e17 100644 --- a/crypto/account.go +++ b/crypto/account.go @@ -2,19 +2,31 @@ package crypto import ( "crypto/rand" + "encoding/binary" "encoding/json" "fmt" "log" + "strings" + + "github.com/ChainSQL/go-chainsql-api/common" ) //Account define the account format type Account struct { - Address string `json:"address"` - PublicKey string `json:"publicKey"` - PublicKeyHex string `json:"publicKeyHex"` - PrivateKey string `json:"privateKey"` + Address string `json:"address"` + PublicKeyBase58 string `json:"publicKeyBase58"` + PublicKeyHex string `json:"publicKeyHex"` + PrivateSeed string `json:"privateSeed"` + PrivateKey interface{} `json:"privateKey"` + PublicKey interface{} `json:"publicKey"` +} + +type SeedKey struct { + Seed string `json:"seed"` + PublicKey string `json:"publicKey"` } +//生成特殊地址仍然使用此方法 func GenerateAccount(args ...string) (string, error) { var seed Hash var err error @@ -43,11 +55,22 @@ func GenerateAccount(args ...string) (string, error) { sequenceZero := uint32(0) account, _ := AccountId(key, &sequenceZero) publicKey, _ := AccountPublicKey(key, &sequenceZero) + pk, err := key.PK(&sequenceZero) + if err != nil { + return "", err + } + + pub, err := key.PUB(&sequenceZero) + if err != nil { + return "", err + } generated := Account{ - Address: account.String(), - PublicKey: publicKey.String(), - PublicKeyHex: fmt.Sprintf("%X", key.Public(&sequenceZero)), - PrivateKey: seed.String(), + Address: account.String(), + PublicKeyBase58: publicKey.String(), + PublicKeyHex: fmt.Sprintf("%X", key.Public(&sequenceZero)), + PrivateSeed: seed.String(), + PrivateKey: pk, + PublicKey: pub, } jsonStr, err := json.Marshal(generated) if err != nil { @@ -56,8 +79,72 @@ func GenerateAccount(args ...string) (string, error) { return string(jsonStr), nil } -func ValidationCreate() (string, error) { - generated := Account{} +func GenerateAddress(options string) (string, error) { + var seed *Seed + var err error + var key Key + if strings.Contains(options, "secret") && !strings.Contains(options, "algorithm") { + return "", fmt.Errorf("Invalid parameter") + } + seed, err = GenerateSeed(options) + if err != nil { + return "", err + } + sVersion := seed.version + switch sVersion { + case common.Ed25519: + key, err = NewEd25519Key(seed.SeedHash.Payload()) + break + case common.SoftGMAlg: + // key, err = GenerateKeyPair(seed) + break + case common.ECDSA: + key, err = NewECDSAKey(seed.SeedHash.Payload()) + break + default: + key, err = NewECDSAKey(seed.SeedHash.Payload()) + } + if err != nil { + return "", err + } + + sequenceZero := uint32(0) + account, err := AccountId(key, &sequenceZero) + if err != nil { + return "", err + } + publicKey, err := AccountPublicKey(key, &sequenceZero) + if err != nil { + return "", err + } + var privSeed Hash + if sVersion == common.SoftGMAlg { + privSeed, err = AccountPrivateKey(key, &sequenceZero) + if err != nil { + return "", err + } + } else { + privSeed = seed.SeedHash + } + pk, err := key.PK(&sequenceZero) + if err != nil { + return "", err + } + + pub, err := key.PUB(&sequenceZero) + if err != nil { + return "", err + } + + generated := Account{ + Address: account.String(), + PublicKeyBase58: publicKey.String(), + PublicKeyHex: fmt.Sprintf("%X", key.Public(&sequenceZero)), + PrivateSeed: privSeed.String(), + PrivateKey: pk, + PublicKey: pub, + } + jsonStr, err := json.Marshal(generated) if err != nil { return "", err @@ -65,8 +152,115 @@ func ValidationCreate() (string, error) { return string(jsonStr), nil } -func GetAccountInfo(address string) (string, error) { - generated := Account{} +func GenerateAddressObj(options string) (*Account, error) { + var seed *Seed + var err error + var key Key + if strings.Contains(options, "secret") && !strings.Contains(options, "algorithm") { + return nil, fmt.Errorf("Invalid parameter") + } + seed, err = GenerateSeed(options) + if err != nil { + return nil, err + } + sVersion := seed.version + sequenceZero := uint32(0) + switch sVersion { + case common.Ed25519: + key, err = NewEd25519Key(seed.SeedHash.Payload()) + break + case common.SoftGMAlg: + // key, err = GenerateKeyPair(seed) + break + case common.ECDSA: + key1 := &ecdsaKey{} + key1, err = NewECDSAKey(seed.SeedHash.Payload()) + key = key1.GenerateEcdsaKey(sequenceZero) + break + default: + key, err = NewECDSAKey(seed.SeedHash.Payload()) + } + + if err != nil { + return nil, err + } + + account, err := AccountId(key, nil) + if err != nil { + return nil, err + } + publicKey, err := AccountPublicKey(key, nil) + if err != nil { + return nil, err + } + var privSeed Hash + if sVersion == common.SoftGMAlg { + privSeed, err = AccountPrivateKey(key, nil) + if err != nil { + return nil, err + } + } else { + privSeed = seed.SeedHash + } + pk, err := key.PK(nil) + if err != nil { + return nil, err + } + + pub, err := key.PUB(nil) + if err != nil { + return nil, err + } + + return &Account{ + Address: account.String(), + PublicKeyBase58: publicKey.String(), + PublicKeyHex: fmt.Sprintf("%X", key.Public(nil)), + PrivateSeed: privSeed.String(), + PrivateKey: pk, + PublicKey: pub, + }, nil +} + +func CreateContractAddr(ctrOwnerAddr string, sequence uint32) (string, error) { + deBase58Addr, error := newHashFromString(ctrOwnerAddr) + if error != nil { + return "", error + } + + seqByte := make([]byte, 4) + binary.BigEndian.PutUint32(seqByte, sequence) + finalByte := append(deBase58Addr.Payload(), seqByte...) + + contractAddr, error := NewAccountId(Sha256RipeMD160(finalByte)) + if error != nil { + return "", error + } + return contractAddr.String(), nil +} + +func ValidationCreate() (string, error) { + var seed Hash + var err error + var key *ecdsaKey + rndBytes := make([]byte, 16) + if _, err := rand.Read(rndBytes); err != nil { + return "", err + } + seed, err = GenerateFamilySeed(string(rndBytes)) + if err != nil { + return "", err + } + key, err = NewECDSAKey(seed.Payload()) + if err != nil { + log.Println(err) + return "", err + } + publicKey, _ := NodePublicKey(key) + generated := SeedKey{ + Seed: seed.String(), + PublicKey: publicKey.String(), + } jsonStr, err := json.Marshal(generated) if err != nil { return "", err diff --git a/crypto/base58.go b/crypto/base58.go index 4fbe1da..c96999c 100644 --- a/crypto/base58.go +++ b/crypto/base58.go @@ -1,86 +1,86 @@ -package crypto - -import ( - "bytes" - "fmt" - "math/big" - "strings" -) - -// Purloined from https://github.com/conformal/btcutil/ - -var bigRadix = big.NewInt(58) -var bigZero = big.NewInt(0) - -// Base58Decode decodes a modified base58 string to a byte slice and checks checksum. -func Base58Decode(b, alphabet string) ([]byte, error) { - if len(b) < 5 { - return nil, fmt.Errorf("Base58 string too short: %s", b) - } - answer := big.NewInt(0) - j := big.NewInt(1) - - for i := len(b) - 1; i >= 0; i-- { - tmp := strings.IndexAny(alphabet, string(b[i])) - if tmp == -1 { - return nil, fmt.Errorf("Bad Base58 string: %s", b) - } - idx := big.NewInt(int64(tmp)) - tmp1 := big.NewInt(0) - tmp1.Mul(j, idx) - - answer.Add(answer, tmp1) - j.Mul(j, bigRadix) - } - - tmpval := answer.Bytes() - - var numZeros int - for numZeros = 0; numZeros < len(b); numZeros++ { - if b[numZeros] != alphabet[0] { - break - } - } - flen := numZeros + len(tmpval) - val := make([]byte, flen, flen) - copy(val[numZeros:], tmpval) - - // Check checksum - checksum := DoubleSha256(val[0 : len(val)-4]) - expected := val[len(val)-4:] - if !bytes.Equal(checksum[0:4], expected) { - return nil, fmt.Errorf("Bad Base58 checksum: %v expected %v", checksum, expected) - } - return val, nil -} - -// Base58Encode encodes a byte slice to a modified base58 string. -func Base58Encode(b []byte, alphabet string) string { - checksum := DoubleSha256(b) - b = append(b, checksum[0:4]...) - x := new(big.Int) - x.SetBytes(b) - - answer := make([]byte, 0) - for x.Cmp(bigZero) > 0 { - mod := new(big.Int) - x.DivMod(x, bigRadix, mod) - answer = append(answer, alphabet[mod.Int64()]) - } - - // leading zero bytes - for _, i := range b { - if i != 0 { - break - } - answer = append(answer, alphabet[0]) - } - - // reverse - alen := len(answer) - for i := 0; i < alen/2; i++ { - answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] - } - - return string(answer) -} +package crypto + +import ( + "bytes" + "fmt" + "math/big" + "strings" +) + +// Purloined from https://github.com/conformal/btcutil/ + +var bigRadix = big.NewInt(58) +var bigZero = big.NewInt(0) + +// Base58Decode decodes a modified base58 string to a byte slice and checks checksum. +func Base58Decode(b, alphabet string) ([]byte, error) { + if len(b) < 5 { + return nil, fmt.Errorf("Base58 string too short: %s", b) + } + answer := big.NewInt(0) + j := big.NewInt(1) + + for i := len(b) - 1; i >= 0; i-- { + tmp := strings.IndexAny(alphabet, string(b[i])) + if tmp == -1 { + return nil, fmt.Errorf("Bad Base58 string: %s", b) + } + idx := big.NewInt(int64(tmp)) + tmp1 := big.NewInt(0) + tmp1.Mul(j, idx) + + answer.Add(answer, tmp1) + j.Mul(j, bigRadix) + } + + tmpval := answer.Bytes() + + var numZeros int + for numZeros = 0; numZeros < len(b); numZeros++ { + if b[numZeros] != alphabet[0] { + break + } + } + flen := numZeros + len(tmpval) + val := make([]byte, flen, flen) + copy(val[numZeros:], tmpval) + + // Check checksum + checksum := DoubleSha256(val[0 : len(val)-4]) + expected := val[len(val)-4:] + if !bytes.Equal(checksum[0:4], expected) { + return nil, fmt.Errorf("Bad Base58 checksum: %v expected %v", checksum, expected) + } + return val, nil +} + +// Base58Encode encodes a byte slice to a modified base58 string. +func Base58Encode(b []byte, alphabet string) string { + checksum := DoubleSha256(b) + b = append(b, checksum[0:4]...) + x := new(big.Int) + x.SetBytes(b) + + answer := make([]byte, 0) + for x.Cmp(bigZero) > 0 { + mod := new(big.Int) + x.DivMod(x, bigRadix, mod) + answer = append(answer, alphabet[mod.Int64()]) + } + + // leading zero bytes + for _, i := range b { + if i != 0 { + break + } + answer = append(answer, alphabet[0]) + } + + // reverse + alen := len(answer) + for i := 0; i < alen/2; i++ { + answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] + } + + return string(answer) +} diff --git a/crypto/const.go b/crypto/const.go index 12ebcb1..ad5db1d 100644 --- a/crypto/const.go +++ b/crypto/const.go @@ -21,6 +21,13 @@ const ( RIPPLE_ACCOUNT_PUBLIC HashVersion = 35 ) +// const ( +// ED25519 = 0 +// K256 = 1 +// SM = 3 +// SOFT_SM = 4 +// ) + var hashTypes = [...]struct { Description string Prefix byte @@ -31,6 +38,6 @@ var hashTypes = [...]struct { RIPPLE_NODE_PUBLIC: {"Validation public key for node.", 'n', 33, 53}, RIPPLE_NODE_PRIVATE: {"Validation private key for node.", 'p', 32, 52}, RIPPLE_FAMILY_SEED: {"Family seed.", 's', 16, 29}, - RIPPLE_ACCOUNT_PRIVATE: {"Account private key.", 'p', 32, 52}, - RIPPLE_ACCOUNT_PUBLIC: {"Account public key.", 'a', 33, 53}, + RIPPLE_ACCOUNT_PRIVATE: {"Account private key.", 'p', 65, 52}, + RIPPLE_ACCOUNT_PUBLIC: {"Account public key.", 'a', 65, 53}, } diff --git a/crypto/ecdsa.go b/crypto/ecdsa.go index 0ccb0b4..eecf2ce 100644 --- a/crypto/ecdsa.go +++ b/crypto/ecdsa.go @@ -5,6 +5,8 @@ import ( "encoding/binary" "math/big" + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/btcsuite/btcd/btcec" ) @@ -21,9 +23,21 @@ type ecdsaKey struct { func newKey(seed []byte) *btcec.PrivateKey { inc := big.NewInt(0).SetBytes(seed) inc.Lsh(inc, 32) + accLen := len(seed) + 4 for key := big.NewInt(0); ; inc.Add(inc, one) { - key.SetBytes(Sha512Half(inc.Bytes())) + incBytes := inc.Bytes() + + paddLen := accLen - len(incBytes) + finalBytes := make([]byte, accLen) + if paddLen != 0 { + tempBytes := make([]byte, paddLen) + finalBytes = append(tempBytes[:], incBytes[:]...) + } else { + finalBytes = incBytes + } + key.SetBytes(Sha512Half(finalBytes)) if key.Cmp(zero) > 0 && key.Cmp(order) < 0 { + // privKey, _ := btcec.PrivKeyFromBytes(elliptic.S256(), key.Bytes()) privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key.Bytes()) return privKey } @@ -41,7 +55,7 @@ func NewECDSAKey(seed []byte) (*ecdsaKey, error) { return &ecdsaKey{newKey(seed)}, nil } -func (k *ecdsaKey) generateKey(sequence uint32) *btcec.PrivateKey { +func (k *ecdsaKey) GenerateKey(sequence uint32) *btcec.PrivateKey { seed := make([]byte, btcec.PubKeyBytesLenCompressed+4) copy(seed, k.PubKey().SerializeCompressed()) binary.BigEndian.PutUint32(seed[btcec.PubKeyBytesLenCompressed:], sequence) @@ -51,6 +65,16 @@ func (k *ecdsaKey) generateKey(sequence uint32) *btcec.PrivateKey { return key } +func (k *ecdsaKey) GenerateEcdsaKey(sequence uint32) *ecdsaKey { + seed := make([]byte, btcec.PubKeyBytesLenCompressed+4) + copy(seed, k.PubKey().SerializeCompressed()) + binary.BigEndian.PutUint32(seed[btcec.PubKeyBytesLenCompressed:], sequence) + key := newKey(seed) + key.D.Add(key.D, k.D).Mod(key.D, order) + key.X, key.Y = key.ScalarBaseMult(key.D.Bytes()) + return &ecdsaKey{key} +} + func (k *ecdsaKey) Id(sequence *uint32) []byte { if sequence == nil { return Sha256RipeMD160(k.PubKey().SerializeCompressed()) @@ -62,14 +86,33 @@ func (k *ecdsaKey) Private(sequence *uint32) []byte { if sequence == nil { return k.D.Bytes() } - return k.generateKey(*sequence).D.Bytes() + return k.GenerateKey(*sequence).D.Bytes() +} + +func (k *ecdsaKey) PK(sequence *uint32) (interface{}, error) { + //if sequence == nil { + // return k.PrivateKey, nil + //} + //return k.GenerateKey(*sequence), nil + return k.PrivateKey.ToECDSA(), nil } func (k *ecdsaKey) Public(sequence *uint32) []byte { if sequence == nil { return k.PubKey().SerializeCompressed() } - return k.generateKey(*sequence).PubKey().SerializeCompressed() + + return k.GenerateKey(*sequence).PubKey().SerializeCompressed() } +func (k *ecdsaKey) PUB(sequence *uint32) (interface{}, error) { + //if sequence == nil { + // return k.PublicKey, nil + //} + //return k.GenerateKey(*sequence).PublicKey, nil + return k.PublicKey, nil +} +func (k *ecdsaKey) Type() common.KeyType { + return common.ECDSA +} diff --git a/crypto/ed25519.go b/crypto/ed25519.go index 7e31549..bc0aada 100644 --- a/crypto/ed25519.go +++ b/crypto/ed25519.go @@ -4,15 +4,18 @@ import ( "bytes" "crypto/ed25519" "crypto/rand" + "fmt" + "github.com/ChainSQL/go-chainsql-api/common" ) type ed25519key struct { priv ed25519.PrivateKey + pub ed25519.PublicKey } func checkSequenceIsNil(seq *uint32) { if seq != nil { - panic("Ed25519 keys do not support account families") + fmt.Errorf("Ed25519 keys do not support account families") } } @@ -26,19 +29,36 @@ func (e *ed25519key) Public(seq *uint32) []byte { return append([]byte{0xED}, e.priv[32:]...) } +func (e *ed25519key) PUB(seq *uint32) (interface{}, error) { + //checkSequenceIsNil(seq) + //return append([]byte{0xED}, e.priv[32:]...), nil + return e.pub, nil +} + func (e *ed25519key) Private(seq *uint32) []byte { checkSequenceIsNil(seq) return e.priv[:] } +func (e *ed25519key) PK(seq *uint32) (interface{}, error) { + //checkSequenceIsNil(seq) + //return e.priv[:], nil + return e.priv, nil +} + +func (k *ed25519key) Type() common.KeyType { + return common.Ed25519 +} + func NewEd25519Key(seed []byte) (*ed25519key, error) { r := rand.Reader if seed != nil { r = bytes.NewReader(Sha512Half(seed)) } - _, priv, err := ed25519.GenerateKey(r) + pub, priv, err := ed25519.GenerateKey(r) if err != nil { return nil, err } - return &ed25519key{priv: priv}, nil + key := &ed25519key{priv: priv, pub: pub} + return key, nil } diff --git a/crypto/hash.go b/crypto/hash.go index 392e317..b049d09 100644 --- a/crypto/hash.go +++ b/crypto/hash.go @@ -8,7 +8,7 @@ import ( // First byte is the network // Second byte is the version // Remaining bytes are the payload -type hash []byte +type hashType []byte func NewRippleHash(s string) (Hash, error) { // Special case which will deal short addresses @@ -36,6 +36,17 @@ func NewRippleHashCheck(s string, version HashVersion) (Hash, error) { return hash, nil } +// Checks hash matches expected version +func NewRippleSeed(s string, version HashVersion) (*Seed, error) { + keySeed := &Seed{} + hash, err := NewRippleHashCheck(s, version) + if err != nil { + return nil, err + } + keySeed.SeedHash = hash + return keySeed, nil +} + func NewAccountId(b []byte) (Hash, error) { return newHash(b, RIPPLE_ACCOUNT_ID) } @@ -89,7 +100,7 @@ func newHash(b []byte, version HashVersion) (Hash, error) { if len(b) > n { return nil, fmt.Errorf("Hash is wrong size, expected: %d got: %d", n, len(b)) } - return append(hash{byte(version)}, b...), nil + return append(hashType{byte(version)}, b...), nil } func newHashFromString(s string) (Hash, error) { @@ -97,24 +108,24 @@ func newHashFromString(s string) (Hash, error) { if err != nil { return nil, err } - return hash(decoded[:len(decoded)-4]), nil + return hashType(decoded[:len(decoded)-4]), nil } -func (h hash) String() string { - b := append(hash{byte(h.Version())}, h.Payload()...) +func (h hashType) String() string { + b := append(hashType{byte(h.Version())}, h.Payload()...) return Base58Encode(b, ALPHABET) } -func (h hash) Version() HashVersion { +func (h hashType) Version() HashVersion { return HashVersion(h[0]) } -func (h hash) Payload() []byte { +func (h hashType) Payload() []byte { return h[1:] } // Return a slice of the payload with leading zeroes omitted -func (h hash) PayloadTrimmed() []byte { +func (h hashType) PayloadTrimmed() []byte { payload := h.Payload() for i := range payload { if payload[i] != 0 { @@ -124,16 +135,16 @@ func (h hash) PayloadTrimmed() []byte { return payload[len(payload)-1:] } -func (h hash) Value() *big.Int { +func (h hashType) Value() *big.Int { return big.NewInt(0).SetBytes(h.Payload()) } -func (h hash) MarshalText() ([]byte, error) { +func (h hashType) MarshalText() ([]byte, error) { return []byte(h.String()), nil } -func (h hash) Clone() Hash { - c := make(hash, len(h)) +func (h hashType) Clone() Hash { + c := make(hashType, len(h)) copy(c, h) return c } diff --git a/crypto/interface.go b/crypto/interface.go index b446935..396d49e 100644 --- a/crypto/interface.go +++ b/crypto/interface.go @@ -1,11 +1,18 @@ package crypto -import "math/big" +import ( + "github.com/ChainSQL/go-chainsql-api/common" + "math/big" +) type Key interface { Private(*uint32) []byte Id(*uint32) []byte Public(*uint32) []byte + Type() common.KeyType + PK(*uint32) (interface{}, error) + PUB(*uint32) (interface{}, error) + //Hasher() Hash } type Hash interface { diff --git a/crypto/key_test.go b/crypto/key_test.go index 84acafa..0f847c0 100644 --- a/crypto/key_test.go +++ b/crypto/key_test.go @@ -18,8 +18,9 @@ func checkHash(h Hash, err error) string { return h.String() } -func checkSignature(c *C, privateKey, publicKey, hash, msg []byte) bool { - sig, err := Sign(privateKey, hash, msg) +func checkSignature(c *C, key Key, publicKey, hash, msg []byte) bool { + sequenceZero := uint32(0) + sig, err := Sign(key, hash, &sequenceZero, msg) c.Assert(err, IsNil) ok, err := Verify(publicKey, hash, msg, sig) c.Assert(err, IsNil) @@ -76,11 +77,11 @@ func (s *KeySuite) TestRippledVectors(c *C) { msg := []byte("Hello, nurse!") hash := Sha512Half(msg) - c.Check(checkSignature(c, key.Private(nil), key.Public(nil), hash, msg), Equals, true) - c.Check(checkSignature(c, key.Private(&sequenceZero), key.Public(&sequenceZero), hash, msg), Equals, true) - c.Check(checkSignature(c, key.Private(&sequenceOne), key.Public(&sequenceOne), hash, msg), Equals, true) - c.Check(checkSignature(c, key.Private(&sequenceOne), key.Public(&sequenceZero), hash, msg), Equals, false) - c.Check(checkSignature(c, key.Private(&sequenceZero), key.Public(&sequenceOne), hash, msg), Equals, false) + c.Check(checkSignature(c, key, key.Public(nil), hash, msg), Equals, true) + c.Check(checkSignature(c, key, key.Public(&sequenceZero), hash, msg), Equals, true) + c.Check(checkSignature(c, key, key.Public(&sequenceOne), hash, msg), Equals, true) + c.Check(checkSignature(c, key, key.Public(&sequenceZero), hash, msg), Equals, false) + c.Check(checkSignature(c, key, key.Public(&sequenceOne), hash, msg), Equals, false) } @@ -102,8 +103,8 @@ func (s *KeySuite) TestEd25119(c *C) { msg := []byte("Hello, nurse!") hash := Sha512Half(msg) - c.Check(checkSignature(c, key.Private(nil), key.Public(nil), hash, msg), Equals, true) - c.Check(checkSignature(c, other.Private(nil), other.Public(nil), hash, msg), Equals, true) - c.Check(checkSignature(c, key.Private(nil), other.Public(nil), hash, msg), Equals, false) - c.Check(checkSignature(c, other.Private(nil), key.Public(nil), hash, msg), Equals, false) + c.Check(checkSignature(c, key, key.Public(nil), hash, msg), Equals, true) + c.Check(checkSignature(c, other, other.Public(nil), hash, msg), Equals, true) + c.Check(checkSignature(c, key, other.Public(nil), hash, msg), Equals, false) + c.Check(checkSignature(c, other, key.Public(nil), hash, msg), Equals, false) } diff --git a/crypto/seed.go b/crypto/seed.go new file mode 100644 index 0000000..3cc14c5 --- /dev/null +++ b/crypto/seed.go @@ -0,0 +1,75 @@ +package crypto + +import ( + "crypto/rand" + "strings" + + "github.com/ChainSQL/go-chainsql-api/common" + + "github.com/buger/jsonparser" +) + +type Seed struct { + SeedHash Hash + version common.KeyType +} + +func GenerateSeed(options string) (*Seed, error) { + var err error + sVersion, _ := jsonparser.GetString([]byte(options), "algorithm") + var version common.KeyType + seed := &Seed{} + switch sVersion { + case "ed25519": + version = common.Ed25519 + break + case "secp256k1": + version = common.ECDSA + break + case "softGMAlg": + version = common.SoftGMAlg + break + default: + version = common.ECDSA + } + seed.version = version + if version == common.SoftGMAlg { + if strings.Contains(options, "secret") { + secret, _ := (jsonparser.GetString([]byte(options), "secret")) + seed.SeedHash, err = newHashFromString(secret) + } else { + seed.SeedHash = nil + } + } else { + if strings.Contains(options, "secret") { + secret, _ := (jsonparser.GetString([]byte(options), "secret")) + seed.SeedHash, err = newHashFromString(secret) + } else { + rndBytes := make([]byte, 16) + if _, err := rand.Read(rndBytes); err != nil { + return nil, err + } + seed.SeedHash, err = GenerateFamilySeed(string(rndBytes)) + } + } + return seed, err +} + +func (s *Seed) GenerateKey(keyType common.KeyType) (Key, error) { + var ( + key Key + err error + ) + switch keyType { + case common.Ed25519: + key, err = NewEd25519Key(s.SeedHash.Payload()) + case common.ECDSA: + key, err = NewECDSAKey(s.SeedHash.Payload()) + case common.SoftGMAlg: + // key, err = GenerateKeyPairBySeed(s.SeedHash.Payload()) + } + if err != nil { + return nil, err + } + return key, nil +} diff --git a/crypto/signature.go b/crypto/signature.go index 61ff587..5ed2931 100644 --- a/crypto/signature.go +++ b/crypto/signature.go @@ -4,15 +4,18 @@ import ( "crypto/ed25519" "fmt" + "github.com/ChainSQL/go-chainsql-api/common" "github.com/btcsuite/btcd/btcec" ) -func Sign(privateKey, hash, msg []byte) ([]byte, error) { - switch len(privateKey) { - case ed25519.PrivateKeySize: - return signEd25519(privateKey, msg) - case btcec.PrivKeyBytesLen: - return signECDSA(privateKey, hash) +func Sign(key Key, hash []byte, sequence *uint32, msg []byte) ([]byte, error) { + switch key.Type() { + case common.Ed25519: + return signEd25519(key.Private(sequence), msg) + case common.ECDSA: + return signECDSA(key.Private(sequence), hash) + // case common.SoftGMAlg: + // return signSoftSM(key.Private(sequence), msg) default: return nil, fmt.Errorf("Unknown private key format") } @@ -48,6 +51,7 @@ func verifyEd25519(pubKey, signature, msg []byte) (bool, error) { // Returns DER encoded signature from input hash func signECDSA(privateKey, hash []byte) ([]byte, error) { + // priv, _ := btcec.PrivKeyFromBytes(elliptic.S256(), privateKey) priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), privateKey) sig, err := priv.Sign(hash) if err != nil { @@ -68,3 +72,16 @@ func verifyECDSA(pubKey, signature, hash []byte) (bool, error) { } return sig.Verify(hash, pk), nil } + +// func signSoftSM(privateKey, msg []byte) ([]byte, error) { +// keyPair, err := PrivKeyFromBytes(privateKey) +// if err != nil { +// return nil, err +// } +// sign, err := SmSign(keyPair, msg) +// if err != nil { +// return nil, err +// } +// signByte, err := hex.DecodeString(sign) // 转码 +// return signByte, nil +// } diff --git a/crypto/util.go b/crypto/util.go index 6914f46..65e8918 100644 --- a/crypto/util.go +++ b/crypto/util.go @@ -1,55 +1,90 @@ -package crypto - -import ( - "crypto/sha256" - "crypto/sha512" - "encoding/hex" - "fmt" - - "golang.org/x/crypto/ripemd160" -) - -// Write operations in a hash.Hash never return an error - -// Returns first 32 bytes of a SHA512 of the input bytes -func Sha512Half(b []byte) []byte { - hasher := sha512.New() - hasher.Write(b) - return hasher.Sum(nil)[:32] -} - -// Returns first 16 bytes of a SHA512 of the input bytes -func Sha512Quarter(b []byte) []byte { - hasher := sha512.New() - hasher.Write(b) - return hasher.Sum(nil)[:16] -} - -func DoubleSha256(b []byte) []byte { - hasher := sha256.New() - hasher.Write(b) - sha := hasher.Sum(nil) - hasher.Reset() - hasher.Write(sha) - return hasher.Sum(nil) -} - -func Sha256RipeMD160(b []byte) []byte { - ripe := ripemd160.New() - sha := sha256.New() - sha.Write(b) - ripe.Write(sha.Sum(nil)) - return ripe.Sum(nil) -} - -func H2B(s string) ([]byte, error) { - return hex.DecodeString(s) -} - -func B2H(b []byte) string { - return fmt.Sprintf("%X", b) -} - -func B2H32(b [32]byte) string { - return fmt.Sprintf("%X", b) -} +package crypto + +import ( + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "fmt" + "hash" + + "github.com/ChainSQL/go-chainsql-api/common" + "golang.org/x/crypto/ripemd160" + "golang.org/x/crypto/sha3" +) + +// Write operations in a hash.Hash never return an error + +// Returns first 32 bytes of a SHA512 of the input bytes +func Sha512Half(b []byte) []byte { + hasher := sha512.New() + hasher.Write(b) + return hasher.Sum(nil)[:32] +} + +// Returns first 16 bytes of a SHA512 of the input bytes +func Sha512Quarter(b []byte) []byte { + hasher := sha512.New() + hasher.Write(b) + return hasher.Sum(nil)[:16] +} + +func DoubleSha256(b []byte) []byte { + hasher := sha256.New() + hasher.Write(b) + sha := hasher.Sum(nil) + hasher.Reset() + hasher.Write(sha) + return hasher.Sum(nil) +} + +func Sha256RipeMD160(b []byte) []byte { + ripe := ripemd160.New() + sha := sha256.New() + sha.Write(b) + ripe.Write(sha.Sum(nil)) + return ripe.Sum(nil) +} + +func H2B(s string) ([]byte, error) { + return hex.DecodeString(s) +} + +func B2H(b []byte) string { + return fmt.Sprintf("%X", b) +} + +func B2H32(b [32]byte) string { + return fmt.Sprintf("%X", b) +} + +type KeccakState interface { + hash.Hash + Read([]byte) (int, error) +} + +// NewKeccakState creates a new KeccakState +func NewKeccakState() KeccakState { + return sha3.NewLegacyKeccak256().(KeccakState) +} + +// Keccak256 calculates and returns the Keccak256 hash of the input data. +func Keccak256(data ...[]byte) []byte { + b := make([]byte, 32) + d := NewKeccakState() + for _, b := range data { + d.Write(b) + } + d.Read(b) + return b +} + +// Keccak256Hash calculates and returns the Keccak256 hash of the input data, +// converting it to an internal Hash data structure. +func Keccak256Hash(data ...[]byte) (h common.Hash) { + d := NewKeccakState() + for _, b := range data { + d.Write(b) + } + d.Read(h[:]) + return h +} diff --git a/data/amount.go b/data/amount.go index cea36bd..df6279d 100644 --- a/data/amount.go +++ b/data/amount.go @@ -252,7 +252,7 @@ func NewExchangeRate(a, b *Amount) (ExchangeRate, error) { return 0, nil } if rate.offset >= -100 || rate.offset <= 155 { - panic("Impossible Rate") + return 0, fmt.Errorf("Impossible Rate") } return ExchangeRate(uint64(rate.offset+100)<<54 | uint64(rate.num)), nil } diff --git a/data/decoder.go b/data/decoder.go index e31ae47..8a08d27 100644 --- a/data/decoder.go +++ b/data/decoder.go @@ -3,6 +3,7 @@ package data import ( "errors" "fmt" + "github.com/ChainSQL/go-chainsql-api/common" "reflect" ) @@ -81,7 +82,7 @@ func ReadTransaction(r Reader) (Transaction, error) { // ReadTransactionAndMetadata combines the inputs from the two // readers into a TransactionWithMetaData -func ReadTransactionAndMetadata(tx, meta Reader, hash Hash256, ledger uint32) (*TransactionWithMetaData, error) { +func ReadTransactionAndMetadata(tx, meta Reader, hash Hash256, ledger uint32, keyType common.KeyType) (*TransactionWithMetaData, error) { t, err := ReadTransaction(tx) if err != nil { return nil, err @@ -95,7 +96,7 @@ func ReadTransactionAndMetadata(tx, meta Reader, hash Hash256, ledger uint32) (* return nil, err } *txm.GetHash() = hash - if txm.Id, err = NodeId(txm); err != nil { + if txm.Id, err = NodeId(txm, keyType); err != nil { return nil, err } return txm, nil diff --git a/data/encoder.go b/data/encoder.go index a020581..2b53ee1 100644 --- a/data/encoder.go +++ b/data/encoder.go @@ -4,47 +4,55 @@ import ( "bytes" "crypto/sha512" "fmt" + "hash" "io" "reflect" "sort" "strings" + + "github.com/ChainSQL/go-chainsql-api/common" ) -func Raw(h Hashable) (Hash256, []byte, error) { - return raw(h, h.Prefix(), false) +func Raw(h Hashable, keyType common.KeyType) (Hash256, []byte, error) { + return raw(h, h.Prefix(), false, keyType) } -func NodeId(h Hashable) (Hash256, error) { - nodeid, _, err := raw(h, h.Prefix(), false) +func NodeId(h Hashable, keyType common.KeyType) (Hash256, error) { + nodeid, _, err := raw(h, h.Prefix(), false, keyType) return nodeid, err } -func SigningHash(s Signer) (Hash256, []byte, error) { - return raw(s, s.SigningPrefix(), true) +func SigningHash(s Signer, keyType common.KeyType) (Hash256, []byte, error) { + return raw(s, s.SigningPrefix(), true, keyType) } -func Node(h Storer) (Hash256, []byte, error) { +func Node(h Storer, keyType common.KeyType) (Hash256, []byte, error) { var header bytes.Buffer for _, v := range []interface{}{h.Ledger(), h.Ledger(), h.NodeType(), h.Prefix()} { if err := write(&header, v); err != nil { return zero256, nil, err } } - key, value, err := raw(h, h.Prefix(), true) + key, value, err := raw(h, h.Prefix(), true, keyType) if err != nil { return zero256, nil, err } return key, append(header.Bytes(), value...), nil } -func raw(value interface{}, prefix HashPrefix, ignoreSigningFields bool) (Hash256, []byte, error) { +func raw(value interface{}, prefix HashPrefix, ignoreSigningFields bool, keyType common.KeyType) (Hash256, []byte, error) { buf := new(bytes.Buffer) - hasher := sha512.New() + var hasher hash.Hash + if keyType == common.SoftGMAlg { + // hasher = sm3.New() + } else { + hasher = sha512.New() + } multi := io.MultiWriter(buf, hasher) if err := write(hasher, prefix); err != nil { return zero256, nil, err } - if err := writeRaw(multi, value, ignoreSigningFields); err != nil { + if err := writeRaw(multi, value, ignoreSigningFields, keyType); err != nil { return zero256, nil, err } var hash Hash256 @@ -53,7 +61,7 @@ func raw(value interface{}, prefix HashPrefix, ignoreSigningFields bool) (Hash25 } // Disgusting node format and ordering handled here -func writeRaw(w io.Writer, value interface{}, ignoreSigningFields bool) error { +func writeRaw(w io.Writer, value interface{}, ignoreSigningFields bool, keyType common.KeyType) error { switch v := value.(type) { case *Ledger: return write(w, v.LedgerHeader) @@ -68,7 +76,7 @@ func writeRaw(w io.Writer, value interface{}, ignoreSigningFields bool) error { return write(w, v) } case *TransactionWithMetaData: - txid, tx, err := Raw(v.Transaction) + txid, tx, err := Raw(v.Transaction, keyType) if err != nil { return err } @@ -107,8 +115,12 @@ func encode(w io.Writer, value interface{}, ignoreSigningFields bool) error { if ignoreSigningFields && e.SigningField() { return nil } - if err := writeEncoding(w, e); err != nil { - return err + // if err := writeEncoding(w, e); err != nil { + // return err + // } + err1 := writeEncoding(w, e) + if err1 != nil { + return err1 } var err error switch v2 := v.(type) { diff --git a/data/factory.go b/data/factory.go index cce0a10..a8b0ef8 100644 --- a/data/factory.go +++ b/data/factory.go @@ -43,6 +43,11 @@ const ( TRUST_SET TransactionType = 20 TABLE_LIST_SET TransactionType = 21 SQLSTATEMENT TransactionType = 22 + SQLTRANSACTION TransactionType = 23 + CONTRACT TransactionType = 24 + SCHEMA_CREATE TransactionType = 25 + SCHEMA_MODIFY TransactionType = 26 + SCHEMA_DELETE TransactionType = 28 ACCOUNT_DELETE TransactionType = 51 AMENDMENT TransactionType = 100 SET_FEE TransactionType = 101 @@ -88,6 +93,9 @@ var TxFactory = [...]func() Transaction{ CHECK_CREATE: func() Transaction { return &CheckCreate{TxBase: TxBase{TransactionType: CHECK_CREATE}} }, CHECK_CASH: func() Transaction { return &CheckCash{TxBase: TxBase{TransactionType: CHECK_CASH}} }, CHECK_CANCEL: func() Transaction { return &CheckCancel{TxBase: TxBase{TransactionType: CHECK_CANCEL}} }, + SCHEMA_CREATE: func() Transaction { return &SchemaCreate{TxBase: TxBase{TransactionType: SCHEMA_CREATE}} }, + SCHEMA_MODIFY: func() Transaction { return &SchemaModify{TxBase: TxBase{TransactionType: SCHEMA_MODIFY}} }, + SCHEMA_DELETE: func() Transaction { return &SchemaDelete{TxBase: TxBase{TransactionType: SCHEMA_DELETE}} }, } var ledgerEntryNames = [...]string{ @@ -144,6 +152,11 @@ var txNames = [...]string{ CHECK_CANCEL: "CheckCancel", TABLE_LIST_SET: "TableListSet", SQLSTATEMENT: "SQLStatement", + SQLTRANSACTION: "SQLTransaction", + CONTRACT: "Contract", + SCHEMA_CREATE: "SchemaCreate", + SCHEMA_MODIFY: "SchemaModify", + SCHEMA_DELETE: "SchemaDelete", } var txTypes = map[string]TransactionType{ @@ -168,6 +181,11 @@ var txTypes = map[string]TransactionType{ "CheckCancel": CHECK_CANCEL, "TableListSet": TABLE_LIST_SET, "SQLStatement": SQLSTATEMENT, + "SQLTransaction": SQLTRANSACTION, + "Contract": CONTRACT, + "SchemaCreate": SCHEMA_CREATE, + "SchemaModify": SCHEMA_MODIFY, + "SchemaDelete": SCHEMA_DELETE, } var HashableTypes []string diff --git a/data/format.go b/data/format.go index ace0a26..b7a45f5 100644 --- a/data/format.go +++ b/data/format.go @@ -95,6 +95,7 @@ var encodings = map[enc]string{ // 16-bit unsigned integers (uncommon) enc{ST_UINT16, 16}: "Version", enc{ST_UINT16, 50}: "OpType", + enc{ST_UINT16, 51}: "ContractOpType", // 32-bit unsigned integers (common) enc{ST_UINT32, 2}: "Flags", enc{ST_UINT32, 3}: "SourceTag", @@ -134,6 +135,7 @@ var encodings = map[enc]string{ enc{ST_UINT32, 37}: "FinishAfter", enc{ST_UINT32, 38}: "SignerListID", enc{ST_UINT32, 39}: "SettleDelay", + enc{ST_UINT32, 55}: "Gas", // 64-bit unsigned integers (common) enc{ST_UINT64, 1}: "IndexNext", enc{ST_UINT64, 2}: "IndexPrevious", @@ -166,6 +168,8 @@ var encodings = map[enc]string{ enc{ST_HASH256, 21}: "Digest", enc{ST_HASH256, 22}: "Channel", enc{ST_HASH256, 24}: "CheckID", + enc{ST_HASH256, 58}: "AnchorLedgerHash", + enc{ST_HASH256, 59}: "SchemaID", // currency amount (common) enc{ST_AMOUNT, 1}: "Amount", enc{ST_AMOUNT, 2}: "Balance", @@ -181,6 +185,7 @@ var encodings = map[enc]string{ enc{ST_AMOUNT, 16}: "MinimumOffer", enc{ST_AMOUNT, 17}: "RippleEscrow", enc{ST_AMOUNT, 18}: "DeliveredAmount", + enc{ST_AMOUNT, 19}: "ContractValue", // variable length (common) enc{ST_VL, 1}: "PublicKey", enc{ST_VL, 2}: "MessageKey", @@ -203,16 +208,25 @@ var encodings = map[enc]string{ enc{ST_VL, 51}: "TableName", enc{ST_VL, 52}: "Raw", enc{ST_VL, 54}: "AutoFillField", + enc{ST_VL, 64}: "ContractCode", + enc{ST_VL, 65}: "ContractData", + enc{ST_VL, 66}: "ContractTxs", + enc{ST_VL, 67}: "ContractLogs", + enc{ST_VL, 68}: "SchemaName", + enc{ST_VL, 69}: "Endpoint", + enc{ST_VL, 75}: "ContractDetailMsg", // account - enc{ST_ACCOUNT, 1}: "Account", - enc{ST_ACCOUNT, 2}: "Owner", - enc{ST_ACCOUNT, 3}: "Destination", - enc{ST_ACCOUNT, 4}: "Issuer", - enc{ST_ACCOUNT, 5}: "Authorize", - enc{ST_ACCOUNT, 6}: "Unauthorize", - enc{ST_ACCOUNT, 7}: "Target", - enc{ST_ACCOUNT, 8}: "RegularKey", - + enc{ST_ACCOUNT, 1}: "Account", + enc{ST_ACCOUNT, 2}: "Owner", + enc{ST_ACCOUNT, 3}: "Destination", + enc{ST_ACCOUNT, 4}: "Issuer", + enc{ST_ACCOUNT, 5}: "Authorize", + enc{ST_ACCOUNT, 6}: "Unauthorize", + enc{ST_ACCOUNT, 7}: "Target", + enc{ST_ACCOUNT, 8}: "RegularKey", + enc{ST_ACCOUNT, 51}: "OriginalAddress", + enc{ST_ACCOUNT, 52}: "ContractAddress", + enc{ST_ACCOUNT, 53}: "SchemaAdmin", // inner object enc{ST_OBJECT, 1}: "EndOfObject", enc{ST_OBJECT, 2}: "TransactionMetaData", @@ -229,6 +243,9 @@ var encodings = map[enc]string{ enc{ST_OBJECT, 16}: "Signer", enc{ST_OBJECT, 18}: "Majority", enc{ST_OBJECT, 50}: "Table", + enc{ST_OBJECT, 52}: "Validator", + enc{ST_OBJECT, 53}: "Peer", + // array of objects enc{ST_ARRAY, 1}: "EndOfArray", enc{ST_ARRAY, 2}: "SigningAccounts", @@ -242,12 +259,15 @@ var encodings = map[enc]string{ enc{ST_ARRAY, 51}: "Tables", // array of objects (uncommon) enc{ST_ARRAY, 16}: "Majorities", + enc{ST_ARRAY, 53}: "Validators", + enc{ST_ARRAY, 54}: "PeerList", // 8-bit unsigned integers (common) enc{ST_UINT8, 1}: "CloseResolution", enc{ST_UINT8, 2}: "Method", enc{ST_UINT8, 3}: "TransactionResult", // 8-bit unsigned integers (uncommon) enc{ST_UINT8, 16}: "TickSize", + enc{ST_UINT8, 28}: "SchemaStrategy", // 160-bit (common) enc{ST_HASH160, 1}: "TakerPaysCurrency", enc{ST_HASH160, 2}: "TakerPaysIssuer", diff --git a/data/hash.go b/data/hash.go index 8f3e7af..d7850c8 100644 --- a/data/hash.go +++ b/data/hash.go @@ -4,43 +4,43 @@ import ( "bytes" "encoding/hex" "fmt" + "regexp" "strings" + "github.com/ChainSQL/go-chainsql-api/common" "github.com/ChainSQL/go-chainsql-api/crypto" ) -type KeyType int - const ( - ECDSA KeyType = 0 - Ed25519 KeyType = 1 + PUBKEY_LENGTH_GM int = 65 + PUBKEYL_ENGTH_COMMON int = 33 ) -func (keyType KeyType) String() string { - switch keyType { - case ECDSA: - return "ECDSA" - case Ed25519: - return "Ed25519" - default: - return "unknown key type" - } -} + type Hash128 [16]byte type Hash160 [20]byte type Hash256 [32]byte type Vector256 []Hash256 type VariableLength []byte -type PublicKey [33]byte + +//type PublicKey [33]byte +type PublicKey struct { + KeyStore [65]byte + KeyValue []byte + KeyType common.KeyType +} + type Account [20]byte type RegularKey [20]byte -type Seed [16]byte + +//type Seed [16]byte var zero256 Hash256 var zeroAccount Account -var zeroPublicKey PublicKey -var zeroSeed Seed + +//var zeroPublicKey PublicKey +//var zeroSeed Seed func (h *Hash128) Bytes() []byte { if h == nil { @@ -159,8 +159,17 @@ func (v *VariableLength) Bytes() []byte { return []byte(nil) } +func (p *PublicKey) SetKey(kType common.KeyType) { + p.KeyType = kType + if common.SoftGMAlg == kType { + p.KeyValue = p.KeyStore[:PUBKEY_LENGTH_GM] + } else { + p.KeyValue = p.KeyStore[:PUBKEYL_ENGTH_COMMON] + } + +} func (p PublicKey) NodePublicKey() string { - hash, err := crypto.NewNodePublicKey(p[:]) + hash, err := crypto.NewNodePublicKey(p.KeyValue[:]) if err != nil { return "Bad node public key" } @@ -173,12 +182,15 @@ func (p PublicKey) String() string { } func (p PublicKey) IsZero() bool { - return p == zeroPublicKey + if p.KeyValue == nil { + return true + } + return false } func (p *PublicKey) Bytes() []byte { if p != nil { - return p[:] + return p.KeyValue[:] } return []byte(nil) } @@ -266,63 +278,79 @@ func (r *RegularKey) Bytes() []byte { } // Expects address in base58 form -func NewSeedFromAddress(s string) (*Seed, error) { - hash, err := crypto.NewRippleHashCheck(s, crypto.RIPPLE_FAMILY_SEED) - if err != nil { - return nil, err - } - var seed Seed - copy(seed[:], hash.Payload()) - return &seed, nil -} - -func (s Seed) Hash() (crypto.Hash, error) { - return crypto.NewFamilySeed(s[:]) -} - -func (s Seed) String() string { - address, err := s.Hash() - if err != nil { - return fmt.Sprintf("Bad Address: %s", b2h(s[:])) - } - return address.String() -} - -func (s *Seed) Bytes() []byte { - if s != nil { - return s[:] - } - return []byte(nil) -} - -func (s *Seed) Key(keyType KeyType) crypto.Key { - var ( - key crypto.Key - err error - ) - switch keyType { - case Ed25519: - key, err = crypto.NewEd25519Key(s[:]) - case ECDSA: - key, err = crypto.NewECDSAKey(s[:]) - } - if err != nil { - panic(fmt.Sprintf("bad seed: %v", err)) - } - return key -} - -func (s *Seed) AccountId(keyType KeyType, sequence *uint32) Account { - var account Account - copy(account[:], s.Key(keyType).Id(sequence)) - return account -} +// func NewSeedFromAddress(s string, version crypto.HashVersion) (*Seed, error) { +// keySeed := &Seed{} +// hash, err := crypto.NewRippleHashCheck(s, version) +// if err != nil { +// return nil, err +// } +// keySeed.seedHash = hash +// return keySeed, nil +// } + +// func (s *Seed) Hash() (crypto.Hash, error) { +// return crypto.NewFamilySeed(s[:]) +// } + +// func (s Seed) String() string { +// address, err := s.Hash() +// if err != nil { +// return fmt.Sprintf("Bad Address: %s", b2h(s[:])) +// } +// return address.String() +// } + +// func (s *Seed) Bytes() []byte { +// if s != nil { +// return s[:] +// } +// return []byte(nil) +// } + +// func (s *Seed) Key(keyType KeyType) crypto.Key { +// var ( +// key crypto.Key +// err error +// ) +// switch keyType { +// case Ed25519: +// key, err = crypto.NewEd25519Key(s[:]) +// case ECDSA: +// key, err = crypto.NewECDSAKey(s[:]) +// case SoftGMAlg: +// key, err = crypto.GenerateKeyPairBySeed(s[:]) +// } +// if err != nil { +// panic(fmt.Sprintf("bad seed: %v", err)) +// } +// return key +// } + +// func (s *Seed) AccountId(keyType KeyType, sequence *uint32) Account { +// var account Account +// copy(account[:], s.Key(keyType).Id(sequence)) +// return account +// } func KeyFromSecret(secret string) (crypto.Key, error) { - seed, err := NewSeedFromAddress(secret) - if err != nil { - return nil, err + var version crypto.HashVersion + var err error + var seed *crypto.Seed + var regSoftGMSeed = "^[a-zA-Z1-9]{51,51}" + r := regexp.MustCompile(regSoftGMSeed) + if r.MatchString(secret) { + version = crypto.RIPPLE_ACCOUNT_PRIVATE + seed, err = crypto.NewRippleSeed(secret, version) + if err != nil { + return nil, err + } + return seed.GenerateKey(common.SoftGMAlg) + } else { + version = crypto.RIPPLE_FAMILY_SEED + seed, err := crypto.NewRippleSeed(secret, version) + if err != nil { + return nil, err + } + return seed.GenerateKey(common.ECDSA) } - - return seed.Key(ECDSA), nil } diff --git a/data/interface.go b/data/interface.go index 7f01d00..ff4cc3e 100644 --- a/data/interface.go +++ b/data/interface.go @@ -2,6 +2,8 @@ package data import ( "io" + + "github.com/ChainSQL/go-chainsql-api/common" ) type Hashable interface { @@ -12,15 +14,19 @@ type Hashable interface { type Signer interface { Hashable - InitialiseForSigning() + InitialiseForSigning(kType common.KeyType) SigningPrefix() HashPrefix GetPublicKey() *PublicKey GetSignature() *VariableLength + GetBlob() *VariableLength + SetTxBase(seq uint32, fee Value, astLedgerSequence *uint32, account Account) + GetRaw() string + GetStatements() string } type Router interface { Hashable - SuppressionId() Hash256 + SuppressionId(keyType common.KeyType) Hash256 } type Storer interface { diff --git a/data/json.go b/data/json.go index f9da62b..108bb95 100644 --- a/data/json.go +++ b/data/json.go @@ -464,23 +464,23 @@ func (r *RegularKey) UnmarshalText(b []byte) error { return nil } -func (s Seed) MarshalText() ([]byte, error) { - address, err := s.Hash() - if err != nil { - return nil, err - } - return address.MarshalText() -} +// func (s Seed) MarshalText() ([]byte, error) { +// address, err := s.Hash() +// if err != nil { +// return nil, err +// } +// return address.MarshalText() +// } // Expects base58-encoded account id -func (s *Seed) UnmarshalText(b []byte) error { - account, err := NewSeedFromAddress(string(b)) - if err != nil { - return err - } - copy(s[:], account[:]) - return nil -} +// func (s *Seed) UnmarshalText(b []byte, version crypto.HashVersion) error { +// account, err := NewSeedFromAddress(string(b), version) +// if err != nil { +// return err +// } +// copy(s[:], account[:]) +// return nil +// } func (v VariableLength) MarshalText() ([]byte, error) { return b2h(v), nil @@ -497,12 +497,12 @@ func (p PublicKey) MarshalText() ([]byte, error) { if p.IsZero() { return []byte{}, nil } - return b2h(p[:]), nil + return b2h(p.KeyValue[:]), nil } // Expects public key hex func (p *PublicKey) UnmarshalText(b []byte) error { - _, err := hex.Decode(p[:], b) + _, err := hex.Decode(p.KeyValue[:], b) return err } @@ -518,6 +518,4 @@ func (h *Uint64Hex) UnmarshalText(b []byte) error { return err } -func (keyType KeyType) MarshalText() ([]byte, error) { - return []byte(keyType.String()), nil -} + diff --git a/data/log.go b/data/log.go new file mode 100644 index 0000000..5899812 --- /dev/null +++ b/data/log.go @@ -0,0 +1,139 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package data + +import ( + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/common/hexutil" + // "github.com/ethereum/go-ethereum/rlp" +) + +//go:generate go run github.com/fjl/gencodec -type Log -field-override logMarshaling -out gen_log_json.go + +// Log represents a contract log event. These events are generated by the LOG opcode and +// stored/indexed by the node. +type Log struct { + // Consensus fields: + // address of the contract that generated the event + // Address common.Address `json:"address" gencodec:"required"` + Address string `json:"address" gencodec:"required"` + // list of topics provided by the contract. + Topics []common.Hash `json:"topics" gencodec:"required"` + // supplied by the contract, usually ABI-encoded + Data []byte `json:"data" gencodec:"required"` + + // Derived fields. These fields are filled in by the node + // but not secured by consensus. + // block in which the transaction was included + BlockNumber uint64 `json:"blockNumber"` + // hash of the transaction + TxHash common.Hash `json:"transactionHash" gencodec:"required"` + // index of the transaction in the block + TxIndex uint `json:"transactionIndex"` + // hash of the block in which the transaction was included + BlockHash common.Hash `json:"blockHash"` + // index of the log in the block + Index uint `json:"logIndex"` + + // The Removed field is true if this log was reverted due to a chain reorganisation. + // You must pay attention to this field if you receive logs through a filter query. + Removed bool `json:"removed"` +} + +type logMarshaling struct { + Data hexutil.Bytes + BlockNumber hexutil.Uint64 + TxIndex hexutil.Uint + Index hexutil.Uint +} + +//go:generate go run ../../rlp/rlpgen -type rlpLog -out gen_log_rlp.go + +type rlpLog struct { + Address common.Address + Topics []common.Hash + Data []byte +} + +// legacyRlpStorageLog is the previous storage encoding of a log including some redundant fields. +type legacyRlpStorageLog struct { + Address common.Address + Topics []common.Hash + Data []byte + BlockNumber uint64 + TxHash common.Hash + TxIndex uint + BlockHash common.Hash + Index uint +} + +// // EncodeRLP implements rlp.Encoder. +// func (l *Log) EncodeRLP(w io.Writer) error { +// rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data} +// return rlp.Encode(w, &rl) +// } + +// // DecodeRLP implements rlp.Decoder. +// func (l *Log) DecodeRLP(s *rlp.Stream) error { +// var dec rlpLog +// err := s.Decode(&dec) +// if err == nil { +// l.Address, l.Topics, l.Data = dec.Address, dec.Topics, dec.Data +// } +// return err +// } + +// // LogForStorage is a wrapper around a Log that handles +// // backward compatibility with prior storage formats. +// type LogForStorage Log + +// // EncodeRLP implements rlp.Encoder. +// func (l *LogForStorage) EncodeRLP(w io.Writer) error { +// rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data} +// return rlp.Encode(w, &rl) +// } + +// // DecodeRLP implements rlp.Decoder. +// // +// // Note some redundant fields(e.g. block number, tx hash etc) will be assembled later. +// func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error { +// blob, err := s.Raw() +// if err != nil { +// return err +// } +// var dec rlpLog +// err = rlp.DecodeBytes(blob, &dec) +// if err == nil { +// *l = LogForStorage{ +// Address: dec.Address, +// Topics: dec.Topics, +// Data: dec.Data, +// } +// } else { +// // Try to decode log with previous definition. +// var dec legacyRlpStorageLog +// err = rlp.DecodeBytes(blob, &dec) +// if err == nil { +// *l = LogForStorage{ +// Address: dec.Address, +// Topics: dec.Topics, +// Data: dec.Data, +// } +// } +// } +// return err +// } diff --git a/data/metadata.go b/data/metadata.go index 27b4e82..29b91bd 100644 --- a/data/metadata.go +++ b/data/metadata.go @@ -65,13 +65,17 @@ func (t *TransactionWithMetaData) NodeType() NodeType { return NT_TRANSACTION_NO func (t *TransactionWithMetaData) Ledger() uint32 { return t.LedgerSequence } func (t *TransactionWithMetaData) NodeId() *Hash256 { return &t.Id } -func (t *TransactionWithMetaData) Affects(account Account) bool { +func (t *TransactionWithMetaData) Affects(account Account) (bool, error) { for _, effect := range t.MetaData.AffectedNodes { - if _, final, _, _ := effect.AffectedNode(); final.Affects(account) { - return true + _, final, _, _ , err:= effect.AffectedNode() + if err != nil{ + return false,err + } + if final.Affects(account) { + return true, nil } } - return false + return false, nil } func NewTransactionWithMetadata(typ TransactionType) *TransactionWithMetaData { @@ -80,7 +84,7 @@ func NewTransactionWithMetadata(typ TransactionType) *TransactionWithMetaData { // AffectedNode returns the AffectedNode, the current LedgerEntry, // the previous LedgerEntry (which might be nil) and the LedgerEntryState -func (effect *NodeEffect) AffectedNode() (*AffectedNode, LedgerEntry, LedgerEntry, LedgerEntryState) { +func (effect *NodeEffect) AffectedNode() (*AffectedNode, LedgerEntry, LedgerEntry, LedgerEntryState, error) { var ( node *AffectedNode final, previous LedgerEntry @@ -96,11 +100,11 @@ func (effect *NodeEffect) AffectedNode() (*AffectedNode, LedgerEntry, LedgerEntr case effect.ModifiedNode != nil && effect.ModifiedNode.FinalFields == nil: node, final, state = effect.ModifiedNode, LedgerEntryFactory[effect.ModifiedNode.LedgerEntryType](), Modified default: - panic(fmt.Sprintf("Unknown LedgerEntryState: %+v", effect)) + return nil, nil, nil, Created, fmt.Errorf("Unknown LedgerEntryState: %+v", effect) } previous = node.PreviousFields if previous == nil { previous = LedgerEntryFactory[final.GetLedgerEntryType()]() } - return node, final, previous, state + return node, final, previous, state, nil } diff --git a/data/orderbook.go b/data/orderbook.go index 4527711..d97309c 100644 --- a/data/orderbook.go +++ b/data/orderbook.go @@ -97,10 +97,10 @@ func defaultUint32(v *uint32) uint32 { return *v } -func (s *AccountOfferSlice) Add(offer *Offer) bool { +func (s *AccountOfferSlice) Add(offer *Offer) (bool, error) { quality, err := offer.TakerPays.Divide(offer.TakerGets) if err != nil { - panic(fmt.Sprintf("impossible quality: %s %s", offer.TakerPays, offer.TakerGets)) + return false, fmt.Errorf("impossible quality: %s %s", offer.TakerPays, offer.TakerGets) } o := AccountOffer{ Flags: LedgerEntryFlag(defaultUint32((*uint32)(offer.Flags))), @@ -114,14 +114,14 @@ func (s *AccountOfferSlice) Add(offer *Offer) bool { switch { case i == len(*s): *s = append(*s, o) - return true + return true, nil case (*s)[i].Sequence != *offer.Sequence: *s = append(*s, AccountOffer{}) copy((*s)[i+1:], (*s)[i:]) (*s)[i] = o - return true + return true, nil default: - return false + return false, nil } } diff --git a/data/proposal.go b/data/proposal.go index c299d59..3db5cac 100644 --- a/data/proposal.go +++ b/data/proposal.go @@ -1,5 +1,7 @@ package data +import "github.com/ChainSQL/go-chainsql-api/common" + type Proposal struct { Hash Hash256 LedgerHash Hash256 @@ -16,7 +18,7 @@ func (p *Proposal) GetSignature() *VariableLength { return &p.Signature } func (p *Proposal) Prefix() HashPrefix { return HP_PROPOSAL } func (p *Proposal) SigningPrefix() HashPrefix { return HP_PROPOSAL } func (p *Proposal) GetHash() *Hash256 { return &p.Hash } -func (p *Proposal) InitialiseForSigning() {} +func (p *Proposal) InitialiseForSigning(kType common.KeyType) {} func (p Proposal) SigningValues() []interface{} { return []interface{}{ @@ -27,7 +29,7 @@ func (p Proposal) SigningValues() []interface{} { } } -func (p Proposal) SuppressionId() (Hash256, error) { +func (p Proposal) SuppressionId(keyType common.KeyType) (Hash256, error) { return hashValues([]interface{}{ p.LedgerHash, p.PreviousLedger, diff --git a/data/schema.go b/data/schema.go new file mode 100644 index 0000000..03ea581 --- /dev/null +++ b/data/schema.go @@ -0,0 +1,45 @@ +package data + +type CreateSchema struct { + SchemaName string + WithState bool + SchemaAdmin string `json:"SchemaAdmin,omitempty"` + AnchorLedgerHash string `json:"AnchorLedgerHash,omitempty"` + PeerList []Peer + Validators []Validator +} + +type ModifySchema struct { + OpType uint16 + Validators []Validator + PeerList []Peer + SchemaID string +} + +type Peer struct { + Peer Endpoint +} +type PeerFormat struct { + Peer EndpointFormat +} +type Endpoint struct { + Endpoint string +} + +type EndpointFormat struct { + Endpoint VariableLength +} + +type Validator struct { + Validator PublicKeyObj +} +type ValidatorFormat struct { + Validator PublicKeyObjFormat +} +type PublicKeyObj struct { + PublicKey string +} + +type PublicKeyObjFormat struct { + PublicKey VariableLength +} diff --git a/data/signing.go b/data/signing.go index 7a641a6..48080d3 100644 --- a/data/signing.go +++ b/data/signing.go @@ -1,29 +1,33 @@ package data -import "github.com/ChainSQL/go-chainsql-api/crypto" +import ( + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/crypto" +) -func Sign(s Signer, key crypto.Key, sequence *uint32) error { - s.InitialiseForSigning() +func Sign(s Signer, key crypto.Key, sequence *uint32, keyType common.KeyType) error { + s.InitialiseForSigning(key.Type()) copy(s.GetPublicKey().Bytes(), key.Public(sequence)) - hash, msg, err := SigningHash(s) + hash, msg, err := SigningHash(s, keyType) if err != nil { return err } - sig, err := crypto.Sign(key.Private(sequence), hash.Bytes(), append(s.SigningPrefix().Bytes(), msg...)) + sig, err := crypto.Sign(key, hash.Bytes(), sequence, append(s.SigningPrefix().Bytes(), msg...)) if err != nil { return err } *s.GetSignature() = VariableLength(sig) - hash, _, err = Raw(s) + hash, blob, err := Raw(s, keyType) if err != nil { return err } copy(s.GetHash().Bytes(), hash.Bytes()) + *s.GetBlob() = VariableLength(blob) return nil } -func CheckSignature(s Signer) (bool, error) { - hash, msg, err := SigningHash(s) +func CheckSignature(s Signer, keyType common.KeyType) (bool, error) { + hash, msg, err := SigningHash(s, keyType) if err != nil { return false, err } diff --git a/data/trade.go b/data/trade.go index 2852242..2466aa8 100644 --- a/data/trade.go +++ b/data/trade.go @@ -17,7 +17,10 @@ type Trade struct { } func newTrade(txm *TransactionWithMetaData, i int) (*Trade, error) { - _, final, previous, action := txm.MetaData.AffectedNodes[i].AffectedNode() + _, final, previous, action, err := txm.MetaData.AffectedNodes[i].AffectedNode() + if err != nil { + return nil, err + } v, ok := final.(*Offer) if !ok || action == Created { return nil, nil diff --git a/data/transaction.go b/data/transaction.go index db6a509..a41aa71 100644 --- a/data/transaction.go +++ b/data/transaction.go @@ -1,5 +1,7 @@ package data +import "github.com/ChainSQL/go-chainsql-api/common" + type TxBase struct { TransactionType TransactionType Flags *TransactionFlag `json:",omitempty"` @@ -14,6 +16,7 @@ type TxBase struct { PreviousTxnID *Hash256 `json:",omitempty"` LastLedgerSequence *uint32 `json:",omitempty"` Hash Hash256 `json:"hash"` + TxBlob *VariableLength } type Payment struct { @@ -194,16 +197,57 @@ type SQLStatement struct { // AutoFillField *VariableLength `json:"AutoFillField,omitempty"` } +type SchemaCreate struct { + TxBase + SchemaName VariableLength + SchemaStrategy uint8 + SchemaAdmin *Account `json:",omitempty"` + AnchorLedgerHash *Hash256 `json:",omitempty"` + Validators []ValidatorFormat + PeerList []PeerFormat +} + +type SchemaModify struct { + TxBase + OpType uint16 + Validators []ValidatorFormat + PeerList []PeerFormat + SchemaID Hash256 +} + +type SchemaDelete struct { + TxBase + SchemaID Hash256 +} + +type ContractTx struct { + TxBase + ContractAddress Account `json:"ContractAddress"` //option + ContractOpType uint16 `json:"ContractOpType"` + ContractData VariableLength `json:"ContractData"` + ContractValue Amount `json:"ContractValue"` + Gas uint32 `json:"Gas"` +} + +func (txBase *TxBase) SetTxBase(seq uint32, fee Value, lastLedgerSequence *uint32, account Account) { + txBase.Sequence = seq + txBase.Fee = fee + txBase.Account = account + txBase.LastLedgerSequence = lastLedgerSequence +} + func (t *TxBase) GetBase() *TxBase { return t } func (t *TxBase) GetType() string { return txNames[t.TransactionType] } func (t *TxBase) GetTransactionType() TransactionType { return t.TransactionType } func (t *TxBase) Prefix() HashPrefix { return HP_TRANSACTION_ID } func (t *TxBase) GetPublicKey() *PublicKey { return t.SigningPubKey } func (t *TxBase) GetSignature() *VariableLength { return t.TxnSignature } +func (t *TxBase) GetBlob() *VariableLength { return t.TxBlob } func (t *TxBase) SigningPrefix() HashPrefix { return HP_TRANSACTION_SIGN } func (t *TxBase) PathSet() PathSet { return PathSet(nil) } func (t *TxBase) GetHash() *Hash256 { return &t.Hash } - +func (t *TxBase) GetRaw() string { return "" } +func (t *TxBase) GetStatements() string { return "" } func (t *TxBase) Compare(other *TxBase) int { switch { case t.Account.Equals(other.Account): @@ -222,13 +266,22 @@ func (t *TxBase) Compare(other *TxBase) int { } } -func (t *TxBase) InitialiseForSigning() { +// func SetBase(txInterface TxInterface) { +// txInterface.SetSeq() + +// } +func (t *TxBase) InitialiseForSigning(kType common.KeyType) { if t.SigningPubKey == nil { - t.SigningPubKey = new(PublicKey) + pubkey := &PublicKey{} + pubkey.SetKey(kType) + t.SigningPubKey = pubkey } if t.TxnSignature == nil { t.TxnSignature = new(VariableLength) } + if t.TxBlob == nil { + t.TxBlob = new(VariableLength) + } } func (o *OfferCreate) Ratio() *Value { diff --git a/data/validation.go b/data/validation.go index 1ad51a3..d541c43 100644 --- a/data/validation.go +++ b/data/validation.go @@ -1,5 +1,9 @@ package data +import ( + "github.com/ChainSQL/go-chainsql-api/common" +) + type Validation struct { Hash Hash256 Flags uint32 @@ -21,6 +25,6 @@ func (v Validation) GetPublicKey() *PublicKey { return &v.SigningPubKey } func (v Validation) GetSignature() *VariableLength { return &v.Signature } func (v Validation) Prefix() HashPrefix { return HP_VALIDATION } func (v Validation) SigningPrefix() HashPrefix { return HP_VALIDATION } -func (v Validation) SuppressionId() (Hash256, error) { return NodeId(&v) } +func (v Validation) SuppressionId(keyType common.KeyType) (Hash256, error) { return NodeId(&v, keyType) } func (v Validation) GetHash() *Hash256 { return &v.Hash } -func (v Validation) InitialiseForSigning() {} +func (v Validation) InitialiseForSigning(kType common.KeyType) {} diff --git a/data/wire.go b/data/wire.go index b1df792..2489b54 100644 --- a/data/wire.go +++ b/data/wire.go @@ -140,7 +140,7 @@ func (a *Account) Marshal(w io.Writer) error { } func (k *PublicKey) Unmarshal(r Reader) error { - return readExpectedLength(r, k[:], "PublicKey") + return readExpectedLength(r, k.KeyValue[:], "PublicKey") } func (k *PublicKey) Marshal(w io.Writer) error { diff --git a/event/manager.go b/event/manager.go index be29d98..879d899 100644 --- a/event/manager.go +++ b/event/manager.go @@ -1,9 +1,13 @@ package event import ( + "encoding/hex" "log" + "strings" "sync" + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/data" "github.com/ChainSQL/go-chainsql-api/export" "github.com/ChainSQL/go-chainsql-api/util" "github.com/buger/jsonparser" @@ -12,6 +16,8 @@ import ( // Manager manages the subscription type Manager struct { txCache map[string]export.Callback + contractCache map[string]bool + ctrEventCache map[string]chan *data.Log tableCache map[string]export.Callback ledgerCloseCache []export.Callback muxTx *sync.Mutex @@ -23,6 +29,8 @@ func NewEventManager() *Manager { return &Manager{ txCache: make(map[string]export.Callback), tableCache: make(map[string]export.Callback), + contractCache: make(map[string]bool), + ctrEventCache: make(map[string]chan *data.Log), ledgerCloseCache: make([]export.Callback, 0, 10), muxTx: new(sync.Mutex), muxTable: new(sync.Mutex), @@ -57,6 +65,32 @@ func (e *Manager) UnSubscribeTx(hash string) { e.muxTx.Unlock() } +//SubscribeCtrAddr subscribe a contract +func (e *Manager) SubscribeCtrAddr(address string, ok bool) { + e.muxTx.Lock() + e.contractCache[address] = ok + e.muxTx.Unlock() +} + +//UnSubscribeCtrAddr unsubscribe a contract +func (e *Manager) UnSubscribeCtrAddr(address string) { + e.muxTx.Lock() + delete(e.contractCache, address) + e.muxTx.Unlock() +} + +func (e *Manager) RegisterCtrEvent(eventSign string, contractMsgCh chan *data.Log) { + e.muxTx.Lock() + e.ctrEventCache[strings.ToUpper(eventSign[2:])] = contractMsgCh + e.muxTx.Unlock() +} + +func (e *Manager) UnRegisterCtrEvent(eventSign string, contractMsgCh chan *data.Log) { + e.muxTx.Lock() + delete(e.ctrEventCache, eventSign) + e.muxTx.Unlock() +} + // SubscribeLedger subscribe ledgerClosed func (e *Manager) SubscribeLedger(callback export.Callback) { e.ledgerCloseCache = append(e.ledgerCloseCache, callback) @@ -69,6 +103,70 @@ func (e *Manager) OnLedgerClosed(msg string) { } } +func (e *Manager) OnContractMsg(msg string) { + log.Println(msg) + ctrEventTopic, err := jsonparser.GetString([]byte(msg), "ContractEventTopics", "[0]") + if err != nil { + return + } + + logRaw := &data.Log{} + contractAddr, err := jsonparser.GetString([]byte(msg), "ContractAddress") + if err != nil { + return + } + logRaw.Address = contractAddr + ctrEventInfo, err := jsonparser.GetString([]byte(msg), "ContractEventInfo") + if err != nil { + return + } + ctrEventInfoHex, err := hex.DecodeString(ctrEventInfo) + if err != nil { + return + } + logRaw.Data = ctrEventInfoHex + ctrEventTopics, _, _, err := jsonparser.Get([]byte(msg), "ContractEventTopics") + if err != nil { + return + } + _, _ = jsonparser.ArrayEach(ctrEventTopics, func(value []byte, dataType jsonparser.ValueType, offset int, errin error) { + valueStr := string(value) + valueHex, err := hex.DecodeString(valueStr) + if err != nil { + return + } + logRaw.Topics = append(logRaw.Topics, common.BytesToHash(valueHex)) + }) + + e.muxTx.Lock() + if _, ok := e.contractCache[contractAddr]; ok { + if eventMsgCh, ok := e.ctrEventCache[ctrEventTopic]; ok { + eventMsgCh <- logRaw + } + } + e.muxTx.Unlock() + // if (data.hasOwnProperty("ContractEventTopics")) { + // data.ContractEventTopics.map(function (topic, index) { + // data.ContractEventTopics[index] = "0x" + data.ContractEventTopics[index].toLowerCase(); + // }); + // } + // if (data.hasOwnProperty("ContractEventInfo") && data.ContractEventInfo !== "") { + // data.ContractEventInfo = "0x" + data.ContractEventInfo; + // } + // let key = data.ContractEventTopics[0]; + // if (that.cache[key]) { + // let contractObj = that.cache[data.ContractAddress]; + // let currentEvent = contractObj.options.jsonInterface.find(function (json) { + // return (json.type === 'event' && json.signature === '0x' + key.replace('0x', '')); + // }); + // let output = contractObj._decodeEventABI(currentEvent, data); + // that.cache[key](null, output); + // // delete that.cache[key]; + // // let keyIndex = contractObj.registeredEvent.indexOf(key); + // // contractObj.registeredEvent.splice(keyIndex,1); + // } +} + // OnSingleTransaction trigger the callback func (e *Manager) OnSingleTransaction(msg string) { // log.Println(msg) diff --git a/event/subscription.go b/event/subscription.go new file mode 100644 index 0000000..2c8484a --- /dev/null +++ b/event/subscription.go @@ -0,0 +1,295 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package event + +import ( + "sync" + // "github.com/ethereum/go-ethereum/common/mclock" +) + +// Subscription represents a stream of events. The carrier of the events is typically a +// channel, but isn't part of the interface. +// +// Subscriptions can fail while established. Failures are reported through an error +// channel. It receives a value if there is an issue with the subscription (e.g. the +// network connection delivering the events has been closed). Only one value will ever be +// sent. +// +// The error channel is closed when the subscription ends successfully (i.e. when the +// source of events is closed). It is also closed when Unsubscribe is called. +// +// The Unsubscribe method cancels the sending of events. You must call Unsubscribe in all +// cases to ensure that resources related to the subscription are released. It can be +// called any number of times. +type Subscription interface { + Err() <-chan error // returns the error channel + Unsubscribe() // cancels sending of events, closing the error channel +} + +// NewSubscription runs a producer function as a subscription in a new goroutine. The +// channel given to the producer is closed when Unsubscribe is called. If fn returns an +// error, it is sent on the subscription's error channel. +func NewSubscription(producer func(<-chan struct{}) error) Subscription { + s := &funcSub{unsub: make(chan struct{}), err: make(chan error, 1)} + go func() { + defer close(s.err) + err := producer(s.unsub) + s.mu.Lock() + defer s.mu.Unlock() + if !s.unsubscribed { + if err != nil { + s.err <- err + } + s.unsubscribed = true + } + }() + return s +} + +type funcSub struct { + unsub chan struct{} + err chan error + mu sync.Mutex + unsubscribed bool +} + +func (s *funcSub) Unsubscribe() { + s.mu.Lock() + if s.unsubscribed { + s.mu.Unlock() + return + } + s.unsubscribed = true + close(s.unsub) + s.mu.Unlock() + // Wait for producer shutdown. + <-s.err +} + +func (s *funcSub) Err() <-chan error { + return s.err +} + +// // Resubscribe calls fn repeatedly to keep a subscription established. When the +// // subscription is established, Resubscribe waits for it to fail and calls fn again. This +// // process repeats until Unsubscribe is called or the active subscription ends +// // successfully. +// // +// // Resubscribe applies backoff between calls to fn. The time between calls is adapted +// // based on the error rate, but will never exceed backoffMax. +// func Resubscribe(backoffMax time.Duration, fn ResubscribeFunc) Subscription { +// return ResubscribeErr(backoffMax, func(ctx context.Context, _ error) (Subscription, error) { +// return fn(ctx) +// }) +// } + +// // A ResubscribeFunc attempts to establish a subscription. +// type ResubscribeFunc func(context.Context) (Subscription, error) + +// // ResubscribeErr calls fn repeatedly to keep a subscription established. When the +// // subscription is established, ResubscribeErr waits for it to fail and calls fn again. This +// // process repeats until Unsubscribe is called or the active subscription ends +// // successfully. +// // +// // The difference between Resubscribe and ResubscribeErr is that with ResubscribeErr, +// // the error of the failing subscription is available to the callback for logging +// // purposes. +// // +// // ResubscribeErr applies backoff between calls to fn. The time between calls is adapted +// // based on the error rate, but will never exceed backoffMax. +// func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscription { +// s := &resubscribeSub{ +// waitTime: backoffMax / 10, +// backoffMax: backoffMax, +// fn: fn, +// err: make(chan error), +// unsub: make(chan struct{}), +// } +// go s.loop() +// return s +// } + +// // A ResubscribeErrFunc attempts to establish a subscription. +// // For every call but the first, the second argument to this function is +// // the error that occurred with the previous subscription. +// type ResubscribeErrFunc func(context.Context, error) (Subscription, error) + +// type resubscribeSub struct { +// fn ResubscribeErrFunc +// err chan error +// unsub chan struct{} +// unsubOnce sync.Once +// lastTry mclock.AbsTime +// lastSubErr error +// waitTime, backoffMax time.Duration +// } + +// func (s *resubscribeSub) Unsubscribe() { +// s.unsubOnce.Do(func() { +// s.unsub <- struct{}{} +// <-s.err +// }) +// } + +// func (s *resubscribeSub) Err() <-chan error { +// return s.err +// } + +// func (s *resubscribeSub) loop() { +// defer close(s.err) +// var done bool +// for !done { +// sub := s.subscribe() +// if sub == nil { +// break +// } +// done = s.waitForError(sub) +// sub.Unsubscribe() +// } +// } + +// func (s *resubscribeSub) subscribe() Subscription { +// subscribed := make(chan error) +// var sub Subscription +// for { +// s.lastTry = mclock.Now() +// ctx, cancel := context.WithCancel(context.Background()) +// go func() { +// rsub, err := s.fn(ctx, s.lastSubErr) +// sub = rsub +// subscribed <- err +// }() +// select { +// case err := <-subscribed: +// cancel() +// if err == nil { +// if sub == nil { +// panic("event: ResubscribeFunc returned nil subscription and no error") +// } +// return sub +// } +// // Subscribing failed, wait before launching the next try. +// if s.backoffWait() { +// return nil // unsubscribed during wait +// } +// case <-s.unsub: +// cancel() +// <-subscribed // avoid leaking the s.fn goroutine. +// return nil +// } +// } +// } + +// func (s *resubscribeSub) waitForError(sub Subscription) bool { +// defer sub.Unsubscribe() +// select { +// case err := <-sub.Err(): +// s.lastSubErr = err +// return err == nil +// case <-s.unsub: +// return true +// } +// } + +// func (s *resubscribeSub) backoffWait() bool { +// if time.Duration(mclock.Now()-s.lastTry) > s.backoffMax { +// s.waitTime = s.backoffMax / 10 +// } else { +// s.waitTime *= 2 +// if s.waitTime > s.backoffMax { +// s.waitTime = s.backoffMax +// } +// } + +// t := time.NewTimer(s.waitTime) +// defer t.Stop() +// select { +// case <-t.C: +// return false +// case <-s.unsub: +// return true +// } +// } + +// // SubscriptionScope provides a facility to unsubscribe multiple subscriptions at once. +// // +// // For code that handle more than one subscription, a scope can be used to conveniently +// // unsubscribe all of them with a single call. The example demonstrates a typical use in a +// // larger program. +// // +// // The zero value is ready to use. +// type SubscriptionScope struct { +// mu sync.Mutex +// subs map[*scopeSub]struct{} +// closed bool +// } + +// type scopeSub struct { +// sc *SubscriptionScope +// s Subscription +// } + +// // Track starts tracking a subscription. If the scope is closed, Track returns nil. The +// // returned subscription is a wrapper. Unsubscribing the wrapper removes it from the +// // scope. +// func (sc *SubscriptionScope) Track(s Subscription) Subscription { +// sc.mu.Lock() +// defer sc.mu.Unlock() +// if sc.closed { +// return nil +// } +// if sc.subs == nil { +// sc.subs = make(map[*scopeSub]struct{}) +// } +// ss := &scopeSub{sc, s} +// sc.subs[ss] = struct{}{} +// return ss +// } + +// // Close calls Unsubscribe on all tracked subscriptions and prevents further additions to +// // the tracked set. Calls to Track after Close return nil. +// func (sc *SubscriptionScope) Close() { +// sc.mu.Lock() +// defer sc.mu.Unlock() +// if sc.closed { +// return +// } +// sc.closed = true +// for s := range sc.subs { +// s.s.Unsubscribe() +// } +// sc.subs = nil +// } + +// // Count returns the number of tracked subscriptions. +// // It is meant to be used for debugging. +// func (sc *SubscriptionScope) Count() int { +// sc.mu.Lock() +// defer sc.mu.Unlock() +// return len(sc.subs) +// } + +// func (s *scopeSub) Unsubscribe() { +// s.s.Unsubscribe() +// s.sc.mu.Lock() +// defer s.sc.mu.Unlock() +// delete(s.sc.subs, s) +// } + +// func (s *scopeSub) Err() <-chan error { +// return s.s.Err() +// } diff --git a/go.mod b/go.mod index 16a4492..f7560b7 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/buger/jsonparser v1.1.1 github.com/gorilla/websocket v1.4.2 github.com/kr/text v0.2.0 // indirect - golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c + gopkg.in/urfave/cli.v1 v1.20.0 ) diff --git a/go.sum b/go.sum deleted file mode 100644 index bbe8234..0000000 --- a/go.sum +++ /dev/null @@ -1,60 +0,0 @@ -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= -github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/net/client.go b/net/client.go index 099c4e5..96fdced 100644 --- a/net/client.go +++ b/net/client.go @@ -4,11 +4,13 @@ import ( "encoding/json" "fmt" "log" + "strings" "sync" "time" "github.com/ChainSQL/go-chainsql-api/common" "github.com/ChainSQL/go-chainsql-api/crypto" + "github.com/ChainSQL/go-chainsql-api/data" "github.com/ChainSQL/go-chainsql-api/event" "github.com/ChainSQL/go-chainsql-api/export" "github.com/ChainSQL/go-chainsql-api/util" @@ -22,7 +24,7 @@ const ReconnectInterval = 10 // Client is used to send and recv websocket msg type Client struct { cmdIDs int64 - schemaID string + SchemaID string wm *WebsocketManager sendMsgChan chan string recvMsgChan chan string @@ -44,16 +46,17 @@ func NewClient() *Client { ServerInfo: NewServerInfo(), Event: event.NewEventManager(), inited: false, + SchemaID: "", } } //Connect is used to create a websocket connection -func (c *Client) Connect(url string) error { +func (c *Client) Connect(url, tlsRootCertPath, tlsClientCertPath, tlsClientKeyPath, serverName string) error { if c.wm != nil { return c.reConnect(url) } - c.wm = NewWsClientManager(url, ReconnectInterval) + c.wm = NewWsClientManager(url, tlsRootCertPath, tlsClientCertPath, tlsClientKeyPath, serverName, ReconnectInterval) err := c.wm.Start() if err != nil { return err @@ -77,7 +80,7 @@ func (c *Client) reConnect(url string) error { c.init() } else { //connect changed,only subscribe - c.initSubscription() + c.InitSubscription() } return nil } @@ -88,13 +91,13 @@ func (c *Client) init() { go c.processMessage() go c.checkReconnection() - c.initSubscription() + c.InitSubscription() c.inited = true } func (c *Client) checkReconnection() { c.wm.OnReconnected(func() { - c.initSubscription() + c.InitSubscription() }) } @@ -102,16 +105,16 @@ func (c *Client) GetWebocketManager() *WebsocketManager { return c.wm } -func (c *Client) initSubscription() { +func (c *Client) InitSubscription() { type Subscribe struct { common.RequestBase Streams []string `json:"streams"` } - c.cmdIDs++ + subCmd := &Subscribe{ RequestBase: common.RequestBase{ Command: "subscribe", - ID: c.cmdIDs, + // ID: c.cmdIDs, }, Streams: []string{"ledger", "server"}, } @@ -134,6 +137,7 @@ func (c *Client) processMessage() { func (c *Client) handleClientMsg(msg string) { // log.Printf("handleClientMsg: %s", msg) msgType, err := jsonparser.GetString([]byte(msg), "type") + // log.Println(msgType) if err != nil { fmt.Printf("handleClientMsg error:%s\n", err) } @@ -151,6 +155,8 @@ func (c *Client) handleClientMsg(msg string) { c.onSingleTransaction(msg) case "table": c.onTableMsg(msg) + case "contract_event": + c.onContractMsg(msg) default: log.Printf("Unhandled message %s", msg) } @@ -192,17 +198,21 @@ func (c *Client) onTableMsg(msg string) { c.Event.OnTableMsg(msg) } +func (c *Client) onContractMsg(msg string) { + c.Event.OnContractMsg(msg) +} + // GetLedger request for ledger data func (c *Client) GetLedger(seq int) string { type getLedger struct { common.RequestBase LedgerIndex int `json:"ledger_index"` } - c.cmdIDs++ + ledgerReq := &getLedger{ RequestBase: common.RequestBase{ Command: "ledger", - ID: c.cmdIDs, + // ID: c.cmdIDs, }, LedgerIndex: seq, } @@ -211,22 +221,71 @@ func (c *Client) GetLedger(seq int) string { return request.Response.Value } +// GetLedger request for ledger data +func (c *Client) GetLedgerTransactions(seq int, expand bool) string { + type getLedgerTxs struct { + common.RequestBase + LedgerIndex int `json:"ledger_index"` + Transactions bool `json:"transactions"` + Expand bool `json:"expand"` + } + ledgerReq := &getLedgerTxs{ + RequestBase: common.RequestBase{ + Command: "ledger", + }, + LedgerIndex: seq, + Transactions: true, + } + if expand { + ledgerReq.Expand = true + } + + request := c.syncRequest(ledgerReq) + + return request.Response.Value +} + // GetLedgerVersion request for ledger version func (c *Client) GetLedgerVersion() (int, error) { + type ledgerVersionRequest struct { + common.RequestBase + LedgerIndex string `json:"ledger_index"` + } + ledgerReq := &ledgerVersionRequest{ + RequestBase: common.RequestBase{ + Command: "ledger", + }, + LedgerIndex: "validated", + } + request := c.syncRequest(ledgerReq) + err := c.parseResponseError(request) + if err != nil { + log.Println("GetLedgerVersion:", err) + return 0, err + } + ledgerIndex, err := jsonparser.GetInt([]byte(request.Response.Value), "result", "ledger_index") + if err != nil { + return 0, err + } + return int(ledgerIndex), nil +} + +// GetLedgerCurrent request for ledger version +func (c *Client) GetLedgerCurrent() (int, error) { type Request struct { common.RequestBase } - c.cmdIDs++ + ledgerReq := &Request{ RequestBase: common.RequestBase{ Command: "ledger_current", - ID: c.cmdIDs, + // ID: c.cmdIDs, }, } request := c.syncRequest(ledgerReq) err := c.parseResponseError(request) if err != nil { - log.Println("GetLedgerVersion:", err) + log.Println("GetLedgerCurrent:", err) return 0, err } ledgerIndex, err := jsonparser.GetInt([]byte(request.Response.Value), "result", "ledger_current_index") @@ -236,6 +295,10 @@ func (c *Client) GetLedgerVersion() (int, error) { return int(ledgerIndex), nil } +func (c *Client) ParseResponseError(request *Request) error { + return c.parseResponseError(request) +} + func (c *Client) parseResponseError(request *Request) error { status, err := jsonparser.GetString([]byte(request.Response.Value), "status") if err != nil { @@ -254,9 +317,8 @@ func (c *Client) GetAccountInfo(address string) (string, error) { common.RequestBase Account string `json:"account"` } - c.cmdIDs++ + accountReq := &getAccount{} - accountReq.ID = c.cmdIDs accountReq.Command = "account_info" accountReq.Account = address @@ -277,9 +339,8 @@ func (c *Client) GetNameInDB(address string, tableName string) (string, error) { Account string `json:"account"` TableName string `json:"tablename"` } - c.cmdIDs++ + req := &Request{} - req.ID = c.cmdIDs req.Command = "g_dbname" req.Account = address req.TableName = tableName @@ -303,9 +364,8 @@ func (c *Client) Submit(blob string) string { common.RequestBase TxBlob string `json:"tx_blob"` } - c.cmdIDs++ + req := &Request{} - req.ID = c.cmdIDs req.Command = "submit" req.TxBlob = blob @@ -325,7 +385,7 @@ func (c *Client) SubscribeTx(hash string, callback export.Callback) { req := Request{} req.Command = "subscribe" req.TxHash = hash - c.asyncRequest(req) + c.syncRequest(&req) } //UnSubscribeTx subscribe a transaction by hash @@ -339,7 +399,45 @@ func (c *Client) UnSubscribeTx(hash string) { req := Request{} req.Command = "unsubscribe" req.TxHash = hash - c.asyncRequest(req) + c.syncRequest(&req) +} + +//SubscribeCtrAddr subscribe a contract by address +func (c *Client) SubscribeCtrAddr(address string, ok bool) { + c.Event.SubscribeCtrAddr(address, ok) + + type SubCtrAddrReq struct { + common.RequestBase + ContractAddr [1]string `json:"accounts_contract"` + } + + ctrAddrArray := [1]string{address} + req := SubCtrAddrReq{} + req.Command = "subscribe" + req.ContractAddr = ctrAddrArray + c.syncRequest(&req) +} + +//UnSubscribeCtrAddr unsubscribe a contract by address +func (c *Client) UnSubscribeCtrAddr(address string) { + c.Event.UnSubscribeCtrAddr(address) + + type SubCtrAddrReq struct { + common.RequestBase + ContractAddr string `json:"accounts_contract"` + } + req := SubCtrAddrReq{} + req.Command = "unsubscribe" + req.ContractAddr = address + c.syncRequest(&req) +} + +func (c *Client) RegisterCtrEvent(eventSign string, contractMsgCh chan *data.Log) { + c.Event.RegisterCtrEvent(eventSign, contractMsgCh) +} + +func (c *Client) UnRegisterCtrEvent(eventSign string, contractMsgCh chan *data.Log) { + c.Event.UnRegisterCtrEvent(eventSign, contractMsgCh) } func (c *Client) GetTableData(dataJSON interface{}, bSql bool) (string, error) { @@ -350,9 +448,8 @@ func (c *Client) GetTableData(dataJSON interface{}, bSql bool) (string, error) { SigningData string `json:"signingData"` TxJSON interface{} `json:"tx_json"` } - c.cmdIDs++ + req := &Request{} - req.ID = c.cmdIDs req.Command = "r_get" if bSql { req.Command = "r_get_sql_user" @@ -398,7 +495,20 @@ func (c *Client) GetTableData(dataJSON interface{}, bSql bool) (string, error) { return string(result), nil } +func (c *Client) SyncRequest(v common.IRequest) *Request { + return c.syncRequest(v) +} + func (c *Client) syncRequest(v common.IRequest) *Request { + c.mutex.Lock() + c.cmdIDs++ + c.mutex.Unlock() + if v.GetID() == 0 { + v.SetID(c.cmdIDs) + } + if c.SchemaID != "" { + v.SetSchemaID(c.SchemaID) + } data, _ := json.Marshal(v) request := NewRequest(v.GetID(), string(data)) request.Wait.Add(1) @@ -434,7 +544,189 @@ func (c *Client) sendRequest(request *Request) { c.sendMsgChan <- request.JSON } -func (c *Client) asyncRequest(v interface{}) { +func (c *Client) asyncRequest(v common.IRequest) { + if c.SchemaID != "" { + v.SetSchemaID(c.SchemaID) + } data, _ := json.Marshal(v) c.sendMsgChan <- string(data) } + +// GetServerInfo request for ServerInfo +func (c *Client) GetServerInfo() (string, error) { + type getServerInfo struct { + common.RequestBase + } + + accountReq := &getServerInfo{} + accountReq.Command = "server_info" + + request := c.syncRequest(accountReq) + + err := c.parseResponseError(request) + if err != nil { + return "", err + } + + return request.Response.Value, nil +} + +func (c *Client) GetSchemaList(params string) (string, error) { + account, _ := jsonparser.GetString([]byte(params), "account") + running, _ := jsonparser.GetBoolean([]byte(params), "running") + type getRunSchemaList struct { + common.RequestBase + Account string `json:"account,omitempty"` + Running bool `json:"running"` + } + type getSchemaList struct { + common.RequestBase + Account string `json:"account,omitempty"` + } + + var request *Request + if strings.Contains(params, "running") { + schemaListReq := &getRunSchemaList{} + schemaListReq.Command = "schema_list" + if account != "" { + schemaListReq.Account = account + } + schemaListReq.Running = running + request = c.syncRequest(schemaListReq) + + } else { + schemaListReq := &getSchemaList{} + schemaListReq.Command = "schema_list" + if account != "" { + schemaListReq.Account = account + } + + request = c.syncRequest(schemaListReq) + } + + err := c.parseResponseError(request) + if err != nil { + return "", err + } + return request.Response.Value, nil +} +func (c *Client) GetSchemaInfo(schemaID string) (string, error) { + if schemaID == "" { + return "", fmt.Errorf("Invalid parameter") + } + type getSchemaInfo struct { + common.RequestBase + Schema string `json:"schema"` + } + + schemaInfoReq := &getSchemaInfo{} + schemaInfoReq.Command = "schema_info" + schemaInfoReq.Schema = schemaID + request := c.syncRequest(schemaInfoReq) + + err := c.parseResponseError(request) + if err != nil { + return "", err + } + return request.Response.Value, nil +} + +func (c *Client) StopSchema(schemaID string) (string, error) { + if schemaID == "" { + return "", fmt.Errorf("Invalid parameter") + } + type StopSchema struct { + common.RequestBase + Schema string `json:"schema"` + } + + StopSchemaReq := &StopSchema{} + StopSchemaReq.Command = "stop" + StopSchemaReq.Schema = schemaID + request := c.syncRequest(StopSchemaReq) + + err := c.parseResponseError(request) + if err != nil { + return "", err + } + return request.Response.Value, nil +} + +func (c *Client) StartSchema(schemaID string) (string, error) { + if schemaID == "" { + return "", fmt.Errorf("Invalid parameter") + } + type StartSchema struct { + common.RequestBase + Schema string `json:"schema"` + } + + StartSchemaReq := &StartSchema{} + StartSchemaReq.Command = "schema_start" + StartSchemaReq.Schema = schemaID + request := c.syncRequest(StartSchemaReq) + + err := c.parseResponseError(request) + if err != nil { + return "", err + } + return request.Response.Value, nil +} + +func (c *Client) Unsubscribe() (string, error) { + type Unsubscribe struct { + common.RequestBase + Streams []string `json:"streams"` + } + + unsubCmd := &Unsubscribe{ + RequestBase: common.RequestBase{ + Command: "unsubscribe", + // ID: c.cmdIDs, + }, + Streams: []string{"ledger", "server"}, + } + request := c.syncRequest(unsubCmd) + + err := c.parseResponseError(request) + if err != nil { + return "", err + } + return request.Response.Value, nil +} + +func (c *Client) GetTransaction(hash string) (string, error) { + type getTransaction struct { + common.RequestBase + Transaction string `json:"transaction"` + } + + getTransactionReq := &getTransaction{} + getTransactionReq.Command = "tx" + getTransactionReq.Transaction = hash + request := c.syncRequest(getTransactionReq) + + err := c.parseResponseError(request) + if err != nil { + return "", err + } + return request.Response.Value, nil +} + +func (c *Client) GetTransactionResult(hash string) (string, error) { + type getTransactionResult struct { + common.RequestBase + Transaction string `json:"transaction"` + } + + getTxResultReq := &getTransactionResult{} + getTxResultReq.Command = "tx_result" + getTxResultReq.Transaction = hash + request := c.syncRequest(getTxResultReq) + + err := c.parseResponseError(request) + if err != nil { + return "", err + } + return request.Response.Value, nil +} diff --git a/net/serverinfo.go b/net/serverinfo.go index 7a6242e..0208f99 100644 --- a/net/serverinfo.go +++ b/net/serverinfo.go @@ -48,7 +48,7 @@ func (s *ServerInfo) Update(result string) { s.GetFieldInt(result, &s.TxnCount, "txn_count") // s.GetFieldString(result, &s.Ledgerhash, "ledger_hash") // s.GetFieldString(result, &s.ServerStatus, "server_status") - log.Printf("!!!!!!!!!!ServerUpdate ledger_index=%d\n", s.LedgerIndex) +// log.Printf("!!!!!!!!!!ServerUpdate ledger_index=%d\n", s.LedgerIndex) s.Updated = true } @@ -58,7 +58,7 @@ func (s *ServerInfo) GetFieldInt(result string, field *int, fieldInJSON string) if err == nil { *field = int(nValue) } else if fieldInJSON == "ledger_index" { - log.Printf("GetFieldInt error for field %s:%s\n", fieldInJSON, result) + //log.Printf("GetFieldInt error for field %s:%s\n", fieldInJSON, result) } } diff --git a/net/utility.go b/net/utility.go index 511f751..a107101 100644 --- a/net/utility.go +++ b/net/utility.go @@ -6,6 +6,8 @@ import ( "github.com/buger/jsonparser" ) +const LastLedgerSeqOffset = 20 + //PrepareTable return the account sequence and table NameInDB func PrepareTable(client *Client, name string) (uint32, string, error) { w := new(sync.WaitGroup) @@ -39,3 +41,41 @@ func PrepareTable(client *Client, name string) (uint32, string, error) { w.Wait() return seq, nameInDB, err } + +func PrepareRipple(client *Client) (uint32, error) { + + var seq uint32 = 0 + err := error(nil) + + info, errTmp := client.GetAccountInfo(client.Auth.Address) + if errTmp != nil { + err = errTmp + return 0, errTmp + } + sequence, errTmp := jsonparser.GetInt([]byte(info), "result", "account_data", "Sequence") + if errTmp != nil { + err = errTmp + return 0, errTmp + } + seq = uint32(sequence) + + return seq, err +} + +func PrepareLastLedgerSeqAndFee(client *Client) (int64, uint32, error) { + var fee int64 = 10 + var lastLedgerSeq uint32 = 10 + if client.ServerInfo.Updated { + lastLedgerSeq = uint32(client.ServerInfo.LedgerIndex + LastLedgerSeqOffset) + fee = int64(client.ServerInfo.ComputeFee()) + } else { + ledgerIndex, err := client.GetLedgerCurrent() + if err != nil { + return 0, 0, err + } + lastLedgerSeq = uint32(ledgerIndex + LastLedgerSeqOffset) + + fee = 50 + } + return fee, lastLedgerSeq, nil +} diff --git a/net/websocketManager.go b/net/websocketManager.go index 7d6d423..ddb1117 100644 --- a/net/websocketManager.go +++ b/net/websocketManager.go @@ -1,6 +1,10 @@ package net import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" "log" "sync" "time" @@ -18,16 +22,21 @@ type WebsocketManager struct { sendMsgChan chan string recvMsgChan chan string isAlive bool + isClose bool timeout int // used for reconnecting muxRead *sync.Mutex muxWrite *sync.Mutex muxConnect *sync.Mutex onReconnected Reconnected first_connect bool + serverName string + tlsRootCertPath string + tlsClientKeyPath string + tlsClientCertPath string } // NewWsClientManager is a constructor -func NewWsClientManager(url string, timeout int) *WebsocketManager { +func NewWsClientManager(url, tlsRootCertPath, tlsClientCertPath, tlsClientKeyPath, serverName string, timeout int) *WebsocketManager { var sendChan = make(chan string, 1024) var recvChan = make(chan string, 1024) var conn *websocket.Conn @@ -37,26 +46,65 @@ func NewWsClientManager(url string, timeout int) *WebsocketManager { sendMsgChan: sendChan, recvMsgChan: recvChan, isAlive: false, + isClose: false, timeout: timeout, muxRead: new(sync.Mutex), muxWrite: new(sync.Mutex), muxConnect: new(sync.Mutex), onReconnected: nil, first_connect: true, + serverName: serverName, + tlsRootCertPath: tlsRootCertPath, + tlsClientKeyPath: tlsClientKeyPath, + tlsClientCertPath: tlsClientCertPath, } } // 链接服务端 func (wsc *WebsocketManager) dail() error { var err error - log.Printf("connecting to %s", wsc.url) + //log.Printf("connecting to %s", wsc.url) websocket.DefaultDialer.HandshakeTimeout = util.DIAL_TIMEOUT * time.Second - wsc.conn, _, err = websocket.DefaultDialer.Dial(wsc.url, nil) + + // tls config + tlsConfig := &tls.Config{ + MaxVersion: tls.VersionTLS12, + } + + if wsc.tlsRootCertPath != "" { + var caPem []byte + caPem, err = ioutil.ReadFile(wsc.tlsRootCertPath) + if err != nil { + return fmt.Errorf("failed to load tls root cert(path = %s), err = %v", wsc.tlsRootCertPath, err) + } + caPool := x509.NewCertPool() + if !caPool.AppendCertsFromPEM(caPem) { + return fmt.Errorf("credentials: failed to append certificates") + } + + tlsConfig.ServerName = wsc.serverName + tlsConfig.RootCAs = caPool + + if wsc.tlsClientCertPath != "" { + clientCert, err := tls.LoadX509KeyPair(wsc.tlsClientCertPath, wsc.tlsClientKeyPath) + tlsConfig.Certificates = []tls.Certificate{clientCert} + if err != nil { + return fmt.Errorf("failed to load tls client (cert path = %s; key path = %s), err = %v", wsc.tlsClientCertPath, wsc.tlsClientKeyPath, err) + } + } + + dailer := &websocket.Dialer{TLSClientConfig: tlsConfig} + wsc.conn, _, err = dailer.Dial(wsc.url, nil) + } else { + wsc.conn, _, err = websocket.DefaultDialer.Dial(wsc.url, nil) + } + if err != nil { - log.Printf("connecting to %s failed,err:%s", wsc.url, err.Error()) + //log.Printf("connecting to %s failed,err:%s", wsc.url, err.Error()) return err } wsc.isAlive = true + wsc.isClose = false log.Printf("connecting to %s success!", wsc.url) return nil } @@ -71,8 +119,11 @@ func (wsc *WebsocketManager) Disconnect() error { return err } wsc.conn = nil + close(wsc.sendMsgChan) + close(wsc.recvMsgChan) } + wsc.isClose = true wsc.isAlive = false wsc.muxConnect.Unlock() @@ -88,11 +139,16 @@ func (wsc *WebsocketManager) SetUrl(url string) { func (wsc *WebsocketManager) sendMsgThread() { go func() { for { + if wsc.isClose{ + break + } if wsc.isAlive { msg := <-wsc.sendMsgChan - if wsc.conn != nil { wsc.muxWrite.Lock() + if wsc.conn == nil || !wsc.isAlive{ + break + } // wsc.conn.SetWriteDeadline(time.Now().Add(time.Duration(wsc.timeout))) err := wsc.conn.WriteMessage(websocket.TextMessage, []byte(msg)) wsc.muxWrite.Unlock() @@ -119,11 +175,20 @@ func (wsc *WebsocketManager) OnReconnected(cb Reconnected) { func (wsc *WebsocketManager) readMsgThread() { go func() { for { + if wsc.isClose{ + break + } if wsc.conn != nil && wsc.isAlive { wsc.muxRead.Lock() + if wsc.conn == nil || !wsc.isAlive{ + break + } _, message, err := wsc.conn.ReadMessage() wsc.muxRead.Unlock() if err != nil { + if wsc.conn == nil || !wsc.isAlive{ + break + } wsc.close() log.Println("read:", err) wsc.isAlive = false @@ -150,8 +215,11 @@ func (wsc *WebsocketManager) close() { func (wsc *WebsocketManager) checkReconnect() { go func() { for { + if wsc.isClose{ + break + } if !wsc.isAlive { - log.Println("checkReconnect ws disconnected,reconnect!") + //log.Println("checkReconnect ws disconnected,reconnect!") wsc.muxConnect.Lock() err := wsc.connectAndRun() wsc.muxConnect.Unlock() @@ -198,7 +266,7 @@ func (wsc *WebsocketManager) ReadChan() chan string { } func (wsc *WebsocketManager) IsConnected() bool { - return wsc.isAlive + return wsc.isAlive && !wsc.isClose } // func main() { diff --git a/test/READ.md b/test/READ.md new file mode 100644 index 0000000..9c4d946 --- /dev/null +++ b/test/READ.md @@ -0,0 +1,8 @@ +## 编译镜像下载: + docker pull peersafes/ubuntu-server-base:golang-1.17.8-gm +## 编译: + ./build.sh +## 运行: + ./test + 注:运行环境必须是linux环境 + diff --git a/test/build.sh b/test/build.sh new file mode 100644 index 0000000..53d372b --- /dev/null +++ b/test/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash +#set -x + +docker-compose -f ./build/build.yaml run --no-deps --rm go-sdk-build diff --git a/test/build/build.yaml b/test/build/build.yaml new file mode 100644 index 0000000..e6f93a6 --- /dev/null +++ b/test/build/build.yaml @@ -0,0 +1,13 @@ +version: '3' + +services: + go-sdk-build: + container_name: go-sdk-build + image: peersafes/ubuntu-server-base:golang-1.17.8-gm + environment: + - GODEBUG=netdns=go + - GO111MODULE=off + working_dir: /opt/gopath/src/github.com/ChainSQL/go-chainsql-api/test/ + volumes: + - $GOPATH/src/github.com/ChainSQL/go-chainsql-api:/opt/gopath/src/github.com/ChainSQL/go-chainsql-api + command: go build . diff --git a/test/certs/client/client.crt b/test/certs/client/client.crt new file mode 100644 index 0000000..64175d1 --- /dev/null +++ b/test/certs/client/client.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICPDCCAeKgAwIBAgIRAL3DlaPFDf1ZUmld0fu6ONAwCgYIKoEcz1UBg3UwdjEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs +c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwNTEzMDMyNTAwWhcNMzIwNTEwMDMy +NTAwWjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE +BxMNU2FuIEZyYW5jaXNjbzEfMB0GA1UEAwwWQWRtaW5Ab3JnMS5leGFtcGxlLmNv +bTBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABHalTgV4QUVHCCBLrvTZ63aNUkzc +hr+8hBHn45WDhNsWzwVexwIrb9hViu4lPpzRRKA5v6pVQYvtfyHdzy30pyqjbDBq +MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +DAYDVR0TAQH/BAIwADArBgNVHSMEJDAigCC1ICJozJDscucgrrKok+wD8UlrbAID +koYA655LGIwZSjAKBggqgRzPVQGDdQNIADBFAiBMQSjtsz1Xnn5VzMqQ+xa1TVhE +/ScqLs4zwyPcELzolQIhAK27dPZIeTsoY4gAynawWdnquQppagwatBXvYFJ7sel8 +-----END CERTIFICATE----- diff --git a/test/certs/client/client.key b/test/certs/client/client.key new file mode 100644 index 0000000..df737d7 --- /dev/null +++ b/test/certs/client/client.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgUWoKJ6ldGIICHuqV +hpEck8d55ftxey3ZLkghHIz2k2GgCgYIKoEcz1UBgi2hRANCAAR2pU4FeEFFRwgg +S6702et2jVJM3Ia/vIQR5+OVg4TbFs8FXscCK2/YVYruJT6c0USgOb+qVUGL7X8h +3c8t9Kcq +-----END PRIVATE KEY----- diff --git a/test/certs/node/server.crt b/test/certs/node/server.crt new file mode 100644 index 0000000..3d614dd --- /dev/null +++ b/test/certs/node/server.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICZzCCAg6gAwIBAgIRAIhQf7H/cE4wavCbgGrgFqwwCgYIKoEcz1UBg3UwdjEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs +c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwNTEzMDMyNTAwWhcNMzIwNTEwMDMy +NTAwWjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE +BxMNU2FuIEZyYW5jaXNjbzEfMB0GA1UEAxMWcGVlcjAub3JnMS5leGFtcGxlLmNv +bTBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABPZyT2sNJcPUBEYM91dQCNwqD9HJ +ZVsuYpPHUVzw0cAbL9wWEgHW55f/dnmH68Q8IY6fo89Z5O9Xy2s/YKrdgTOjgZcw +gZQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAILUgImjMkOxy5yCusqiT7APxSWts +AgOShgDrnksYjBlKMCgGA1UdEQQhMB+CFnBlZXIwLm9yZzEuZXhhbXBsZS5jb22C +BXBlZXIwMAoGCCqBHM9VAYN1A0cAMEQCIBiAllL72WZqRt5OE+jxf/JwepisPeoV +/OTry40qNrbFAiBWMe1QaGItDceL1l8z6tX43P+C1JUaXodnkt4P8zER+Q== +-----END CERTIFICATE----- diff --git a/test/certs/node/server.key b/test/certs/node/server.key new file mode 100644 index 0000000..055e887 --- /dev/null +++ b/test/certs/node/server.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgKdDEY7bQbGeDffDw +TmNmJWu8qkMK5nexQHPBbc2C2vWgCgYIKoEcz1UBgi2hRANCAAT2ck9rDSXD1ARG +DPdXUAjcKg/RyWVbLmKTx1Fc8NHAGy/cFhIB1ueX/3Z5h+vEPCGOn6PPWeTvV8tr +P2Cq3YEz +-----END PRIVATE KEY----- diff --git a/test/certs/root/ca.crt b/test/certs/root/ca.crt new file mode 100644 index 0000000..0da45c0 --- /dev/null +++ b/test/certs/root/ca.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICVzCCAf2gAwIBAgIQaoOw/b1RJMntvqZzcn7k2DAKBggqgRzPVQGDdTB2MQsw +CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy +YW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz +Y2Eub3JnMS5leGFtcGxlLmNvbTAeFw0yMjA1MTMwMzI1MDBaFw0zMjA1MTAwMzI1 +MDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH +Ew1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD +VQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoEcz1UB +gi0DQgAEilxdQnIWsxFUmuQSB7GItyWO8oM0izVAFRviG3m0hYOFUlExlTvrNCnW +eYBau1fUGLYI+xS0noGnOzkS5dyCCqNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud +JQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud +DgQiBCC1ICJozJDscucgrrKok+wD8UlrbAIDkoYA655LGIwZSjAKBggqgRzPVQGD +dQNIADBFAiANIAqpAKHewXkISdv5VnxUHoAs9/sVCnijDPvu17f/2QIhAKuRgnz1 +TGVAeMiFFfnKw2YEMNTaQNFRtNl73nSbsHA8 +-----END CERTIFICATE----- diff --git a/test/certs/server/server.crt b/test/certs/server/server.crt new file mode 100644 index 0000000..3d614dd --- /dev/null +++ b/test/certs/server/server.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICZzCCAg6gAwIBAgIRAIhQf7H/cE4wavCbgGrgFqwwCgYIKoEcz1UBg3UwdjEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs +c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwNTEzMDMyNTAwWhcNMzIwNTEwMDMy +NTAwWjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE +BxMNU2FuIEZyYW5jaXNjbzEfMB0GA1UEAxMWcGVlcjAub3JnMS5leGFtcGxlLmNv +bTBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABPZyT2sNJcPUBEYM91dQCNwqD9HJ +ZVsuYpPHUVzw0cAbL9wWEgHW55f/dnmH68Q8IY6fo89Z5O9Xy2s/YKrdgTOjgZcw +gZQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAILUgImjMkOxy5yCusqiT7APxSWts +AgOShgDrnksYjBlKMCgGA1UdEQQhMB+CFnBlZXIwLm9yZzEuZXhhbXBsZS5jb22C +BXBlZXIwMAoGCCqBHM9VAYN1A0cAMEQCIBiAllL72WZqRt5OE+jxf/JwepisPeoV +/OTry40qNrbFAiBWMe1QaGItDceL1l8z6tX43P+C1JUaXodnkt4P8zER+Q== +-----END CERTIFICATE----- diff --git a/test/certs/server/server.key b/test/certs/server/server.key new file mode 100644 index 0000000..055e887 --- /dev/null +++ b/test/certs/server/server.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgKdDEY7bQbGeDffDw +TmNmJWu8qkMK5nexQHPBbc2C2vWgCgYIKoEcz1UBgi2hRANCAAT2ck9rDSXD1ARG +DPdXUAjcKg/RyWVbLmKTx1Fc8NHAGy/cFhIB1ueX/3Z5h+vEPCGOn6PPWeTvV8tr +P2Cq3YEz +-----END PRIVATE KEY----- diff --git a/test/chainsql-gm/certs/client/client.crt b/test/chainsql-gm/certs/client/client.crt new file mode 100644 index 0000000..64175d1 --- /dev/null +++ b/test/chainsql-gm/certs/client/client.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICPDCCAeKgAwIBAgIRAL3DlaPFDf1ZUmld0fu6ONAwCgYIKoEcz1UBg3UwdjEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs +c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwNTEzMDMyNTAwWhcNMzIwNTEwMDMy +NTAwWjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE +BxMNU2FuIEZyYW5jaXNjbzEfMB0GA1UEAwwWQWRtaW5Ab3JnMS5leGFtcGxlLmNv +bTBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABHalTgV4QUVHCCBLrvTZ63aNUkzc +hr+8hBHn45WDhNsWzwVexwIrb9hViu4lPpzRRKA5v6pVQYvtfyHdzy30pyqjbDBq +MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +DAYDVR0TAQH/BAIwADArBgNVHSMEJDAigCC1ICJozJDscucgrrKok+wD8UlrbAID +koYA655LGIwZSjAKBggqgRzPVQGDdQNIADBFAiBMQSjtsz1Xnn5VzMqQ+xa1TVhE +/ScqLs4zwyPcELzolQIhAK27dPZIeTsoY4gAynawWdnquQppagwatBXvYFJ7sel8 +-----END CERTIFICATE----- diff --git a/test/chainsql-gm/certs/client/client.key b/test/chainsql-gm/certs/client/client.key new file mode 100644 index 0000000..df737d7 --- /dev/null +++ b/test/chainsql-gm/certs/client/client.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgUWoKJ6ldGIICHuqV +hpEck8d55ftxey3ZLkghHIz2k2GgCgYIKoEcz1UBgi2hRANCAAR2pU4FeEFFRwgg +S6702et2jVJM3Ia/vIQR5+OVg4TbFs8FXscCK2/YVYruJT6c0USgOb+qVUGL7X8h +3c8t9Kcq +-----END PRIVATE KEY----- diff --git a/test/chainsql-gm/certs/node/server.crt b/test/chainsql-gm/certs/node/server.crt new file mode 100644 index 0000000..3d614dd --- /dev/null +++ b/test/chainsql-gm/certs/node/server.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICZzCCAg6gAwIBAgIRAIhQf7H/cE4wavCbgGrgFqwwCgYIKoEcz1UBg3UwdjEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs +c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwNTEzMDMyNTAwWhcNMzIwNTEwMDMy +NTAwWjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE +BxMNU2FuIEZyYW5jaXNjbzEfMB0GA1UEAxMWcGVlcjAub3JnMS5leGFtcGxlLmNv +bTBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABPZyT2sNJcPUBEYM91dQCNwqD9HJ +ZVsuYpPHUVzw0cAbL9wWEgHW55f/dnmH68Q8IY6fo89Z5O9Xy2s/YKrdgTOjgZcw +gZQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAILUgImjMkOxy5yCusqiT7APxSWts +AgOShgDrnksYjBlKMCgGA1UdEQQhMB+CFnBlZXIwLm9yZzEuZXhhbXBsZS5jb22C +BXBlZXIwMAoGCCqBHM9VAYN1A0cAMEQCIBiAllL72WZqRt5OE+jxf/JwepisPeoV +/OTry40qNrbFAiBWMe1QaGItDceL1l8z6tX43P+C1JUaXodnkt4P8zER+Q== +-----END CERTIFICATE----- diff --git a/test/chainsql-gm/certs/node/server.key b/test/chainsql-gm/certs/node/server.key new file mode 100644 index 0000000..055e887 --- /dev/null +++ b/test/chainsql-gm/certs/node/server.key @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgKdDEY7bQbGeDffDw +TmNmJWu8qkMK5nexQHPBbc2C2vWgCgYIKoEcz1UBgi2hRANCAAT2ck9rDSXD1ARG +DPdXUAjcKg/RyWVbLmKTx1Fc8NHAGy/cFhIB1ueX/3Z5h+vEPCGOn6PPWeTvV8tr +P2Cq3YEz +-----END PRIVATE KEY----- diff --git a/test/chainsql-gm/certs/root/ca.crt b/test/chainsql-gm/certs/root/ca.crt new file mode 100644 index 0000000..0da45c0 --- /dev/null +++ b/test/chainsql-gm/certs/root/ca.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICVzCCAf2gAwIBAgIQaoOw/b1RJMntvqZzcn7k2DAKBggqgRzPVQGDdTB2MQsw +CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy +YW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz +Y2Eub3JnMS5leGFtcGxlLmNvbTAeFw0yMjA1MTMwMzI1MDBaFw0zMjA1MTAwMzI1 +MDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH +Ew1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD +VQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoEcz1UB +gi0DQgAEilxdQnIWsxFUmuQSB7GItyWO8oM0izVAFRviG3m0hYOFUlExlTvrNCnW +eYBau1fUGLYI+xS0noGnOzkS5dyCCqNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud +JQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud +DgQiBCC1ICJozJDscucgrrKok+wD8UlrbAIDkoYA655LGIwZSjAKBggqgRzPVQGD +dQNIADBFAiANIAqpAKHewXkISdv5VnxUHoAs9/sVCnijDPvu17f/2QIhAKuRgnz1 +TGVAeMiFFfnKw2YEMNTaQNFRtNl73nSbsHA8 +-----END CERTIFICATE----- diff --git a/test/chainsql-gm/chainsqld.cfg b/test/chainsql-gm/chainsqld.cfg new file mode 100644 index 0000000..5dfd022 --- /dev/null +++ b/test/chainsql-gm/chainsqld.cfg @@ -0,0 +1,146 @@ +[server] +port_https_admin_local +port_wss_public +port_peer + +[port_https_admin_local] +port = 5005 +ip = 0.0.0.0 +admin = 0.0.0.0 +protocol = https +#节点https服务的密钥和证书配置 +ssl_key = ./node/server.key +ssl_cert = ./node/server.crt +ssl_verify = 1 + +[port_wss_public] +port = 6006 +ip = 0.0.0.0 +admin = 0.0.0.0 +protocol = wss +#节点wss服务的密钥和证书配置 +ssl_key = ./node/server.key +ssl_cert = ./node/server.crt +ssl_verify = 1 + +[port_peer] +port = 5017 +ip = 0.0.0.0 +protocol = peer + +##节点准入的根证书配置 +#[peer_x509_root_path] +#/home/wangchao/workplace/chainsqld_peers/peer1/ca1.crt +#/home/wangchao/workplace/chainsqld_peers/peer1/ca2.crt +# +##节点准入的节点子证书配置 +#[peer_x509_cred_path] +#/home/wangchao/workplace/chainsqld_peers/peer1/peer1.crt + +#------------------------------------------------------------------------------- + +#账户证书体系的根证书配置 +#[x509_crt_path] +#./root.crt + +#------------------------------------------------------------------------------- + +[node_size] +tiny +#medium + +[node_db] +type=RocksDB +path=./data/RocksDB +open_files=2000 +filter_bits=12 +cache_mb=256 +file_size_mb=8 +file_size_mult=2 + +[ledger_history] +full + +[database_path] +./data + +[debug_logfile] +./data/debug.log + +[sntp_servers] +time.windows.com +time.apple.com +time.nist.gov +pool.ntp.org + +# Turn down default logging to save disk space in the long run. +# Valid values here are trace, debug, info, warning, error, and fatal +[rpc_startup] +{ "command" : "log_level", "severity" : "Warning" } +{ "command" : "log_level", "partition" : "Server", "severity" : "trace" } + +[ssl_verify] +0 + +[ips] + +[validation_seed] +pcL4xp1EkaDtvafHTjDEVmPUu9txadBCGMstvFzUxDmTJvFXgRX + +[validation_public_key] +pEnTgGrYumV8vicA6YuPnyYXPhtXJY3W7gUHdRF9LLDtwvNVty5pZUbNTJpjXo6V7G7K8Z6uozrvUSUyvQB2xZQ9ZrbRocog + +[validators] +pEnTgGrYumV8vicA6YuPnyYXPhtXJY3W7gUHdRF9LLDtwvNVty5pZUbNTJpjXo6V7G7K8Z6uozrvUSUyvQB2xZQ9ZrbRocog + +#[validator_list_sites] +#http://192.168.29.69:8000 +# +#[validator_list_keys] +#029d1f40fc569fff2a76417008d98936a04417db0758c8ab123dee6dbd08d79398 + +[features] +#FeeEscalation +MultiSign +Escrow + + +[consensus] +type=pop +init_time=10 + +[veto_amendments] +C6970A8B603D8778783B61C0D445C23D1633CCFAEF0D43E7DBCD1521D34BD7C3 SHAMapV2 +C1B8D934087225F509BEB5A8EC24447854713EE447D277F69545ABFA0E0FD490 Tickets +1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146 CryptoConditions +86E83A7D2ECE3AD5FA87AB2195AE015C950469ABF0B72EAACED318F74886AE90 CryptoConditionsSuite +#3012E8230864E95A58C60FD61430D7E1B4D3353195F2981DC12B0C7C0950FFAC FlowCross +#42EEA5E28A97824821D4EF97081FE36A54E9593C6E4F20CBAE098C69D2E072DC fix1373 +#740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11 Flow +#E2E6F2866106419B88C50045ACE96368558C345566AC8F2BDF5A5B5587F0E6FA fix1368 + +[governance] +#admin=zwotLHCa2EAw8As42jRLR8jBMyQnXe67Q9 +#admin=zNPZvrBoRzkhoAvesV7pZu2rAG17ezPz4Y +default_authority_enabled=0 + +#[auto_sync] +#1 +# +#[sync_db] +#type=mysql +#host=10.100.0.78 +#port=3306 +#user=root +#pass=1234 +#db=wc +#first_storage=0 +#charset=utf8 + +[workers] +8 + +[crypto_alg] +node_alg_type=gmalg + + diff --git a/test/chainsql-gm/docker-compose.yaml b/test/chainsql-gm/docker-compose.yaml new file mode 100644 index 0000000..632749a --- /dev/null +++ b/test/chainsql-gm/docker-compose.yaml @@ -0,0 +1,28 @@ +version: '2' +services: + chainsql: + container_name: chainsql + image: peersafes/chainsql:latest + restart: always + ports: + - 5005:5005 + - 6006:6006 + - 5017:5017 + ulimits: + nproc: 65535 + nofile: + soft: 65535 + hard: 65535 + volumes: + - ./certs/node:/opt/chainsql/node + - ./certs/root:/opt/chainsql/root + - /etc/localtime:/etc/localtime + - ./update:/usr/local/bin/update + - ./chainsqld.cfg:/opt/chainsql/chainsqld.cfg + + entrypoint: ["/bin/sh","-c","./chainsqld"] + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "100" diff --git a/test/chainsql-gm/update b/test/chainsql-gm/update new file mode 100644 index 0000000..aa31856 Binary files /dev/null and b/test/chainsql-gm/update differ diff --git a/test/main.go b/test/main.go index f0dd6e7..e06bd22 100644 --- a/test/main.go +++ b/test/main.go @@ -4,9 +4,11 @@ import ( "encoding/json" "fmt" "log" + "math/big" "time" "github.com/ChainSQL/go-chainsql-api/core" + "github.com/ChainSQL/go-chainsql-api/test/storage" "github.com/buger/jsonparser" "github.com/gorilla/websocket" ) @@ -17,45 +19,103 @@ type Account struct { secret string } +var root1 = Account{ + address: "zPHxWCKNZjpbQHV5DLpBy8rSR8HdXpPDzi", + secret: "xp6FwxZP1rrmPy2GDTobvHTgnZnrC", +} + +//zPHxWCKNZjpbQHV5DLpBy8rSR8HdXpPDzi +//zEdmwPS5BoEukn1ech2LHjuV6STkq3QYkM +var root = Account{ + address: "zHb9CJAWyB4zj91VRWn96DkukG4bwdtyTh", + secret: "xnoPBzXtMeMyMHUVTgbuqAfg1SUTb", +} +var user1 = Account{ + address: "zBonp9s7isAaDUPcfrFfYjNnhgeznoBHxF", + secret: "xn2FhQLRQqhKJeNhpgMzp2PGAYbdw", +} +var user2 = Account{ + address: "zKXfeKXkTtLSTkEzaJyu2cRmRBFRvTW2zc", + secret: "xhtBo8BLBZtTgc3LHnRspaFro5P4H", +} +var gmRoot = Account{ + address: "zN7TwUjJ899xcvNXZkNJ8eFFv2VLKdESsj", + secret: "p97evg5Rht7ZB7DbEpVqmV3yiSBMxR3pRBKJyLcRWt7SL5gEeBb", +} +var smUser1 = Account{ + secret: "pwRdHmA4cSUKKtFyo4m2vhiiz5g6ym58Noo9dTsUU97mARNjevj", + address: "zMXMtS2C36J1p3uhTxRFWV8pEhHa8AMMSL", +} var tableName = "hello2" func main() { c := core.NewChainsql() - // err := c.Connect("ws://127.0.0.1:6006") - // log.Println("IsConnected:", c.IsConnected()) - // if err != nil { - // log.Println(err) - // return - // } + + // address := "wss://127.0.0.1:6006" + address := "ws://127.0.0.1:6006" + + serverName := "peer0.org1.example.com" + rootPath := "./certs/root/ca.crt" + clientCertPath := "./certs/client/client.crt" + clientKeyPath := "./certs/client/client.key" + + serverName = "" + rootPath = "" + clientCertPath = "" + clientKeyPath = "" + + err := c.Connect(address, rootPath, clientCertPath, clientKeyPath, serverName) + log.Println("IsConnected:", c.IsConnected()) + if err != nil { + log.Println(err) + return + } // var root = Account{ // address: "zHb9CJAWyB4zj91VRWn96DkukG4bwdtyTh", // secret: "xnoPBzXtMeMyMHUVTgbuqAfg1SUTb", // } - // var user = Account{ - // address: "zBonp9s7isAaDUPcfrFfYjNnhgeznoBHxF", - // secret: "xn2FhQLRQqhKJeNhpgMzp2PGAYbdw", - // } - // c.As(user.address, user.secret) - // c.As(root.address, root.secret) + c.As(gmRoot.address, gmRoot.secret) + //c.As(smRoot.address, smRoot.secret) + //c.SetSchema("44C2C733C17335C11B01BCB0B55340EA422F37307188FF84E6127F8BEBBF0C60") + //GenerateKey(rand.Reader) // c.Use(root.address) - // // testSubLedger(c) - testGenerateAccount(c) - // testInsert(c) + testContract(c) + // testSubLedger(c) + // testGenerateAccount(c) + //testInsert(c) // testGetLedger(c) + // testGetLedgerTxs(c) + // testGetLedgerVersion(c) // testSignPlainText(c) // testGetTableData(c) // testGetBySqlUser(c) // testWebsocket() - // testTickerGet(c) + // testTickerGet(c)\ + //testValidationCreate(c) + // testGetAccountInfo(c) + // testGetServerInfo(c) + // testPay(c) + //testSchemaCreate(c) //创建子链 + //testSchemaModify(c) // 修改子链 + //testGetSchemaList(c) //获取子链列表 + //testGetSchemaInfo(c) //依据子链id获取子链信息 + //testStopSchema(c) // + //testStartSchema(c) + + // testGetTransaction(c) + //testGetSchemaId(c) + // testGenerateAddress(c) + //testDeleteSchema(c) + // testGetTransactionResult(c) for { - time.Sleep(time.Second * 10) + time.Sleep(time.Second * 1) } } -func testGenerateAccount(c *core.Chainsql) { +/*func testGenerateAccount(c *core.Chainsql) { accStr, err := c.GenerateAccount() if err != nil { log.Println(err) @@ -71,11 +131,11 @@ func testGenerateAccount(c *core.Chainsql) { return } log.Println(accStr) -} +}*/ func testInsert(c *core.Chainsql) { var data = []byte(`[{"id":1,"name":"echo","age":18}]`) - ret := c.Table(tableName).Insert(string(data)).Submit("db_success") + ret := c.Table("gmTest50").Insert(string(data)).Submit("db_success") log.Println(ret) } @@ -140,6 +200,21 @@ func testGetLedger(c *core.Chainsql) { } } +func testGetLedgerTxs(c *core.Chainsql) { + for i := 20; i < 25; i++ { + ledger := c.GetLedgerTransactions(i, true) + log.Printf("GetLedgerTransactions %d:%s\n", i, ledger) + } +} + +func testGetLedgerVersion(c *core.Chainsql) { + ledger, err := c.GetLedgerVersion() + if err != nil { + log.Println("error:", err) + } + log.Println("GetLedgerVersion", ledger) +} + func testSubLedger(c *core.Chainsql) { go func() { c.OnLedgerClosed(func(msg string) { @@ -206,3 +281,193 @@ func testTickerGet(c *core.Chainsql) { ticker.Stop() done <- true } + +func testValidationCreate(c *core.Chainsql) { + seedKey, err := c.ValidationCreate() + if err != nil { + log.Println(err) + } + log.Printf("seedKey %s\n", seedKey) +} + +func testGetAccountInfo(c *core.Chainsql) { + account, err := c.GetAccountInfo(gmRoot.address) + if err != nil { + log.Println(err) + } + log.Printf("seedKey %s\n", account) +} + +func testGetServerInfo(c *core.Chainsql) { + serverInfo, err := c.GetServerInfo() + if err != nil { + log.Println(err) + } + log.Printf("seedKey %s\n", serverInfo) +} + +func testContract(c *core.Chainsql) { + testDeployContract(c) + + // contractAddr := "zwbCuvYRupsr2MRUMyTrz1eft28RewQuQJ" + // testInvokeContract(c, contractAddr) +} +func testDeployContract(c *core.Chainsql) { + transactOpt := &core.TransactOpts{ + ContractValue: 0, + Gas: 3000000, + Expectation: "validate_success", + } + + deployRet, _, _ := storage.DeployStorage(c, transactOpt) + log.Println(deployRet) +} +func testInvokeContract(c *core.Chainsql, contractAddr string) { + storageIns, _ := storage.NewStorage(c, contractAddr) + + // testCallContract(c, storageIns) + // testEventContract(c, storageIns) + // testSubmitContract(c, storageIns) + testGetPastEvent(c, storageIns) + log.Println("finish all") +} +func testCallContract(c *core.Chainsql, storageIns *storage.Storage) { + callOpt := &core.CallOpts{} + ret, _ := storageIns.Retrieve(callOpt) + log.Println(ret) +} +func testSubmitContract(c *core.Chainsql, storageIns *storage.Storage) { + transactOpt := &core.TransactOpts{ + ContractValue: 0, + Gas: 3000000, + Expectation: "validate_success", + } + + ret, err := storageIns.Store(transactOpt, big.NewInt(789)) + if err != nil { + log.Fatalln(err) + } else { + log.Println(ret) + } +} +func testEventContract(c *core.Chainsql, storageIns *storage.Storage) { + watchOpt := &core.WatchOpts{} + sinkCh := make(chan *storage.StorageNumberChanges) + event, err := storageIns.WatchNumberChanges(watchOpt, sinkCh) + if err != nil { + event.Unsubscribe() + } + go func() { + for logInfo := range sinkCh { + log.Println(logInfo) + } + }() + // event.Unsubscribe() +} + +func testGetPastEvent(c *core.Chainsql, storageIns *storage.Storage) { + // eventLogs, err := storageIns.GetPastEvent("283CC2AF0AC3913FD07D9967891DCEB3B95B39D85E09429D202588B1CA9B1C42", "") + eventLogs, err := storageIns.GetNumberChangesPastEvent("", "5B0A2020207B0A20202020202022636F6E74726163745F6461746122203A202230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030333135222C0A20202020202022636F6E74726163745F746F7069637322203A205B20223131363144363745334534304436344441304632324634313035343132304237343541323841413235453635443938443135334642414634443331393532353122205D0A2020207D0A5D0A") + if err != nil { + log.Fatalln(err) + } + + for _, eventLog := range eventLogs { + log.Println(eventLog) + } +} + +func testPay(c *core.Chainsql) { + ret := c.Pay(smUser1.address, 6000000).Submit("validate_success") + log.Println(ret) +} + +func testSchemaCreate(c *core.Chainsql) { + schemaInfo := "{\"SchemaName\":\"hello\",\"WithState\":true,\"SchemaAdmin\":\"zHb9CJAWyB4zj91VRWn96DkukG4bwdtyTh\",\"Validators\":[{\"Validator\":{\"PublicKey\":\"02E273A1B6C5D8427F60F490FC8E03D52F35D1A57B8755E19DB0F3B683D9A81239\"}},{\"Validator\":{\"PublicKey\":\"03946B1514971B645F22EE9C96843C5705D388D65C563B7DDEB8B56E29A5283D3C\"}},{\"Validator\":{\"PublicKey\":\"026C3AB1DE848906C5F42316D2B92C3382EEE68FC08F738A2BE9D8C0BC4954936D\"}},{\"Validator\":{\"PublicKey\":\"030F9602B680A71D962111CC3EF9D27601EB9FEC7C3BA24BB4323D94C3A1CF9A04\"}}],\"PeerList\":[{\"Peer\":{\"Endpoint\":\"10.100.0.78:25413\"}},{\"Peer\":{\"Endpoint\":\"10.100.0.78:25414\"}},{\"Peer\":{\"Endpoint\":\"10.100.0.78:25415\"}},{\"Peer\":{\"Endpoint\":\"10.100.0.104:5510\"}}]}" + //schemaInfo := "{"SchemaName":"子链1","WithState":false,"SchemaAdmin":"zKTqp9kqJBag59YGmL7imH9RfukG6qVtfS","Validators":[{"Validator":{"PublicKey":"037B2D1B1C97A996B44A2FA25765DE5D937247840C960AC6E84D0E3AA8A718F96E"}},{"Validator":{"PublicKey":"038C4245389C8AB8C7665CA4002AEE75EF5D7EEB51A4410D48797BC74F275E9CC3"}},{"Validator":{"PublicKey":"0237788307F53E50D9F799F0D0ABD48258BC41D9418638BD51C481D1848E005443"}}],"PeerList":[{"Peer":{"Endpoint":"192.168.0.242:12260"}},{"Peer":{"Endpoint":"192.168.0.242:12264"}},{"Peer":{"Endpoint":"192.168.0.242:12269"}}]}" + //schemaInfo := "{\"SchemaName\":\"hello\",\"WithState\":true,\"SchemaAdmin\":\"zN7TwUjJ899xcvNXZkNJ8eFFv2VLKdESsj\",\"Validators\":[{\"Validator\":{\"PublicKey\":\"47F7288B41B45F49342FAC6B65EC529B5ED52F3DDD35140C53BB54A3A7D03F3E9166B0FD574F098F2F9E30526EC8293CE95D4956AD8EC02B34060F0709DCDEA3C5\"}},{\"Validator\":{\"PublicKey\":\"47594A1F76382A89A811B485E3B3414F18967C55A9A2BB90DF7EF36FFF5FDCB915B9C495D66ADEA79DAD97C897596F6FE093C7CDADF90BDD0C91B99D8D014C1B05\"}},{\"Validator\":{\"PublicKey\":\"47C45A7D125E49FDFF1DE6C08F738122FFDC7171E0F5AFA794D02198E35F7F1B1F07CE271CDBF9BA4DB94AA087BE4F59F2A15A60868BE4ACFA86D13B448CD06038\"}}],\"PeerList\":[{\"Peer\":{\"Endpoint\":\"192.168.177.109:5432\"}},{\"Peer\":{\"Endpoint\":\"192.168.177.109:5433\"}},{\"Peer\":{\"Endpoint\":\"192.168.177.109:5441\"}}]}" + ret := c.CreateSchema(schemaInfo).Submit("validate_success") + log.Println(ret) +} + +func testSchemaModify(c *core.Chainsql) { + schemaInfo := "{\"SchemaID\":\"2A4C77CF90F1EE9495733D95549B6E0C2ECD61A49343D04C75FE2FD366366A13\",\"Validators\":[{\"Validator\":{\"PublicKey\":\"471D5247096A08552746B5E321E925639A43D6A3ED5F48E48C678E1630F6B92F88EBE20579DCEF85371C43D7305787CAA9AADF7D705BDD1523BBCCF9865FEB34A4\"}}],\"PeerList\":[{\"Peer\":{\"Endpoint\":\"10.100.0.104:5410\"}}]}" + //schemaInfo := "{\"SchemaName\":\"hello\",\"WithState\":false,\"SchemaAdmin\":\"zBonp9s7isAaDUPcfrFfYjNnhgeznoBHxF\",\"Validators\":\"fhfhhfhfhfhfh\",\"PeerList\":[{\"Peer\":{\"Endpoint\":\"127.0.0.1:15125\"}},{\"Peer\":{\"Endpoint\":\"127.0.0.1:25125\"}},{\"Peer\":{\"Endpoint\":\"127.0.0.1:35125\"}}]}" + ret := c.ModifySchema("schema_add", schemaInfo).Submit("validate_success") + log.Println(ret) +} + +func testGetSchemaList(c *core.Chainsql) { + //param := "{\"running\":false}" + param := "" + ret, err := c.GetSchemaList(param) + log.Println(ret) + log.Println(err) +} + +func testGetSchemaInfo(c *core.Chainsql) { + schemaID := "44C2C733C17335C11B01BCB0B55340EA422F37307188FF84E6127F8BEBBF0C60" + ret, err := c.GetSchemaInfo(schemaID) + log.Println(ret) + log.Println(err) +} + +func testStopSchema(c *core.Chainsql) { + schemaID := "2A4C77CF90F1EE9495733D95549B6E0C2ECD61A49343D04C75FE2FD366366A13" + ret, err := c.StopSchema(schemaID) + log.Println(ret) + log.Println(err) +} + +func testStartSchema(c *core.Chainsql) { + schemaID := "2A4C77CF90F1EE9495733D95549B6E0C2ECD61A49343D04C75FE2FD366366A13" + ret, err := c.StartSchema(schemaID) + log.Println(ret) + log.Println(err) +} + +func testGetTransaction(c *core.Chainsql) { + txHash := "283CC2AF0AC3913FD07D9967891DCEB3B95B39D85E09429D202588B1CA9B1C42" + ret, err := c.GetTransaction(txHash) + log.Println(ret) + log.Println(err) +} + +func testGetSchemaId(c *core.Chainsql) { + txHash := "1E1EA9E9936574D17646EE9801B72B106DB35D13923FE4357746AD2DD2135C78" + ret, err := c.GetSchemaId(txHash) + log.Println(ret) + log.Println(err) +} + +func testGenerateAddress(c *core.Chainsql) { + //option := "" + option := "{\"algorithm\":\"softGMAlg\"}" + // option := "{\"algorithm\":\"softGMAlg\", \"secret\":\"pwRdHmA4cSUKKtFyo4m2vhiiz5g6ym58Noo9dTsUU97mARNjevj\"}" + //xp6FwxZP1rrmPy2GDTobvHTgnZnrC + //xnoPBzXtMeMyMHUVTgbuqAfg1SUTb + //option := "{\"algorithm\":\"secp256k1\", \"secret\":\"xp6FwxZP1rrmPy2GDTobvHTgnZnrC\"}" + ret, err := c.GenerateAddress(option) + if err != nil { + log.Println(err) + } else { + log.Println(ret) + } +} + +func testDeleteSchema(c *core.Chainsql) { + schemaID := "362C1B00328F08A3CE093FE64B0BC88951D364DC6D2B44601268E7F273354B4A" + ret := c.DeleteSchema(schemaID).Submit("validate_success") + log.Println(ret) +} + +func testGetTransactionResult(c *core.Chainsql) { + txHash := "48AF80C9169790BE33CBD8C5F32151C9D4483D855B195D188854636EB6551AF5" + ret, err := c.GetTransactionResult(txHash) + if err != nil { + log.Println(err) + } else { + // {"id":2,"result":{"ledger_index":28,"transaction_result":"tesSUCCESS","tx_hash":"48AF80C9169790BE33CBD8C5F32151C9D4483D855B195D188854636EB6551AF5","tx_status":"validated"},"status":"success","type":"response"} + log.Println(ret) + } +} diff --git a/test/storage/storage.abi b/test/storage/storage.abi new file mode 100644 index 0000000..ec6f37b --- /dev/null +++ b/test/storage/storage.abi @@ -0,0 +1,41 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "num", + "type": "uint256" + } + ], + "name": "numberChanges", + "type": "event" + }, + { + "inputs": [], + "name": "retrieve", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "num", + "type": "uint256" + } + ], + "name": "store", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/test/storage/storage.bin b/test/storage/storage.bin new file mode 100644 index 0000000..7e238a1 --- /dev/null +++ b/test/storage/storage.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5060bd8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80632e64cec11460375780636057361d14604c575b600080fd5b60005460405190815260200160405180910390f35b605b60573660046098565b605d565b005b60008190556040518181527f1161d67e3e40d64da0f22f41054120b745a28aa25e65d98d153fbaf4d31952519060200160405180910390a150565b60006020828403121560a957600080fd5b503591905056fea164736f6c6343000805000a \ No newline at end of file diff --git a/test/storage/storage.go b/test/storage/storage.go new file mode 100644 index 0000000..d928f5c --- /dev/null +++ b/test/storage/storage.go @@ -0,0 +1,412 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package storage + +import ( + "errors" + "math/big" + "strings" + + "github.com/ChainSQL/go-chainsql-api/abigen/abi" + "github.com/ChainSQL/go-chainsql-api/abigen/abi/bind" + "github.com/ChainSQL/go-chainsql-api/common" + "github.com/ChainSQL/go-chainsql-api/core" + "github.com/ChainSQL/go-chainsql-api/data" + "github.com/ChainSQL/go-chainsql-api/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = bind.Bind + _ = common.Big1 +) + +// StorageMetaData contains all meta data concerning the Storage contract. +var StorageMetaData = &core.CtrMetaData{ + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"num\",\"type\":\"uint256\"}],\"name\":\"numberChanges\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"retrieve\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"num\",\"type\":\"uint256\"}],\"name\":\"store\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5060bd8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80632e64cec11460375780636057361d14604c575b600080fd5b60005460405190815260200160405180910390f35b605b60573660046098565b605d565b005b60008190556040518181527f1161d67e3e40d64da0f22f41054120b745a28aa25e65d98d153fbaf4d31952519060200160405180910390a150565b60006020828403121560a957600080fd5b503591905056fea164736f6c6343000805000a", +} + +// StorageABI is the input ABI used to generate the binding from. +// Deprecated: Use StorageMetaData.ABI instead. +var StorageABI = StorageMetaData.ABI + +// StorageBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use StorageMetaData.Bin instead. +var StorageBin = StorageMetaData.Bin + +// DeployStorage deploys a new ChainSQL contract, binding an instance of Storage to it. +func DeployStorage(chainsql *core.Chainsql, auth *core.TransactOpts) (*core.DeployTxRet, *Storage, error) { + parsed, err := StorageMetaData.GetAbi() + if err != nil { + return &core.DeployTxRet{}, nil, err + } + if parsed == nil { + return &core.DeployTxRet{}, nil, errors.New("GetABI returned nil") + } + + deployRet, contract, err := core.DeployContract(chainsql, auth, *parsed, common.FromHex(StorageBin)) + if err != nil { + return &core.DeployTxRet{}, nil, err + } + return deployRet, &Storage{StorageCaller: StorageCaller{contract: contract}, StorageTransactor: StorageTransactor{contract: contract}, StorageFilterer: StorageFilterer{contract: contract}}, nil +} + +// Storage is an auto generated Go binding around an ChainSQL contract. +type Storage struct { + StorageCaller // Read-only binding to the contract + StorageTransactor // Write-only binding to the contract + StorageFilterer // Log filterer for contract events +} + +// StorageCaller is an auto generated read-only Go binding around an ChainSQL contract. +type StorageCaller struct { + contract *core.BoundContract // Generic contract wrapper for the low level calls +} + +// StorageTransactor is an auto generated write-only Go binding around an ChainSQL contract. +type StorageTransactor struct { + contract *core.BoundContract // Generic contract wrapper for the low level calls +} + +// StorageFilterer is an auto generated log filtering Go binding around an ChainSQL contract events. +type StorageFilterer struct { + contract *core.BoundContract // Generic contract wrapper for the low level calls +} + +// StorageSession is an auto generated Go binding around an ChainSQL contract, +// with pre-set call and transact options. +type StorageSession struct { + Contract *Storage // Generic contract binding to set the session for + CallOpts core.CallOpts // Call options to use throughout this session + TransactOpts core.TransactOpts // Transaction auth options to use throughout this session +} + +// StorageCallerSession is an auto generated read-only Go binding around an ChainSQL contract, +// with pre-set call options. +type StorageCallerSession struct { + Contract *StorageCaller // Generic contract caller binding to set the session for + CallOpts core.CallOpts // Call options to use throughout this session +} + +// StorageTransactorSession is an auto generated write-only Go binding around an ChainSQL contract, +// with pre-set transact options. +type StorageTransactorSession struct { + Contract *StorageTransactor // Generic contract transactor binding to set the session for + TransactOpts core.TransactOpts // Transaction auth options to use throughout this session +} + +// StorageRaw is an auto generated low-level Go binding around an ChainSQL contract. +type StorageRaw struct { + Contract *Storage // Generic contract binding to access the raw methods on +} + +// StorageCallerRaw is an auto generated low-level read-only Go binding around an ChainSQL contract. +type StorageCallerRaw struct { + Contract *StorageCaller // Generic read-only contract binding to access the raw methods on +} + +// StorageTransactorRaw is an auto generated low-level write-only Go binding around an ChainSQL contract. +type StorageTransactorRaw struct { + Contract *StorageTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewStorage creates a new instance of Storage, bound to a specific deployed contract. +func NewStorage(chainsql *core.Chainsql, address string) (*Storage, error) { + contract, err := bindStorage(chainsql, address) + if err != nil { + return nil, err + } + return &Storage{StorageCaller: StorageCaller{contract: contract}, StorageTransactor: StorageTransactor{contract: contract}, StorageFilterer: StorageFilterer{contract: contract}}, nil +} + +// // NewStorageCaller creates a new read-only instance of Storage, bound to a specific deployed contract. +// func NewStorageCaller(address common.Address, caller bind.ContractCaller) (*StorageCaller, error) { +// contract, err := bindStorage(address, caller, nil, nil) +// if err != nil { +// return nil, err +// } +// return &StorageCaller{contract: contract}, nil +// } + +// // NewStorageTransactor creates a new write-only instance of Storage, bound to a specific deployed contract. +// func NewStorageTransactor(address common.Address, transactor bind.ContractTransactor) (*StorageTransactor, error) { +// contract, err := bindStorage(address, nil, transactor, nil) +// if err != nil { +// return nil, err +// } +// return &StorageTransactor{contract: contract}, nil +// } + +// // NewStorageFilterer creates a new log filterer instance of Storage, bound to a specific deployed contract. +// func NewStorageFilterer(address common.Address, filterer bind.ContractFilterer) (*StorageFilterer, error) { +// contract, err := bindStorage(address, nil, nil, filterer) +// if err != nil { +// return nil, err +// } +// return &StorageFilterer{contract: contract}, nil +// } + +// bindStorage binds a generic wrapper to an already deployed contract. +func bindStorage(chainsql *core.Chainsql, address string) (*core.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(StorageABI)) + if err != nil { + return nil, err + } + return core.NewBoundContract(chainsql, address, parsed), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +// func (_Storage *StorageRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { +// return _Storage.Contract.StorageCaller.contract.Call(opts, result, method, params...) +// } + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +// func (_Storage *StorageRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { +// return _Storage.Contract.StorageTransactor.contract.Transfer(opts) +// } + +// Transact invokes the (paid) contract method with params as input values. +// func (_Storage *StorageRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { +// return _Storage.Contract.StorageTransactor.contract.Transact(opts, method, params...) +// } + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +// func (_Storage *StorageCallerRaw) Call(opts *core.CallOpts, result *[]interface{}, method string, params ...interface{}) error { +// return _Storage.Contract.contract.Call(opts, result, method, params...) +// } + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +// func (_Storage *StorageTransactorRaw) Transfer(opts *core.TransactOpts) (*types.Transaction, error) { +// return _Storage.Contract.contract.Transfer(opts) +// } + +// Transact invokes the (paid) contract method with params as input values. +// func (_Storage *StorageTransactorRaw) Transact(opts *core.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { +// return _Storage.Contract.contract.Transact(opts, method, params...) +// } + +// Retrieve is a free data retrieval call binding the contract method 0x2e64cec1. +// +// Solidity: function retrieve() view returns(uint256) +func (_Storage *StorageCaller) Retrieve(opts *core.CallOpts) (*big.Int, error) { + var out []interface{} + err := _Storage.contract.Call(opts, &out, "retrieve") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Retrieve is a free data retrieval call binding the contract method 0x2e64cec1. +// +// Solidity: function retrieve() view returns(uint256) +func (_Storage *StorageSession) Retrieve() (*big.Int, error) { + return _Storage.Contract.Retrieve(&_Storage.CallOpts) +} + +// Retrieve is a free data retrieval call binding the contract method 0x2e64cec1. +// +// Solidity: function retrieve() view returns(uint256) +func (_Storage *StorageCallerSession) Retrieve() (*big.Int, error) { + return _Storage.Contract.Retrieve(&_Storage.CallOpts) +} + +// Store is a paid mutator transaction binding the contract method 0x6057361d. +// +// Solidity: function store(uint256 num) returns() +func (_Storage *StorageTransactor) Store(opts *core.TransactOpts, num *big.Int) (*common.TxResult, error) { + return _Storage.contract.Transact(opts, "store", num) +} + +// Store is a paid mutator transaction binding the contract method 0x6057361d. +// +// Solidity: function store(uint256 num) returns() +func (_Storage *StorageSession) Store(num *big.Int) (*common.TxResult, error) { + return _Storage.Contract.Store(&_Storage.TransactOpts, num) +} + +// Store is a paid mutator transaction binding the contract method 0x6057361d. +// +// Solidity: function store(uint256 num) returns() +func (_Storage *StorageTransactorSession) Store(num *big.Int) (*common.TxResult, error) { + return _Storage.Contract.Store(&_Storage.TransactOpts, num) +} + +// StorageNumberChangesIterator is returned from FilterNumberChanges and is used to iterate over the raw logs and unpacked data for NumberChanges events raised by the Storage contract. +type StorageNumberChangesIterator struct { + Event *StorageNumberChanges // Event containing the contract specifics and raw log + + contract *core.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan data.Log // Log channel receiving the found contract events + sub event.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *StorageNumberChangesIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(StorageNumberChanges) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(StorageNumberChanges) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *StorageNumberChangesIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *StorageNumberChangesIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// StorageNumberChanges represents a NumberChanges event raised by the Storage contract. +type StorageNumberChanges struct { + Num *big.Int + Raw data.Log // Blockchain specific contextual infos +} + +// FilterNumberChanges is a free log retrieval operation binding the contract event 0x1161d67e3e40d64da0f22f41054120b745a28aa25e65d98d153fbaf4d3195251. +// +// Solidity: event numberChanges(uint256 num) +// func (_Storage *StorageFilterer) FilterNumberChanges(opts *core.FilterOpts) (*StorageNumberChangesIterator, error) { +// +// + +// logs, sub, err := _Storage.contract.FilterLogs(opts, "numberChanges") +// if err != nil { +// return nil, err +// } +// return &StorageNumberChangesIterator{contract: _Storage.contract, event: "numberChanges", logs: logs, sub: sub}, nil +// } + +// WatchNumberChanges is a free log subscription operation binding the contract event 0x1161d67e3e40d64da0f22f41054120b745a28aa25e65d98d153fbaf4d3195251. +// +// Solidity: event numberChanges(uint256 num) +func (_Storage *StorageFilterer) WatchNumberChanges(opts *core.WatchOpts, sink chan<- *StorageNumberChanges) (event.Subscription, error) { + + sub, err := _Storage.contract.WatchLogs(opts, "numberChanges") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.UnSubscribe() + for { + select { + case log := <-sub.EventMsgCh: + // New log arrived, parse the event and forward to the user + event := new(StorageNumberChanges) + if err := _Storage.contract.UnpackLog(event, "numberChanges", *log); err != nil { + return err + } + event.Raw = *log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_Storage *StorageFilterer) GetNumberChangesPastEvent(txHash string, ContractLogs string) ([]*StorageNumberChanges, error) { + var logRaws []*data.Log + var err error + if ContractLogs != "" { + logRaws, err = _Storage.contract.GetPastEventByCtrLog(ContractLogs) + } else if txHash != "" { + logRaws, err = _Storage.contract.GetPastEventByTxHash(txHash) + } else { + return nil, errors.New("both txHash or ContractLogs is not provided for param") + } + + if err != nil { + return nil, err + } + var events []*StorageNumberChanges + for _, logRaw := range logRaws { + event, err := _Storage.ParseNumberChanges(*logRaw) + if err != nil { + return nil, err + } + events = append(events, event) + } + return events, nil +} + +// ParseNumberChanges is a log parse operation binding the contract event 0x1161d67e3e40d64da0f22f41054120b745a28aa25e65d98d153fbaf4d3195251. +// +// Solidity: event numberChanges(uint256 num) +func (_Storage *StorageFilterer) ParseNumberChanges(log data.Log) (*StorageNumberChanges, error) { + event := new(StorageNumberChanges) + if err := _Storage.contract.UnpackLog(event, "numberChanges", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/util/define.go b/util/define.go index cb34cba..ecdbd1d 100644 --- a/util/define.go +++ b/util/define.go @@ -31,3 +31,16 @@ const ( REQUEST_TIMEOUT = 5 DIAL_TIMEOUT = 2 ) + +const ( + SchemaAdd = "schema_add" + SchemaDel = "schema_del" +) +const ( + OpTypeSchemaAdd = 1 + OpTypeSchemaDel = 2 +) + +const ( + Seqinterval = 20 +) diff --git a/util/util.go b/util/util.go index 080fbea..8dd92f7 100644 --- a/util/util.go +++ b/util/util.go @@ -35,11 +35,9 @@ func SignPlainData(privateKey string, data string) (string, error) { return "", err } sequenceZero := uint32(0) - private := key.Private(&sequenceZero) hash := crypto.Sha512Half([]byte(data)) - sigBytes, err := crypto.Sign(private, hash, nil) + sigBytes, err := crypto.Sign(key, hash, &sequenceZero, nil) if err != nil { - log.Println(err) return "", err } return crypto.B2H(sigBytes), nil