Skip to content

Commit

Permalink
Merge pull request #50 from hyperledger/wsbackend
Browse files Browse the repository at this point in the history
Add wsbackend client
  • Loading branch information
peterbroadhurst authored Oct 24, 2023
2 parents be29668 + 7e346ca commit e4d4a70
Show file tree
Hide file tree
Showing 11 changed files with 1,140 additions and 26 deletions.
23 changes: 15 additions & 8 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
run:
tests: false
skip-dirs:
- "mocks"
- "mocks"
- "ffconfig"
linters-settings:
golint: {}
gocritic:
enabled-checks: []
disabled-checks:
- regexpMust
revive:
rules:
- name: unused-parameter
disabled: true
gosec:
excludes:
- G601 # Appears not to handle taking an address of a sub-structure, within a pointer to a structure within a loop. Which is valid and safe.
goheader:
values:
regexp:
COMPANY: .*
template: |-
Copyright © {{ YEAR }} {{ COMPANY }}
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
linters:
disable-all: false
disable:
- structcheck
enable:
- bodyclose
- deadcode
Expand All @@ -50,10 +60,7 @@ linters:
- nakedret
- revive
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
1 change: 1 addition & 0 deletions config.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ nav_order: 2
|---|-----------|----|-------------|
|count|The maximum number of times to retry|`int`|`5`
|enabled|Enables retries|`boolean`|`false`
|errorStatusCodeRegex|The regex that the error response status code must match to trigger retry|`string`|`<nil>`
|initWaitTime|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`250ms`
|maxWaitTime|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s`

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/fsnotify/fsnotify v1.6.0
github.com/go-resty/resty/v2 v2.7.0
github.com/gorilla/mux v1.8.0
github.com/hyperledger/firefly-common v1.3.0
github.com/hyperledger/firefly-common v1.3.1-0.20231003142556-37246a8c5e72
github.com/karlseguin/ccache v2.0.3+incompatible
github.com/pelletier/go-toml v1.9.5
github.com/santhosh-tekuri/jsonschema/v5 v5.0.2
Expand Down Expand Up @@ -69,7 +69,7 @@ require (
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect; indirectå
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/firefly-common v1.3.0 h1:eLFUJuPU8E5iZXYGHlXghQuN+opWG/qp7zvMKavKEPU=
github.com/hyperledger/firefly-common v1.3.0/go.mod h1:17lOH4YufiPy82LpKm8fPa/YXJ0pUyq01zK1CmklJwM=
github.com/hyperledger/firefly-common v1.3.1-0.20231003142556-37246a8c5e72 h1:l/Yo3woV2pih5EVmUHFwXr0LVr0twtXCVAdJAWInf5Q=
github.com/hyperledger/firefly-common v1.3.1-0.20231003142556-37246a8c5e72/go.mod h1:17lOH4YufiPy82LpKm8fPa/YXJ0pUyq01zK1CmklJwM=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
Expand Down
3 changes: 3 additions & 0 deletions internal/signermsgs/en_error_messges.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,7 @@ var (
MsgRequestCanceledContext = ffe("FF22063", "Request with id %s failed due to canceled context")
MsgInvalidSigner = ffe("FF22064", "Invalid signer")
MsgResultParseFailed = ffe("FF22065", "Failed to parse result (expected=%T): %s")
MsgSubscribeResponseInvalid = ffe("FF22066", "Subscription response invalid")
MsgWebSocketReconnected = ffe("FF22067", "WebSocket reconnected during JSON/RPC call")
MsgContextCancelledWSConnect = ffe("FF22068", "Context canceled while connecting WebSocket")
)
96 changes: 96 additions & 0 deletions pkg/ethereum/ethereum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright © 2023 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ethereum

import (
"math/big"

"github.com/hyperledger/firefly-common/pkg/fftypes"
"github.com/hyperledger/firefly-signer/pkg/ethtypes"
)

// txReceiptJSONRPC is the receipt obtained over JSON/RPC from the ethereum client, with gas used, logs and contract address
type TXReceiptJSONRPC struct {
BlockHash ethtypes.HexBytes0xPrefix `json:"blockHash"`
BlockNumber *ethtypes.HexInteger `json:"blockNumber"`
ContractAddress *ethtypes.AddressWithChecksum `json:"contractAddress"`
CumulativeGasUsed *ethtypes.HexInteger `json:"cumulativeGasUsed"`
From *ethtypes.AddressWithChecksum `json:"from"`
GasUsed *ethtypes.HexInteger `json:"gasUsed"`
Logs []*LogJSONRPC `json:"logs"`
Status *ethtypes.HexInteger `json:"status"`
To *ethtypes.AddressWithChecksum `json:"to"`
TransactionHash ethtypes.HexBytes0xPrefix `json:"transactionHash"`
TransactionIndex *ethtypes.HexInteger `json:"transactionIndex"`
}

// receiptExtraInfo is the version of the receipt we store under the TX.
// - We omit the full logs from the JSON/RPC
// - We omit fields already in the standardized cross-blockchain section
// - We format numbers as decimals
type ReceiptExtraInfo struct {
ContractAddress *ethtypes.AddressWithChecksum `json:"contractAddress"`
CumulativeGasUsed *fftypes.FFBigInt `json:"cumulativeGasUsed"`
From *ethtypes.AddressWithChecksum `json:"from"`
To *ethtypes.AddressWithChecksum `json:"to"`
GasUsed *fftypes.FFBigInt `json:"gasUsed"`
Status *fftypes.FFBigInt `json:"status"`
ErrorMessage *string `json:"errorMessage"`
}

// txInfoJSONRPC is the transaction info obtained over JSON/RPC from the ethereum client, with input data
type TXInfoJSONRPC struct {
BlockHash ethtypes.HexBytes0xPrefix `json:"blockHash"` // null if pending
BlockNumber *ethtypes.HexInteger `json:"blockNumber"` // null if pending
From *ethtypes.AddressWithChecksum `json:"from"`
ChainID *ethtypes.HexInteger `json:"chainID"`
Gas *ethtypes.HexInteger `json:"gas"`
GasPrice *ethtypes.HexInteger `json:"gasPrice"`
Hash ethtypes.HexBytes0xPrefix `json:"hash"`
Input ethtypes.HexBytes0xPrefix `json:"input"`
Nonce *ethtypes.HexInteger `json:"nonce"`
R *ethtypes.HexInteger `json:"r"`
S *ethtypes.HexInteger `json:"s"`
To *ethtypes.AddressWithChecksum `json:"to"`
TransactionIndex *ethtypes.HexInteger `json:"transactionIndex"` // null if pending
Type *ethtypes.HexInteger `json:"type"`
V *ethtypes.HexInteger `json:"v"`
Value *ethtypes.HexInteger `json:"value"`
}

func (t *TXInfoJSONRPC) Cost() *big.Int {
return big.NewInt(0).Mul(t.GasPrice.BigInt(), t.Gas.BigInt())
}

type LogFilterJSONRPC struct {
FromBlock *ethtypes.HexInteger `json:"fromBlock,omitempty"`
ToBlock *ethtypes.HexInteger `json:"toBlock,omitempty"`
Address *ethtypes.Address0xHex `json:"address,omitempty"`
Topics [][]ethtypes.HexBytes0xPrefix `json:"topics,omitempty"`
}

type LogJSONRPC struct {
Removed bool `json:"removed"`
LogIndex *ethtypes.HexInteger `json:"logIndex"`
TransactionIndex *ethtypes.HexInteger `json:"transactionIndex"`
BlockNumber *ethtypes.HexInteger `json:"blockNumber"`
TransactionHash ethtypes.HexBytes0xPrefix `json:"transactionHash"`
BlockHash ethtypes.HexBytes0xPrefix `json:"blockHash"`
Address *ethtypes.Address0xHex `json:"address"`
Data ethtypes.HexBytes0xPrefix `json:"data"`
Topics []ethtypes.HexBytes0xPrefix `json:"topics"`
}
32 changes: 31 additions & 1 deletion pkg/ethtypes/hexinteger.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2022 Kaleido, Inc.
// Copyright © 2023 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand All @@ -17,9 +17,12 @@
package ethtypes

import (
"context"
"encoding/json"
"fmt"
"math/big"

"github.com/hyperledger/firefly-common/pkg/i18n"
)

// HexInteger is a positive integer - serializes to JSON as an 0x hex string (no leading zeros), and parses flexibly depending on the prefix (so 0x for hex, or base 10 for plain string / float64)
Expand Down Expand Up @@ -62,10 +65,37 @@ func (h *HexInteger) BigInt() *big.Int {
return (*big.Int)(h)
}

func (h *HexInteger) Uint64() uint64 {
return h.BigInt().Uint64()
}

func (h *HexInteger) Int64() int64 {
return h.BigInt().Int64()
}

func NewHexIntegerU64(i uint64) *HexInteger {
return (*HexInteger)(big.NewInt(0).SetUint64(i))
}

func NewHexInteger64(i int64) *HexInteger {
return (*HexInteger)(big.NewInt(i))
}

func NewHexInteger(i *big.Int) *HexInteger {
return (*HexInteger)(i)
}

func (h *HexInteger) Scan(src interface{}) error {
switch src := src.(type) {
case nil:
return nil
case int64:
*h = *NewHexInteger64(src)
return nil
case uint64:
*h = *NewHexIntegerU64(src)
return nil
default:
return i18n.NewError(context.Background(), i18n.MsgTypeRestoreFailed, src, h)
}
}
15 changes: 15 additions & 0 deletions pkg/ethtypes/hexinteger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ func TestHexIntegerOk(t *testing.T) {
assert.Nil(t, testStruct.I4)
assert.Equal(t, int64(0), testStruct.I4.BigInt().Int64()) // BigInt() safe on nils
assert.Nil(t, testStruct.I5)
assert.Equal(t, int64(12345), testStruct.I3.Int64())
assert.Equal(t, uint64(12345), testStruct.I3.Uint64())

jsonSerialized, err := json.Marshal(&testStruct)
assert.JSONEq(t, `{
Expand Down Expand Up @@ -121,4 +123,17 @@ func TestHexIntConstructors(t *testing.T) {
assert.Equal(t, int64(12345), NewHexInteger(big.NewInt(12345)).BigInt().Int64())
assert.Equal(t, "0x0", NewHexInteger(big.NewInt(0)).String())
assert.Equal(t, "0x1", NewHexInteger(big.NewInt(1)).String())
assert.Equal(t, "0x1", NewHexIntegerU64(1).String())
}

func TestScan(t *testing.T) {
i := &HexInteger{}
err := i.Scan(false)
err = i.Scan(nil)
assert.NoError(t, err)
assert.Equal(t, "0x0", i.String())
i.Scan(int64(5555))
assert.Equal(t, "0x15b3", i.String())
i.Scan(uint64(9999))
assert.Equal(t, "0x270f", i.String())
}
49 changes: 36 additions & 13 deletions pkg/rpcbackend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ const (
RPCCodeInternalError RPCCode = -32603
)

type RPC interface {
CallRPC(ctx context.Context, result interface{}, method string, params ...interface{}) *RPCError
}

// Backend performs communication with a backend
type Backend interface {
CallRPC(ctx context.Context, result interface{}, method string, params ...interface{}) *RPCError
RPC
SyncRequest(ctx context.Context, rpcReq *RPCRequest) (rpcRes *RPCResponse, err error)
}

Expand Down Expand Up @@ -90,11 +94,18 @@ func (e *RPCError) Error() error {
return fmt.Errorf(e.Message)
}

func (e *RPCError) String() string {
return e.Message
}

type RPCResponse struct {
JSONRpc string `json:"jsonrpc"`
ID *fftypes.JSONAny `json:"id"`
Result *fftypes.JSONAny `json:"result,omitempty"`
Error *RPCError `json:"error,omitempty"`
// Only for subscription notifications
Method string `json:"method,omitempty"`
Params *fftypes.JSONAny `json:"params,omitempty"`
}

func (r *RPCResponse) Message() string {
Expand All @@ -111,19 +122,11 @@ func (rc *RPCClient) allocateRequestID(req *RPCRequest) string {
}

func (rc *RPCClient) CallRPC(ctx context.Context, result interface{}, method string, params ...interface{}) *RPCError {
req := &RPCRequest{
JSONRpc: "2.0",
Method: method,
Params: make([]*fftypes.JSONAny, len(params)),
}
for i, param := range params {
b, err := json.Marshal(param)
if err != nil {
return &RPCError{Code: int64(RPCCodeInvalidRequest), Message: i18n.NewError(ctx, signermsgs.MsgInvalidParam, i, method, err).Error()}
}
req.Params[i] = fftypes.JSONAnyPtrBytes(b)
rpcReq, rpcErr := buildRequest(ctx, method, params)
if rpcErr != nil {
return rpcErr
}
res, err := rc.SyncRequest(ctx, req)
res, err := rc.SyncRequest(ctx, rpcReq)
if err != nil {
if res.Error != nil && res.Error.Code != 0 {
return res.Error
Expand Down Expand Up @@ -218,3 +221,23 @@ func RPCErrorResponse(err error, id *fftypes.JSONAny, code RPCCode) *RPCResponse
},
}
}

func NewRPCError(ctx context.Context, code RPCCode, msg i18n.ErrorMessageKey, inserts ...interface{}) *RPCError {
return &RPCError{Code: int64(code), Message: i18n.NewError(ctx, msg, inserts...).Error()}
}

func buildRequest(ctx context.Context, method string, params []interface{}) (*RPCRequest, *RPCError) {
req := &RPCRequest{
JSONRpc: "2.0",
Method: method,
Params: make([]*fftypes.JSONAny, len(params)),
}
for i, param := range params {
b, err := json.Marshal(param)
if err != nil {
return nil, NewRPCError(ctx, RPCCodeInvalidRequest, signermsgs.MsgInvalidParam, i, method, err)
}
req.Params[i] = fftypes.JSONAnyPtrBytes(b)
}
return req, nil
}
Loading

0 comments on commit e4d4a70

Please sign in to comment.