Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
17 changes: 3 additions & 14 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
run:
deadline: 15m
timeout: 15m
concurrency: 4
issues-exit-code: 1

Expand All @@ -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
Expand Down Expand Up @@ -79,7 +68,6 @@ linters-settings:
- error
- empty
- stdlib
- v1.JobInterface

tagliatelle:
# Check the struct tag name case.
Expand Down Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -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

Expand Down
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 9 additions & 0 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
type Config struct {
HTTPTendermintURL string
WSTendermintURL string
RPCTendermintURL string
ListenAddr string
LogLevel string
HandlersConfig *handlers.Config
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand Down
24 changes: 21 additions & 3 deletions internal/client/tendermint/api/api.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
Expand All @@ -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{
Expand All @@ -47,6 +55,7 @@ func NewCosmosAPI(serverURL string) (*CosmosAPI, error) {
BasePath: parsedURL.Path,
Schemes: []string{parsedURL.Scheme},
}).Query,
tendermintRPC: tendermintRPC,
}, nil
}

Expand Down Expand Up @@ -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
}
59 changes: 59 additions & 0 deletions internal/client/tendermint/api/tendermint.go
Original file line number Diff line number Diff line change
@@ -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
}
61 changes: 36 additions & 25 deletions internal/server/handlers/dto/ethblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package dto
import (
"encoding/binary"
"fmt"
"math/big"
"math/rand"
"strconv"
"time"
Expand All @@ -30,22 +29,22 @@ 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"`
Extra []byte `json:"extraData"`
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)
}
Expand All @@ -55,43 +54,38 @@ 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
MixDigest: common.Hash(block.Block.Header.ConsensusHash),
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)
}

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
Expand All @@ -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 {
Expand Down
Loading
Loading