diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..31ba60a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: Pull Request + +on: + pull_request: + branches: + - '**' + +jobs: + build: + runs-on: ubuntu-latest + name: Build + steps: + - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.23' + - name: Build + env: + CGO_ENABLED: 0 + run: go build -ldflags="-extldflags=-static" -o ./bin/shuttle ./cmd/ + + lint: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: '1.23' + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.62.2 + args: --timeout 15m --config .golangci.yaml + skip-cache: true diff --git a/.golangci.yaml b/.golangci.yaml index 8a979f3..f425746 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,5 +1,6 @@ run: deadline: 15m + timeout: 15m concurrency: 4 issues-exit-code: 1 @@ -23,18 +24,6 @@ linters: # Seems that there is no need to deny the use of any packages - depguard - # Deprecated: - - deadcode # Replaced by unused - - exhaustivestruct # Replaced by exhaustruct - - golint # Replaced by revive - - maligned # Replaced by govet 'fieldalignment' - - nosnakecase # Replaced by revive - - scopelint # Replaced by exportloopref - - structcheck # Replaced by unused - - varcheck # Replaced by unused - - ifshort # Repository was archived by the owner - - interfacer # Repository was archived by the owner - linters-settings: maligned: suggest-new: true @@ -79,7 +68,6 @@ linters-settings: - error - empty - stdlib - - v1.JobInterface tagliatelle: # Check the struct tag name case. @@ -119,12 +107,13 @@ linters-settings: - .WithStack( - (context.Context).Err() - gomnd: + mnd: # List of numbers to exclude from analysis. # The numbers should be written as string. # Values always ignored: "1", "1.0", "0" and "0.0" # Default: [] ignored-numbers: + - '1' - '2' - '0o400' - '0o444' diff --git a/Dockerfile.dev b/Dockerfile.dev index 26c0c9b..5b97c4f 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,6 +1,6 @@ FROM golang:1.23.3-alpine3.20 as builder -ARG GOLANGCI_LINT_VERSION=1.55.2 +ARG GOLANGCI_LINT_VERSION=1.62.2 WORKDIR /develop diff --git a/Makefile b/Makefile index 8fb1fd4..ebe7bdb 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,7 @@ SWAGGER_API = internal/client/tendermint/api/internal/txs \ internal/client/tendermint/api/internal/blocks \ - internal/client/tendermint/api/internal/bank \ - internal/client/tendermint/api/internal/auth + internal/client/tendermint/api/internal/bank .PHONY: view view: diff --git a/cmd/config/config.go b/cmd/config/config.go index f53dcc6..bd86470 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -9,6 +9,7 @@ import ( type Config struct { HTTPTendermintURL string WSTendermintURL string + RPCTendermintURL string ListenAddr string LogLevel string HandlersConfig *handlers.Config @@ -38,6 +39,14 @@ func (c *Config) BuildFlags() []cli.Flag { Value: "http://localhost:1317", EnvVars: []string{"HTTP_TENDERMINT_URL"}, }, + &cli.StringFlag{ + Name: "rpc-tendermint-url", + Usage: "set up a Tendermint RPC node url", + Destination: &c.RPCTendermintURL, + Required: true, + Value: "http://localhost", + EnvVars: []string{"RPC_TENDERMINT_URL"}, + }, &cli.StringFlag{ Name: "http-listen-addr", Usage: "set up listening address", diff --git a/cmd/main.go b/cmd/main.go index d256c4f..3f8a3e4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -16,7 +16,7 @@ import ( func runServer(cfg config.Config) error { log := config.LoadLogger(cfg.LogLevel) - cl, err := api.NewCosmosAPI(cfg.HTTPTendermintURL) + cl, err := api.NewCosmosAPI(cfg.HTTPTendermintURL, cfg.RPCTendermintURL, cfg.WSTendermintURL) if err != nil { return fmt.Errorf("connect to tendermint api: %w", err) } diff --git a/docker-compose.yml b/docker-compose.yml index e171f72..96ad173 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: environment: HTTP_TENDERMINT_URL: https://cosmos-rest.publicnode.com WS_TENDERMINT_URL: https://cosmos-rest.publicnode.com/websocket + RPC_TENDERMINT_URL: https://cosmos-rpc.publicnode.com:443 HTTP_LISTEN_ADDR: 0.0.0.0:9182 LOG_LEVEL: info BECH_PREFIX: cosmos diff --git a/go.mod b/go.mod index ccb2aad..defcffc 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/fairmath/shuttle -go 1.22.7 - -toolchain go1.22.9 +go 1.23 require ( github.com/cometbft/cometbft v0.38.15 diff --git a/internal/client/tendermint/api/api.go b/internal/client/tendermint/api/api.go index 3918376..949d90f 100644 --- a/internal/client/tendermint/api/api.go +++ b/internal/client/tendermint/api/api.go @@ -1,6 +1,7 @@ package api import ( + "context" "crypto/sha256" "encoding/hex" "fmt" @@ -23,12 +24,19 @@ type CosmosAPI struct { blocksAPI bservice.ClientService txsAPI tservice.ClientService bankAPI bankService.ClientService + + tendermintRPC *TendermintRPC } -func NewCosmosAPI(serverURL string) (*CosmosAPI, error) { - parsedURL, err := url.Parse(serverURL) +func NewCosmosAPI(httpServerURL, rpcServerURL, wsURL string) (*CosmosAPI, error) { + parsedURL, err := url.Parse(httpServerURL) if err != nil { - return nil, fmt.Errorf("parse url: '%s': %w", serverURL, err) + return nil, fmt.Errorf("parse url: '%s': %w", httpServerURL, err) + } + + tendermintRPC, err := NewTendermintRPC(rpcServerURL, wsURL) + if err != nil { + return nil, fmt.Errorf("create tendermint rpc connection: %w", err) } return &CosmosAPI{ @@ -47,6 +55,7 @@ func NewCosmosAPI(serverURL string) (*CosmosAPI, error) { BasePath: parsedURL.Path, Schemes: []string{parsedURL.Scheme}, }).Query, + tendermintRPC: tendermintRPC, }, nil } @@ -120,3 +129,12 @@ func (t *CosmosAPI) GetBalance(prefix, denom string, addr strfmt.Base64) (string return res.Payload.Balance.Amount, nil } + +func (t *CosmosAPI) BlockByHash(ctx context.Context, hash []byte) (Block, error) { + res, err := t.tendermintRPC.BlockByHash(ctx, hash) + if err != nil { + return Block{}, fmt.Errorf("cosmos api: block by hash: %w", err) + } + + return res, nil +} diff --git a/internal/client/tendermint/api/tendermint.go b/internal/client/tendermint/api/tendermint.go new file mode 100644 index 0000000..daa57bb --- /dev/null +++ b/internal/client/tendermint/api/tendermint.go @@ -0,0 +1,59 @@ +package api + +import ( + "context" + "fmt" + "strconv" + + "github.com/cometbft/cometbft/rpc/client/http" + "github.com/go-openapi/strfmt" + + "github.com/fairmath/shuttle/internal/client/tendermint/api/internal/blocks/models" +) + +type TendermintRPC struct { + client *http.HTTP +} + +func NewTendermintRPC(rpcServerURL, wsServerURL string) (*TendermintRPC, error) { + client, err := http.New(rpcServerURL, wsServerURL) + if err != nil { + return nil, fmt.Errorf("connect to tendermint rpc: %w", err) + } + + return &TendermintRPC{ + client: client, // todo: use tendermint ws connection instead of WebsocketPool + }, nil +} + +func (t *TendermintRPC) BlockByHash(ctx context.Context, hash []byte) (Block, error) { + block, err := t.client.BlockByHash(ctx, hash) + if err != nil { + return Block{}, fmt.Errorf("block by hash: %w", err) + } + + return Block{ + BlockID: &models.CosmosBlockBlockID{ + Hash: block.BlockID.Hash.Bytes(), + }, + Block: &models.CosmosBlockBlock{ + Header: &models.CosmosBlockBlockHeader{ + AppHash: strfmt.Base64(block.Block.AppHash), + ChainID: block.Block.ChainID, + ConsensusHash: block.Block.ConsensusHash.Bytes(), + DataHash: block.Block.DataHash.Bytes(), + EvidenceHash: block.Block.Evidence.Evidence.Hash(), + Height: strconv.FormatInt(block.Block.Height, 10), + LastBlockID: &models.CosmosBlockBlockHeaderLastBlockID{ + Hash: strfmt.Base64(block.Block.LastCommit.BlockID.Hash), + }, + LastCommitHash: block.Block.LastCommitHash.Bytes(), + LastResultsHash: strfmt.Base64(block.Block.LastResultsHash), + NextValidatorsHash: block.Block.NextValidatorsHash.Bytes(), + ProposerAddress: block.Block.ProposerAddress.Bytes(), + Time: strfmt.DateTime(block.Block.Time), + ValidatorsHash: block.Block.ValidatorsHash.Bytes(), + }, + }, + }, nil +} diff --git a/internal/server/handlers/dto/ethblock.go b/internal/server/handlers/dto/ethblock.go index 0fc5518..56b9f6b 100644 --- a/internal/server/handlers/dto/ethblock.go +++ b/internal/server/handlers/dto/ethblock.go @@ -3,7 +3,6 @@ package dto import ( "encoding/binary" "fmt" - "math/big" "math/rand" "strconv" "time" @@ -30,8 +29,8 @@ type Header struct { //nolint:tagliatelle // ethereum serialized json TxHash common.Hash `json:"transactionsRoot"` ReceiptHash common.Hash `json:"receiptsRoot"` Bloom ethtypes.Bloom `json:"logsBloom"` - Difficulty *big.Int `json:"difficulty"` - Number *big.Int `json:"number"` + Difficulty HexUint64 `json:"difficulty"` + Number HexUint64 `json:"number"` GasLimit HexUint64 `json:"gasLimit"` GasUsed HexUint64 `json:"gasUsed"` Time HexUint64 `json:"timestamp"` @@ -39,13 +38,13 @@ type Header struct { //nolint:tagliatelle // ethereum serialized json MixDigest common.Hash `json:"mixHash"` Nonce ethtypes.BlockNonce `json:"nonce"` Size HexUint64 `json:"size"` - TotalDifficulty *big.Int `json:"total_difficulty"` - Transactions []Tx `json:"transactions,omitempty"` + TotalDifficulty HexUint64 `json:"total_difficulty"` + Transactions []any `json:"transactions,omitempty"` Uncles []string `json:"uncles"` } func FromCosmosBlock(block *api.Block) (Header, error) { - num, err := strconv.ParseInt(block.Block.Header.Height, 10, 64) + num, err := strconv.ParseUint(block.Block.Header.Height, 10, 64) if err != nil { return Header{}, fmt.Errorf("height conversion '%s' -> int64: %w", block.Block.Header.Height, err) } @@ -55,7 +54,7 @@ func FromCosmosBlock(block *api.Block) (Header, error) { return Header{ Hash: common.Hash(block.BlockID.Hash), - Number: big.NewInt(num), + Number: HexUint64(num), Coinbase: common.Address(block.Block.Header.ProposerAddress), ParentHash: common.Hash(block.Block.Header.LastBlockID.Hash), Nonce: ethtypes.BlockNonce(binary.LittleEndian.AppendUint64([]byte{}, rand.Uint64())), //nolint:gosec,lll // no reason for crypto rand @@ -63,20 +62,20 @@ func FromCosmosBlock(block *api.Block) (Header, error) { ReceiptHash: common.Hash(block.Block.Header.DataHash), UncleHash: common.Hash(block.Block.Header.NextValidatorsHash), Root: common.Hash(block.Block.Header.EvidenceHash), - TxHash: common.Hash{}, + TxHash: firstTxHash(block.Block.Data.Txs), Extra: []byte{}, - Difficulty: big.NewInt(int64(0x1046bb7e3f8)), //nolint:gomnd // need to translate from cosmos chain - TotalDifficulty: big.NewInt(int64(0x1046bb7e3f8)), //nolint:gomnd // need to translate from cosmos chain + Difficulty: HexUint64(0x1046bb7e3f8), //nolint:mnd // need to translate from cosmos chain + TotalDifficulty: HexUint64(0x1046bb7e3f8), //nolint:mnd // need to translate from cosmos chain Size: HexUint64(blockSz), Transactions: nil, Uncles: []string{}, - Time: HexUint64(time.Time(block.Block.Header.Time).Unix()), + Time: HexUint64(time.Time(block.Block.Header.Time).Unix()), //nolint:gosec // time always greater than zero }, nil } -func FromCosmosBlockWithTxs(block *api.BlockWithTxs) (Header, error) { - num, err := strconv.ParseInt(block.Block.Header.Height, 10, 64) +func FromCosmosBlockWithTxs(block *api.BlockWithTxs, fullTransactions bool) (Header, error) { + num, err := strconv.ParseUint(block.Block.Header.Height, 10, 64) if err != nil { return Header{}, fmt.Errorf("height conversion '%s' -> int64: %w", block.Block.Header.Height, err) } @@ -84,14 +83,9 @@ func FromCosmosBlockWithTxs(block *api.BlockWithTxs) (Header, error) { blockBin, _ := block.Block.MarshalBinary() blockSz := len(blockBin) - txs, err := ToEthTxs(block) - if err != nil { - return Header{}, fmt.Errorf("tx convert: %w", err) - } - - return Header{ + res := Header{ Hash: common.Hash(block.BlockID.Hash), - Number: big.NewInt(num), + Number: HexUint64(num), Coinbase: common.Address(block.Block.Header.ProposerAddress), ParentHash: common.Hash(block.Block.Header.LastBlockID.Hash), Nonce: ethtypes.BlockNonce(binary.LittleEndian.AppendUint64([]byte{}, rand.Uint64())), //nolint:gosec,lll // no reason for crypto rand @@ -101,14 +95,31 @@ func FromCosmosBlockWithTxs(block *api.BlockWithTxs) (Header, error) { Root: common.Hash(block.Block.Header.EvidenceHash), TxHash: firstTxHash(block.Block.Data.Txs), Extra: []byte{}, - Difficulty: big.NewInt(int64(0x1046bb7e3f8)), //nolint:gomnd // need to translate from cosmos chain - TotalDifficulty: big.NewInt(int64(0x1046bb7e3f8)), //nolint:gomnd // need to translate from cosmos chain + Difficulty: HexUint64(0x1046bb7e3f8), //nolint:mnd // need to translate from cosmos chain + TotalDifficulty: HexUint64(0x1046bb7e3f8), //nolint:mnd // need to translate from cosmos chain Size: HexUint64(blockSz), - Transactions: txs, Uncles: []string{}, + Time: HexUint64(time.Time(block.Block.Header.Time).Unix()), //nolint:gosec // time always greater than zero + } - Time: HexUint64(time.Time(block.Block.Header.Time).Unix()), - }, nil + txs, err := ToEthTxs(block) + if err != nil { + return Header{}, fmt.Errorf("tx convert: %w", err) + } + + rtxs := make([]any, 0, len(txs)) + + for _, tx := range txs { + if fullTransactions { + rtxs = append(rtxs, tx) + } else { + rtxs = append(rtxs, tx.Hash) + } + } + + res.Transactions = rtxs + + return res, nil } func firstTxHash(data []strfmt.Base64) common.Hash { diff --git a/internal/server/handlers/dto/ethtxs.go b/internal/server/handlers/dto/ethtxs.go index 165711b..b0e3990 100644 --- a/internal/server/handlers/dto/ethtxs.go +++ b/internal/server/handlers/dto/ethtxs.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "errors" "fmt" - "math/big" "slices" "strconv" @@ -23,7 +22,7 @@ type TxLog struct { //nolint:tagliatelle // ethereum serialized json Address common.Address `json:"address"` Topics []common.Hash `json:"topics"` Data string `json:"data"` - BlockNumber *big.Int `json:"blockNumber"` + BlockNumber HexUint64 `json:"blockNumber"` TransactionHash common.Hash `json:"transactionHash"` TransactionIndex HexUint64 `json:"transactionIndex"` BlockHash common.Hash `json:"blockHash"` @@ -33,7 +32,7 @@ type TxLog struct { //nolint:tagliatelle // ethereum serialized json type TxReceipt struct { //nolint:tagliatelle // ethereum serialized json BlockHash common.Hash `json:"blockHash"` - BlockNumber *big.Int `json:"blockNumber"` + BlockNumber HexUint64 `json:"blockNumber"` ContractAddress common.Address `json:"contractAddress"` CumulativeGasUsed HexUint64 `json:"cumulativeGasUsed"` EffectiveGasPrice HexUint64 `json:"effectiveGasPrice"` @@ -50,7 +49,7 @@ type TxReceipt struct { //nolint:tagliatelle // ethereum serialized json type Tx struct { //nolint:tagliatelle // ethereum serialized json BlockHash common.Hash `json:"blockHash"` - BlockNumber *big.Int `json:"blockNumber"` + BlockNumber HexUint64 `json:"blockNumber"` Creates common.Address `json:"creates"` From common.Address `json:"from"` Gas HexUint64 `json:"gas"` @@ -72,7 +71,7 @@ type Tx struct { //nolint:tagliatelle // ethereum serialized json } func ToEthTxs(blockTx *api.BlockWithTxs) ([]Tx, error) { - num, err := strconv.ParseInt(blockTx.Block.Header.Height, 10, 64) + num, err := strconv.ParseUint(blockTx.Block.Header.Height, 10, 64) if err != nil { return nil, fmt.Errorf("height %s convert: %w", blockTx.Block.Header.Height, err) } @@ -109,19 +108,19 @@ func ToEthTxs(blockTx *api.BlockWithTxs) ([]Tx, error) { pk, _ := tx.AuthInfo.SignerInfos[0].PublicKey.MarshalBinary() rtx = append(rtx, Tx{ BlockHash: common.Hash(blockTx.BlockID.Hash), - BlockNumber: big.NewInt(num), + BlockNumber: HexUint64(num), Creates: signer, From: signer, To: convertAddress(tx.AuthInfo.Fee.Granter), - Gas: HexUint64(gas), - GasPrice: HexUint64(amount / gas), + Gas: HexUint64(gas), //nolint:gosec // always positive or 0 + GasPrice: HexUint64(amount / gas), //nolint:gosec // always positive or 0 Hash: common.Hash(blockTx.Block.Data.Txs[i]), Input: "0x" + hex.EncodeToString(msgs), - TransactionIndex: HexUint64(i), - Value: HexUint64(amount), + TransactionIndex: HexUint64(i), //nolint:gosec // always positive or 0 + Value: HexUint64(amount), //nolint:gosec // always positive or 0 Type: 2, ChainID: 1, - V: 0xbd, //nolint:gomnd // seems like there is no V, R and S in cosmos public keys, need investigate in details + V: 0xbd, //nolint:mnd // seems like there is no V, R and S in cosmos public keys, need investigate in details R: "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", S: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", StandardV: 0, @@ -134,7 +133,7 @@ func ToEthTxs(blockTx *api.BlockWithTxs) ([]Tx, error) { } func ToEthTxReceipt(block *api.BlockWithTxs, tx *api.TxInfo) (*TxReceipt, error) { - num, err := strconv.ParseInt(block.Block.Header.Height, 10, 64) + num, err := strconv.ParseUint(block.Block.Header.Height, 10, 64) if err != nil { return nil, fmt.Errorf("height %s convert: %w", block.Block.Header.Height, err) } @@ -158,7 +157,7 @@ func ToEthTxReceipt(block *api.BlockWithTxs, tx *api.TxInfo) (*TxReceipt, error) for i, t := range block.Block.Data.Txs { if slices.Equal(t, txHash) { - txIndex = uint64(i) + txIndex = uint64(i) //nolint:gosec // counter break } @@ -180,14 +179,14 @@ func ToEthTxReceipt(block *api.BlockWithTxs, tx *api.TxInfo) (*TxReceipt, error) return &TxReceipt{ BlockHash: common.Hash(block.BlockID.Hash), - BlockNumber: big.NewInt(num), + BlockNumber: HexUint64(num), ContractAddress: signer, - CumulativeGasUsed: HexUint64(gas), - EffectiveGasPrice: HexUint64(amount / gas), + CumulativeGasUsed: HexUint64(gas), //nolint:gosec // always positive or 0 + EffectiveGasPrice: HexUint64(amount / gas), //nolint:gosec // always positive or 0 From: signer, - GasUsed: HexUint64(gas), + GasUsed: HexUint64(gas), //nolint:gosec // always positive or 0 Logs: []TxLog{}, - Status: HexUint64(status), + Status: HexUint64(status), //nolint:gosec // always positive or 0 To: convertAddress(tx.Tx.AuthInfo.Fee.Granter), TransactionHash: common.Hash(txHash), TransactionIndex: HexUint64(txIndex), diff --git a/internal/server/handlers/eth.go b/internal/server/handlers/eth.go index 3818f84..cfb46ba 100644 --- a/internal/server/handlers/eth.go +++ b/internal/server/handlers/eth.go @@ -1,9 +1,12 @@ package handlers import ( + "context" "encoding/hex" + "errors" "fmt" "math/big" + "strconv" "strings" "github.com/go-openapi/strfmt" @@ -19,6 +22,8 @@ type EthProxy interface { GetBlockTxsByHeight(height string) (*api.BlockWithTxs, error) GetBalance(prefix, denom string, addr strfmt.Base64) (string, error) GetTx(hash string) (*api.TxInfo, error) + + BlockByHash(ctx context.Context, hash []byte) (api.Block, error) } type EthServer struct { @@ -41,7 +46,23 @@ func (e *EthServer) Name() string { return e.name } -func (e *EthServer) GetBlockByNumber(height string, _ bool) (any, error) { +func (e *EthServer) BlockNumber() (any, error) { + const latest = "latest" + + b, err := e.target.GetBlockByHeight(latest) + if err != nil { + return nil, fmt.Errorf("get block '%s': %w", latest, err) + } + + num, err := strconv.ParseInt(b.Block.Header.Height, 10, 64) + if err != nil { + return nil, fmt.Errorf("parse block number: %w", err) + } + + return "0x" + strconv.FormatInt(num, 16), nil +} + +func (e *EthServer) GetBlockByNumber(height string, fullTransactions bool) (any, error) { var ( block *api.BlockWithTxs err error @@ -60,7 +81,7 @@ func (e *EthServer) GetBlockByNumber(height string, _ bool) (any, error) { return nil, fmt.Errorf("get %s txs: %w", height, err) } - ethBlock, err := dto.FromCosmosBlockWithTxs(block) + ethBlock, err := dto.FromCosmosBlockWithTxs(block, fullTransactions) if err != nil { return nil, fmt.Errorf("block conversion: %w", err) } @@ -85,11 +106,11 @@ func (e *EthServer) GetBalance(addr string, _ string) (string, error) { value := big.NewInt(0) - if value, ok = value.SetString(amount, 10); !ok { //nolint:gomnd // base + if value, ok = value.SetString(amount, 10); !ok { //nolint:mnd // base return "", fmt.Errorf("parse balance: %w", err) } - return "0x" + value.Text(16), nil //nolint:gomnd // base + return "0x" + value.Text(16), nil //nolint:mnd // base } func (e *EthServer) GetTransactionReceipt(hash string) (any, error) { @@ -112,3 +133,122 @@ func (e *EthServer) GetTransactionReceipt(hash string) (any, error) { return res, nil } + +func (e *EthServer) GetTransactionByHash(hash string) (any, error) { + trimHash, _ := strings.CutPrefix(hash, "0x") + + txInfo, err := e.target.GetTx(trimHash) + if err != nil { + return nil, fmt.Errorf("get tx by hash: %w", err) + } + + block, err := e.target.GetBlockTxsByHeight(txInfo.TxResponse.Height) + if err != nil { + return nil, fmt.Errorf("get tx by height: %s: %w", txInfo.TxResponse.Height, err) + } + + txs, err := dto.ToEthTxs(block) + if err != nil { + return nil, fmt.Errorf("eth tx converter: %w", err) + } + + for _, t := range txs { + if t.Hash.String() == hash { + return t, nil + } + } + + return nil, errors.New("not found") +} + +func (e *EthServer) GetBlockByHash(hash string, fullTransactions bool) (any, error) { + trimHash, _ := strings.CutPrefix(hash, "0x") + + h, err := hex.DecodeString(trimHash) + if err != nil { + return nil, fmt.Errorf("parse hash: %s: %w", trimHash, err) + } + + cosmosBlock, err := e.target.BlockByHash(context.Background(), h) + if err != nil { + return nil, fmt.Errorf("get block by hash: %w", err) + } + + num, err := strconv.ParseUint(cosmosBlock.Block.Header.Height, 10, 64) + if err != nil { + return nil, fmt.Errorf("parse block height: %w", err) + } + + res, err := e.GetBlockByNumber("0x"+strconv.FormatUint(num, 16), fullTransactions) + if err != nil { + return nil, fmt.Errorf("block by num: %w", err) + } + + return res, nil +} + +func (e *EthServer) GetTransactionByBlockNumberAndIndex(height, txIndex string) (any, error) { + trimIndex, _ := strings.CutPrefix(txIndex, "0x") + + txi, err := strconv.ParseInt(txIndex, 16, 64) + if err != nil { + return nil, fmt.Errorf("parse index: %s: %w", trimIndex, err) + } + + block, err := e.target.GetBlockTxsByHeight(height) + if err != nil { + return nil, fmt.Errorf("get tx by height: %s: %w", height, err) + } + + txs, err := dto.ToEthTxs(block) + if err != nil { + return nil, fmt.Errorf("eth tx converter: %w", err) + } + + for i, t := range txs { + if int64(i) == txi { + return t, nil + } + } + + return nil, errors.New("tx not found") +} + +func (e *EthServer) GetTransactionByBlockHashAndIndex(hash, txIndex string) (any, error) { + trimIndex, _ := strings.CutPrefix(txIndex, "0x") + + txi, err := strconv.ParseInt(txIndex, 16, 64) + if err != nil { + return nil, fmt.Errorf("parse index: %s: %w", trimIndex, err) + } + + trimHash, _ := strings.CutPrefix(hash, "0x") + + h, err := hex.DecodeString(trimHash) + if err != nil { + return nil, fmt.Errorf("parse hash: %s: %w", trimHash, err) + } + + cosmosBlock, err := e.target.BlockByHash(context.Background(), h) + if err != nil { + return nil, fmt.Errorf("get block by hash: %w", err) + } + + block, err := e.target.GetBlockTxsByHeight(cosmosBlock.Block.Header.Height) + if err != nil { + return nil, fmt.Errorf("get tx by height: %s: %w", cosmosBlock.Block.Header.Height, err) + } + + txs, err := dto.ToEthTxs(block) + if err != nil { + return nil, fmt.Errorf("eth tx converter: %w", err) + } + + for i, t := range txs { + if int64(i) == txi { + return t, nil + } + } + + return nil, errors.New("tx not found") +} diff --git a/internal/server/websocket.go b/internal/server/websocket.go index 65542d5..5ae2502 100644 --- a/internal/server/websocket.go +++ b/internal/server/websocket.go @@ -22,6 +22,7 @@ import ( const ETHSubscribeMethod = "eth_subscribe" +// todo: use tendermint ws connection instead of WebsocketPool(see client/tendermint/api/internal/tendermint.go) type WebsocketPool struct { log *zap.Logger tendermintWS string @@ -67,7 +68,7 @@ func NewWebsocketPool(tendermintWS string, log *zap.Logger) (*WebsocketPool, err func (wp *WebsocketPool) ServeHTTP(w http.ResponseWriter, r *http.Request) { upgrader := websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { + CheckOrigin: func(_ *http.Request) bool { return true }, } @@ -93,6 +94,7 @@ func (wp *WebsocketPool) serveConnection(ctx context.Context, connection *websoc subscribers := make(chan uint32) proxyDone := make(chan struct{}) + //nolint:contextcheck // fix after refactoring go func() { defer close(done) @@ -201,8 +203,8 @@ func (wp *WebsocketPool) startProxy(conn *websocket.Conn, id uint64, subscribers UncleHash: common.Hash{}, Root: common.Hash(data.Block.Header.EvidenceHash), Extra: []byte{}, - Difficulty: big.NewInt(int64(0x1046bb7e3f8)), //nolint:gomnd // need to translate from cosmos chain - Time: uint64(data.Block.Header.Time.Unix()), + Difficulty: big.NewInt(int64(0x1046bb7e3f8)), //nolint:mnd // need to translate from cosmos chain + Time: uint64(data.Block.Header.Time.Unix()), //nolint:gosec // always positive or 0 } msg := JSONRpcMsg{