Commit 29a2b9e0 authored by luxq's avatar luxq

add code

parents
Pipeline #877 failed with stages
.idea
data
build
FROM golang:1.24-alpine AS build
# Set up dependencies
ENV PACKAGES build-base
# Install dependencies
RUN apk add --update $PACKAGES
# Add source files
WORKDIR /build
COPY ./ /build/token-bridge
RUN cd /build/token-bridge && go mod tidy && go build -v -o /tmp/validator ./cmd/validator
FROM alpine
WORKDIR /app
COPY ./config.toml /app/config.toml
COPY --from=build /tmp/validator /usr/bin/validator
EXPOSE 8080
\ No newline at end of file
.PHONY: default all clean dev
GOBIN = $(shell pwd)/build/bin
default: all
all: validator
validator:
go build $(BUILD_FLAGS) -v -o=${GOBIN}/$@ ./cmd/validator
dev:
go build $(BUILD_FLAGS) -v -o=${GOBIN}/$@ -gcflags "all=-N -l" ./cmd
docker:
docker build -t token-bridge:latest -f ./Dockerfile .
docker tag token-bridge:latest token-bridge:$${TAG:-latest}
.PHONY: docker
This diff is collapsed.
package main
import (
"code.wuban.net.cn/movabridge/token-bridge/chain"
"code.wuban.net.cn/movabridge/token-bridge/config"
"code.wuban.net.cn/movabridge/token-bridge/dao"
"flag"
"os"
"os/signal"
"syscall"
log "github.com/sirupsen/logrus"
)
func init() {
log.SetFormatter(&log.TextFormatter{
FullTimestamp: true,
})
}
func main() {
flag.Parse()
conf, err := config.New()
if err != nil {
panic(err)
}
d, err := dao.New(conf)
if err != nil {
panic(err)
}
if conf.Debug {
log.SetLevel(log.DebugLevel)
}
syncers := make([]*chain.ChainSync, 0)
for _, chainConfig := range conf.Chains {
syncer := chain.NewChainSync(chainConfig, d)
go syncer.Start()
syncers = append(syncers, syncer)
}
// Set up signal handling
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
// Wait for termination signal
sig := <-sigCh
log.WithField("signal", sig.String()).Info("received termination signal, shutting down")
// Stop all chain sync instances
for _, syncer := range syncers {
syncer.Stop()
}
log.Info("graceful shutdown completed")
os.Exit(0)
}
debug = true
[[chains]]
name = "cad"
rpc = "https://1rpc.io/sepolia"
initial_height = 1
batch_block = 100
confirm_block_count = 2
bridge_contract = "0x19Bd3121fEC07F047ac991e7b35C265a2B1F51eE"
validator_private_key = "af426ee077b1eb602fd011714cccf4398d0fc879fd3001a4c648487b1c3e7d2b"
[[chains]]
name = "mova"
rpc = "https://pegasus.rpc.caduceus.foundation"
initial_height = 1
batch_block = 100
confirm_block_count = 2
bridge_contract = "0xC160b598505c034A820f19e1C8b83ee5d2805A41"
validator_private_key = "af426ee077b1eb602fd011714cccf4398d0fc879fd3001a4c648487b1c3e7d2b"
[mysql]
host = "bridgedb"
port = 3306
user = "root"
password = "XN2UARuys3zy4Oux"
database = "bridge"
max_conn = 20
max_idle_conn = 10
[server]
listen = ":8080"
invalid_headers = [
"Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)",
]
\ No newline at end of file
package config
import (
"flag"
"github.com/BurntSushi/toml"
)
type Config struct {
Debug bool
Chains map[string]*ChainConfig `toml:"chains"`
MySQL MySQLConfig
Server ServerConfig
}
type ChainConfig struct {
Name string `toml:"name"`
RPC string `toml:"rpc"`
InitialHeight int64 `toml:"initial_height"`
BatchBlock int `toml:"batch_block"`
BehindBlock int `toml:"behind_block"`
BridgeContract string `toml:"bridge_contract"`
ValidatorPrivateKey string `toml:"validator_private_key"`
ChainId int64 `toml:"chain_id"` // Will be populated by code
}
type MySQLConfig struct {
Host string
Port int
User string
Password string
Database string
MaxConn int `toml:"max_conn"`
MaxIdleConn int `toml:"max_idle_conn"`
}
type ServerConfig struct {
Listen string
InvalidHeaders []string `toml:"invalid_headers"`
}
var confPath = flag.String("c", "config.toml", "config file path")
func New() (*Config, error) {
var cfg Config
cfg.Chains = make(map[string]*ChainConfig)
// Parse the TOML configuration
_, err := toml.DecodeFile(*confPath, &cfg)
if err != nil {
return nil, err
}
// Process the chains from array to map using name as key
var chainArray []ChainConfig
_, err = toml.DecodeFile(*confPath, &struct {
Chains *[]ChainConfig `toml:"chains"`
*Config
}{
Chains: &chainArray,
Config: &cfg,
})
if err != nil {
return nil, err
}
// Convert to map for easier access
for _, chain := range chainArray {
chainCopy := chain // Create a copy to avoid pointer issues
cfg.Chains[chain.Name] = &chainCopy
}
return &cfg, nil
}
package constant
const JwtSecret = "uEj7AgDNCREwsvnTaCEtzDXt0I5eFDl8"
const (
InvalidParam = "invalid param"
UnsupportedPlatform = "unsupported platform"
InternalError = "internal error"
)
const (
TransferChainNoProcess = 0
TransferChainWaitConfirm = 1
TransferChainExecuted = 2
TransferChainRejected = 3
)
const (
ValidatorStatusNoPrecess = 0
ValidatorStatusConfirmation = 1
ValidatorStatusRejection = 2
ValidatorStatusFailure = 3
)
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
package dao
import (
"code.wuban.net.cn/movabridge/token-bridge/config"
dbModel "code.wuban.net.cn/movabridge/token-bridge/model/db"
"context"
"fmt"
"time"
"github.com/ethereum/go-ethereum/ethclient"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
type Dao struct {
c *config.Config
db *gorm.DB
ethClient map[int64]*ethclient.Client
}
func New(_c *config.Config) (dao *Dao, err error) {
dao = &Dao{
c: _c,
ethClient: make(map[int64]*ethclient.Client),
}
// Connect to all configured chains
for name, chainConfig := range _c.Chains {
var client *ethclient.Client
client, err = ethclient.Dial(chainConfig.RPC)
if err != nil {
return nil, fmt.Errorf("failed to connect to %s chain: %w", name, err)
}
// Get and store chain ID
chainId, err := client.ChainID(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to get %s chain ID: %w", name, err)
}
// Update the chain ID in the config
chainConfig.ChainId = chainId.Int64()
dao.ethClient[chainId.Int64()] = client
fmt.Printf("Connected to %s chain with ID %d\n", name, chainConfig.ChainId)
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True",
_c.MySQL.User, _c.MySQL.Password, _c.MySQL.Host, _c.MySQL.Port, _c.MySQL.Database)
// dbLogger := logger.Default.LogMode(logger.Silent)
// if _c.Debug {
// dbLogger = logger.Default.LogMode(logger.Info)
// }
dao.db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
// Logger: dbLogger,
})
if err != nil {
return
}
sqlDB, err := dao.db.DB()
if err != nil {
return
}
sqlDB.SetMaxOpenConns(_c.MySQL.MaxConn)
sqlDB.SetMaxIdleConns(_c.MySQL.MaxIdleConn)
sqlDB.SetConnMaxIdleTime(time.Hour)
err = dao.db.AutoMigrate(&dbModel.Height{}, &dbModel.BridgeEvent{})
if err != nil {
panic(err)
}
return dao, nil
}
package dao
import (
dbModel "code.wuban.net.cn/movabridge/token-bridge/model/db"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
)
// GetStorageHeight 获取上次缓存的高度
func (d *Dao) GetStorageHeight(key string) (value int64, err error) {
storage := new(dbModel.Height)
err = d.db.Model(storage).Where("`key` = ?", key).First(storage).Error
if err == gorm.ErrRecordNotFound {
return 0, ErrRecordNotFound
}
return storage.IntValue, err
}
// SetStorageHeight 设置上次缓存的高度
func (d *Dao) SetStorageHeight(key string, intValue int64) (err error) {
tx := d.db.Session(&gorm.Session{Logger: d.db.Logger.LogMode(logger.Error)})
return tx.Model(&dbModel.Height{}).Clauses(clause.OnConflict{UpdateAll: true}).Create(&dbModel.Height{
Key: key,
IntValue: intValue,
}).Error
}
// func (d *Dao) CreateBridgeEvent(event *dbModel.BridgeEvent) (err error) {
// return d.db.Clauses(clause.OnConflict{DoNothing: true}).Create(event).Error
// }
//
// func (d *Dao) GetBridgeEventWithOutInfo(fromChain int64, outId int64) (event *dbModel.BridgeEvent, err error) {
// event = new(dbModel.BridgeEvent)
// err = d.db.Model(event).Where("`from_chain` = ? AND `out_id` = ?", fromChain, outId).First(event).Error
// if err == gorm.ErrRecordNotFound {
// return nil, ErrRecordNotFound
// }
// return event, err
// }
//
// func (d *Dao) GetBridgeEventWithInInfo(chain int64, inId int64) (event *dbModel.BridgeEvent, err error) {
// event = new(dbModel.BridgeEvent)
// err = d.db.Model(event).Where("`to_chain` = ? AND `in_id` = ?", chain, inId).First(event).Error
// if err == gorm.ErrRecordNotFound {
// return nil, ErrRecordNotFound
// }
// return event, err
// }
//
// func (d *Dao) UpdateBridgeWithTransferIn(event *dbModel.BridgeEvent) (err error) {
// return d.db.Model(event).Where("`id` = ?", event.ID).Updates(map[string]interface{}{
// "to_contract": event.ToContract,
// "in_timestamp": event.InTimestamp,
// "in_id": event.InId,
// "to_chain_tx_hash": event.ToChainTxHash,
// "to_chain_status": event.ToChainStatus,
// }).Error
// }
//
// func (d *Dao) UpdateBridgeResult(event *dbModel.BridgeEvent, toChainHash string, status int) (err error) {
// return d.db.Model(&dbModel.BridgeEvent{}).Where("`id` = ?", event.ID).Updates(map[string]interface{}{
// "to_chain_status": status,
// "finish_tx_hash": toChainHash,
// }).Error
// }
func (d *Dao) UpdateBridgeValidatorOperation(event *dbModel.BridgeEvent, op int) (err error) {
return d.db.Model(&dbModel.BridgeEvent{}).Where("`id` = ?", event.ID).Updates(map[string]interface{}{
"validator_status": op,
}).Error
}
//
//func (d *Dao) CreateValidatorEvent(hash string, chain int64, validator string, txHash string, eventType string, transferInId int64) (err error) {
// event := &dbModel.ValidatorEvent{
// ChainId: chain,
// Validator: validator,
// TxHash: txHash,
// Event: eventType,
// TransferInId: transferInId,
// Hash: hash,
// }
// return d.db.Clauses(clause.OnConflict{DoNothing: true}).Create(event).Error
//}
package dao
import (
dbModel "code.wuban.net.cn/movabridge/token-bridge/model/db"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
var (
ErrRecordNotFound = gorm.ErrRecordNotFound
)
// Transaction represents a database transaction
type Transaction struct {
tx *gorm.DB
}
// BeginTx starts a new transaction
func (d *Dao) BeginTx() (*Transaction, error) {
tx := d.db.Begin()
if tx.Error != nil {
return nil, tx.Error
}
return &Transaction{tx: tx}, nil
}
// Commit commits the transaction
func (tx *Transaction) Commit() error {
return tx.tx.Commit().Error
}
// Rollback aborts the transaction
func (tx *Transaction) Rollback() error {
return tx.tx.Rollback().Error
}
// Transaction-aware versions of the database methods
func (d *Dao) CreateBridgeEventTx(tx *Transaction, event *dbModel.BridgeEvent) error {
return tx.tx.Clauses(clause.OnConflict{DoNothing: true}).Create(event).Error
}
func (d *Dao) GetBridgeEventWithOutInfoTx(tx *Transaction, fromChain int64, outId int64) (event *dbModel.BridgeEvent, err error) {
event = new(dbModel.BridgeEvent)
err = tx.tx.Model(event).Where("`from_chain` = ? AND `out_id` = ?", fromChain, outId).First(event).Error
if err == gorm.ErrRecordNotFound {
return nil, ErrRecordNotFound
}
return event, err
}
func (d *Dao) GetBridgeEventWithInInfoTx(tx *Transaction, chain int64, inId int64) (event *dbModel.BridgeEvent, err error) {
event = new(dbModel.BridgeEvent)
err = tx.tx.Model(event).Where("`to_chain` = ? AND `in_id` = ?", chain, inId).First(event).Error
if err == gorm.ErrRecordNotFound {
return nil, ErrRecordNotFound
}
return event, err
}
func (d *Dao) UpdateBridgeWithTransferInTx(tx *Transaction, event *dbModel.BridgeEvent) error {
return tx.tx.Model(event).Where("`id` = ?", event.ID).Updates(map[string]interface{}{
"to_contract": event.ToContract,
"in_timestamp": event.InTimestamp,
"in_id": event.InId,
"to_chain_tx_hash": event.ToChainTxHash,
"to_chain_status": event.ToChainStatus,
}).Error
}
func (d *Dao) UpdateBridgeResultTx(tx *Transaction, event *dbModel.BridgeEvent, toChainHash string, status int) error {
return tx.tx.Model(&dbModel.BridgeEvent{}).Where("`id` = ?", event.ID).Updates(map[string]interface{}{
"to_chain_status": status,
"finish_tx_hash": toChainHash,
}).Error
}
func (d *Dao) CreateValidatorEventTx(tx *Transaction, hash string, chain int64, validator string, txHash string, eventType string, transferInId int64) error {
event := &dbModel.ValidatorEvent{
ChainId: chain,
Validator: validator,
TxHash: txHash,
Event: eventType,
TransferInId: transferInId,
Hash: hash,
}
return tx.tx.Clauses(clause.OnConflict{DoNothing: true}).Create(event).Error
}
package dao
import (
"code.wuban.net.cn/movabridge/token-bridge/config"
"code.wuban.net.cn/movabridge/token-bridge/constant"
"code.wuban.net.cn/movabridge/token-bridge/contract/bridge"
dbModel "code.wuban.net.cn/movabridge/token-bridge/model/db"
"context"
"errors"
"github.com/ethereum/go-ethereum/accounts/abi"
"golang.org/x/crypto/sha3"
"math/big"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
log "github.com/sirupsen/logrus"
)
func (d *Dao) GetBlockHeight(chain *config.ChainConfig, behindBlock ...int) (height int64, err error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, ok := d.ethClient[chain.ChainId]; !ok {
return 0, errors.New("chain client not support")
}
n, err := d.ethClient[chain.ChainId].BlockNumber(ctx)
if len(behindBlock) > 0 {
n -= uint64(behindBlock[0])
if n < 0 {
n = 0
}
}
return int64(n), err
}
func (d *Dao) GetLatestBockHash(chain *config.ChainConfig) (hash string, err error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, ok := d.ethClient[chain.ChainId]; !ok {
return "", errors.New("chain client not support")
}
block, err := d.ethClient[chain.ChainId].BlockByNumber(ctx, nil)
if err != nil {
return
}
return block.Hash().Hex(), nil
}
func (d *Dao) GetBlockTime(chain *config.ChainConfig, height int) (timestamp int, err error) {
if _, ok := d.ethClient[chain.ChainId]; !ok {
return 0, errors.New("chain client not support")
}
for i := 0; i < 2; i++ {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
block, err := d.ethClient[chain.ChainId].BlockByNumber(ctx, big.NewInt(int64(height)))
if err == nil {
return int(block.Time()), nil
}
}
return
}
func (d *Dao) GetLogs(chain *config.ChainConfig, beginHeight, endHeight int64, topics, addresses []string) (logs []types.Log, err error) {
if _, ok := d.ethClient[chain.ChainId]; !ok {
return nil, errors.New("chain client not support")
}
for i := 0; i < 2; i++ {
// 重试2次
logs, err = d.getLogs(chain, beginHeight, endHeight, topics, addresses)
if err == nil {
return logs, nil
}
}
return
}
func (d *Dao) getLogs(chain *config.ChainConfig, beginHeight, endHeight int64, topics []string, addresses []string) (logs []types.Log, err error) {
addrs := make([]common.Address, 0)
for _, addr := range addresses {
addrs = append(addrs, common.HexToAddress(addr))
}
q := ethereum.FilterQuery{
FromBlock: big.NewInt(int64(beginHeight)),
ToBlock: big.NewInt(int64(endHeight)),
Topics: [][]common.Hash{{}},
Addresses: addrs,
}
for _, topic := range topics {
q.Topics[0] = append(q.Topics[0], common.HexToHash(topic))
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return d.ethClient[chain.ChainId].FilterLogs(ctx, q)
}
func (d *Dao) buildParam(event *dbModel.BridgeEvent) (bridge.BridgesubmitParams, error) {
sendAmount, _ := new(big.Int).SetString(event.SendAmount, 10)
receiveAmount, _ := new(big.Int).SetString(event.ReceiveAmount, 10)
param := bridge.BridgesubmitParams{
ToChainID: big.NewInt(event.ToChain),
Receiver: common.HexToAddress(event.Receiver),
Token: common.HexToAddress(event.ToToken),
Amount: receiveAmount,
OutId: big.NewInt(int64(event.OutId)),
FromChainID: big.NewInt(event.FromChain),
Sender: common.HexToAddress(event.FromAddress),
SendToken: common.HexToAddress(event.FromToken),
SendAmount: sendAmount,
}
u256Type, _ := abi.NewType("uint256", "", nil)
addrType, _ := abi.NewType("address", "", nil)
arguments := abi.Arguments{
{Type: u256Type},
{Type: u256Type},
{Type: addrType},
{Type: addrType},
{Type: u256Type},
{Type: u256Type},
{Type: addrType},
{Type: addrType},
}
data, err := arguments.Pack(param.OutId, param.FromChainID, param.Sender,
param.SendToken, param.SendAmount, param.ToChainID, param.Receiver, param.Token)
if err != nil {
return bridge.BridgesubmitParams{}, err
}
sh := sha3.NewLegacyKeccak256()
sh.Write(data)
signature := sh.Sum(nil)
copy(param.Signature[:], signature)
return param, nil
}
func (d *Dao) SubmitInTransfer(event *dbModel.BridgeEvent) error {
if _, ok := d.ethClient[event.ToChain]; !ok {
return errors.New("chain client not support")
}
var chain *config.ChainConfig
for _, c := range d.c.Chains {
if c.ChainId == event.ToChain {
chain = c
break
}
}
if chain == nil {
return errors.New("chain not found in config")
}
// verify the event is valid.
valid := d.CheckEventValid()
if !valid {
log.WithField("chainId", chain.ChainId).Error("event is not valid")
return errors.New("event is not valid")
}
ca, err := bridge.NewBridgeContract(common.HexToAddress(chain.BridgeContract), d.ethClient[event.ToChain])
if err != nil {
return err
}
k := chain.ValidatorPrivateKey
signPrivateKey, err := crypto.HexToECDSA(common.Bytes2Hex(common.FromHex(k)))
if err != nil {
log.WithField("chainId", chain.ChainId).WithError(err).Error("failed to parse private key")
return err
}
opts, err := bind.NewKeyedTransactorWithChainID(signPrivateKey, big.NewInt(int64(chain.ChainId)))
if err != nil {
log.WithField("chainId", chain.ChainId).WithError(err).Error("new keyed transfer failed")
return err
}
param, err := d.buildParam(event)
if err != nil {
log.WithField("chainId", chain.ChainId).WithError(err).Error("build param failed")
return err
}
if tx, err := ca.SubmitInTransfer(opts, param); err != nil {
log.WithField("chainId", chain.ChainId).WithError(err).Error("failed to submit in transfer")
return err
} else {
// update validator status.
log.WithField("chainId", chain.ChainId).Infof("submit in transfer tx hash: %s", tx.Hash().Hex())
return d.UpdateBridgeValidatorOperation(event, constant.ValidatorStatusConfirmation)
}
}
func (d *Dao) CheckEventValid() bool {
// Implement the logic to check if the event is valid.
// This is a placeholder implementation.
return true
}
package dao
import (
"bytes"
"code.wuban.net.cn/movabridge/token-bridge/contract/bridge"
"encoding/hex"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/crypto/sha3"
"math/big"
"testing"
)
func TestDao_AbiEncode(t *testing.T) {
ether := new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)
param := bridge.BridgesubmitParams{
ToChainID: big.NewInt(2),
Receiver: common.HexToAddress("0x000000000000000000000000000000000000dead"),
Token: common.HexToAddress("0x000000000000000000000000000000000000beef"),
Amount: new(big.Int).Mul(big.NewInt(10), ether),
OutId: big.NewInt(1),
FromChainID: big.NewInt(1),
Sender: common.HexToAddress("0x000000000000000000000000000000000000cafe"),
SendToken: common.HexToAddress("0x000000000000000000000000000000000000babe"),
SendAmount: new(big.Int).Mul(big.NewInt(10), ether),
}
expectHash := common.HexToHash("0x7be55178ff6b46f92c87979ffdffc36e242f0e559556c89f1f8403a71e72e09c")
u256Type, _ := abi.NewType("uint256", "", nil)
addrType, _ := abi.NewType("address", "", nil)
arguments := abi.Arguments{
{Type: u256Type},
{Type: u256Type},
{Type: addrType},
{Type: addrType},
{Type: u256Type},
{Type: u256Type},
{Type: addrType},
{Type: addrType},
}
data, err := arguments.Pack(param.OutId, param.FromChainID, param.Sender, param.SendToken, param.SendAmount, param.ToChainID, param.Receiver, param.Token)
if err != nil {
t.Fatalf("failed to encode abi: %v", err)
}
fmt.Println("data=0x", hex.EncodeToString(data))
sh := sha3.NewLegacyKeccak256()
sh.Write(data)
signature := sh.Sum(nil)
if bytes.Compare(signature, expectHash[:]) != 0 {
t.Errorf("expect hash: %x, got: %x", expectHash, signature)
}
}
networks:
default:
name: bridge-network
services:
bridgedb:
image: mysql:8
container_name: bridgedb
volumes:
- ./data/db:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: "XN2UARuys3zy4Oux"
MYSQL_DATABASE: "bridge"
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "--password=$$(cat $$MYSQL_ROOT_PASSWORD)" ]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
validator:
image: token-bridge:latest
container_name: bridge-validator
depends_on:
bridgedb:
condition: service_healthy
volumes:
- ./config.toml:/app/config.toml
command:
- "/bin/sh"
- "-c"
- "/usr/bin/validator -c /app/config.toml"
restart:
unless-stopped
\ No newline at end of file
module code.wuban.net.cn/movabridge/token-bridge
go 1.24.0
require (
github.com/BurntSushi/toml v1.5.0
github.com/btcsuite/btcutil v1.0.2
github.com/ethereum/go-ethereum v1.16.2
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.10.1
github.com/google/uuid v1.6.0
github.com/shopspring/decimal v1.4.0
github.com/sirupsen/logrus v1.9.3
golang.org/x/crypto v0.41.0
gorm.io/driver/mysql v1.6.0
gorm.io/gorm v1.30.1
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/bits-and-blooms/bitset v1.20.0 // indirect
github.com/btcsuite/btcd v0.20.1-beta // indirect
github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/consensys/gnark-crypto v0.18.0 // indirect
github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect
github.com/ethereum/go-verkle v0.2.2 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/holiman/uint256 v1.3.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/supranational/blst v0.3.14 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
golang.org/x/arch v0.18.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
This diff is collapsed.
package middleware
import (
"math/rand"
"strings"
"time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func CheckHeaderMiddleware(invalidHeaders []string) gin.HandlerFunc {
for _, invalidHeader := range invalidHeaders {
log.WithField("invalid-header", invalidHeader).Debug("init invalid header")
}
return func(c *gin.Context) {
// 获取user-agent
userAgent := c.Request.Header.Get("User-Agent")
for _, invalidHeader := range invalidHeaders {
if strings.Contains(userAgent, invalidHeader) {
time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)+10))
c.JSON(200, gin.H{
"code": 0,
"msg": "ok",
"data": "",
})
log.WithFields(log.Fields{"user-agent": userAgent}).Debug("invalid header, return fake data")
c.Abort()
return
}
}
c.Next()
}
}
package middleware
import (
"bytes"
"io"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func PrintRequestResponseBodyMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 读取请求 body
var requestBody []byte
if c.Request.Body != nil {
requestBody, _ = io.ReadAll(c.Request.Body)
c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
}
log.WithFields(log.Fields{"method": c.Request.Method, "uri": c.Request.RequestURI, "body": string(requestBody)}).Debug("request body")
bodyWriter := &responseBodyWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = bodyWriter
c.Next()
responseBody := bodyWriter.body.String()
log.WithFields(log.Fields{"status": c.Writer.Status(), "body": responseBody}).Debug("response body")
}
}
type responseBodyWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (r *responseBodyWriter) Write(b []byte) (int, error) {
r.body.Write(b)
return r.ResponseWriter.Write(b)
}
package middleware
package dbModel
import (
"gorm.io/gorm"
)
type Height struct {
Key string `gorm:"primaryKey"`
IntValue int64 `gorm:"type:int;not null"` // 配置value
}
type BridgeEvent struct {
FromChain int64 `gorm:"type:int;comment:源链"`
OutTimestamp int64 `gorm:"type:int;comment:Out时间戳"`
FromContract string `gorm:"type:varchar(255);comment:源合约"`
FromToken string `gorm:"type:varchar(255);comment:源token"`
FromAddress string `gorm:"type:varchar(255);index;comment:源地址"` // 用户地址
FromChainTxHash string `gorm:"type:varchar(255);index;comment:源链交易hash"` // 源链交易hash
SendAmount string `gorm:"type:varchar(255);comment:发送金额"` // 发送金额
FeeAmount string `gorm:"type:varchar(255);comment:手续费金额"` // 手续费金额
Receiver string `gorm:"type:varchar(255);comment:接收者地址"` // 目标链接收者地址
ToChain int64 `gorm:"type:int;comment:目标链"`
ToToken string `gorm:"type:varchar(255);comment:目标token"` // 目标链token
ReceiveAmount string `gorm:"type:varchar(255);comment:接收金额"` // 接收金额 = 发送金额 - 手续费金额
Hash string `gorm:"type:varchar(255);uniqueIndex;comment:bridge hash"`
OutId int64 `gorm:"type:int;index;comment:源链转出ID"`
InTimestamp int64 `gorm:"type:int;comment:Out时间戳"`
InId int64 `gorm:"type:int;index;comment:源链转入ID"`
ToContract string `gorm:"type:varchar(255);comment:目标合约(toAddr)"`
FinishTxHash string `gorm:"type:varchar(255);comment:完成交易hash"` // 完成交易hash
ToChainTxHash string `gorm:"type:varchar(255);comment:目标链交易hash"`
ToChainStatus int `gorm:"type:int;comment:目标链状态"` // 0未执行, 1等待确认, 2已执行, 3已拒绝
ValidatorStatus int `gorm:"type:int;comment:验证者状态"` // 0未验证, 1已确认, 2已拒绝, 3操作失败
gorm.Model
}
type ValidatorEvent struct {
ChainId int64 `gorm:"type:int;comment:链ID"`
Validator string `gorm:"type:varchar(255);index;comment:验证者地址"` // 验证者地址
TxHash string `gorm:"type:varchar(255);index;comment:交易hash"` // 交易hash
Event string `gorm:"type:varchar(255);index;comment:事件类型"` // 事件类型
TransferInId int64 `gorm:"type:int;index;comment:转入ID"` // 转入ID
Hash string `gorm:"type:varchar(255);uniqueIndex;comment:event hash"`
gorm.Model
}
# How to run
1. make docker
2. modify config.yml for chain and mysql password.
3. modify docker-compose.yml for mysql password.
4. docker compose up -d
5. docker compose down
\ No newline at end of file
package util
import (
"encoding/binary"
"fmt"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func TestGenHDKey(t *testing.T) {
// xpub := "xpub69CSVbN4XnU6ituHBAnS4urNA2uiwcCciXWjJ7BKgsM5YAsQV1AfE1gEi7Aeip6dE6kFs3suNghVQukcmoGcdFYPohyAyX7KnxJNnqr2XSU"
xpub := "xpub6C2ojpneBn4KHz1zaHpsyVHQMuJQbeDPbdkXywAUR43hXpjyQNcRv1ZQdvxnGmGKvXLoGPoN1G7cwfmW5CGZjPagpLnggXmqN52HhJk9F4B"
// k, _ := GetPubKeyByPub(xpub, "0/1")
// addr := crypto.PubkeyToAddress(*k)
// t.Log(addr.String())
userA := common.HexToAddress("0xAaf459E071637dE222D0a9e3b439704f478ed767")
path := convertAddrToPath(userA.String())
st := time.Now()
k, _ := GetPubKeyByPub(xpub, path)
addr2 := crypto.PubkeyToAddress(*k)
t.Log(path, addr2.String())
t.Log(time.Since(st))
a, _ := GetDepositAddress(userA, xpub)
t.Log(a.String())
t.Log(addr2.String(), time.Since(st))
}
func convertAddrToPath(address string) string {
addrBytes := common.HexToAddress(address).Bytes()
// split to 8
addrByteArray := make([]string, 7)
for i := 0; i < 7; i++ {
uint32Temp := uint32(0)
if i == 6 {
// fmt.Println("tt", tt)
// binary.BigEndian.PutUint32(tt, addrByteArray[i])
// fmt.Println("after tt", addrByteArray[i])
uint32Temp = binary.BigEndian.Uint32(common.LeftPadBytes(addrBytes[i*3:i*3+2], 4))
} else {
// addrByteArray[i] = addrBytes[i*3 : i*3+3]
// binary.BigEndian.PutUint32(temp4Byte(addrBytes[i*3:i*3+3]), addrByteArray[i])
uint32Temp = binary.BigEndian.Uint32(common.LeftPadBytes(addrBytes[i*3:i*3+3], 4))
}
addrByteArray[i] = fmt.Sprintf("%d", uint32Temp)
}
fmt.Println(strings.Join(addrByteArray, "/"))
return strings.Join(addrByteArray, "/")
}
package util
import (
"crypto/ecdsa"
"encoding/binary"
"fmt"
"strconv"
"strings"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func GetDepositAddress(input common.Address, xpub string) (output common.Address, err error) {
paths := make([]string, 7)
for i := 0; i < 7; i++ {
uint32Temp := uint32(0)
if i == 6 {
uint32Temp = binary.BigEndian.Uint32(common.LeftPadBytes(input.Bytes()[i*3:i*3+2], 4))
} else {
uint32Temp = binary.BigEndian.Uint32(common.LeftPadBytes(input.Bytes()[i*3:i*3+3], 4))
}
paths[i] = fmt.Sprintf("%d", uint32Temp)
}
pubkey, err := GetPubKeyByPub(xpub, strings.Join(paths, "/"))
if err != nil {
return common.Address{}, err
}
return crypto.PubkeyToAddress(*pubkey), nil
}
func GetPubKeyByPub(xpub string, path string) (pubkey *ecdsa.PublicKey, err error) {
pathList := parsePath(path)
var next *hdkeychain.ExtendedKey
for _, floor := range pathList {
idx := floor[0]
isHardened, _ := strconv.ParseBool(strconv.Itoa(floor[1]))
next, err = nextFloor(xpub, isHardened, uint32(idx))
if err != nil {
return
}
xpub = next.String()
}
pk, err := next.ECPubKey()
if err != nil {
return
}
return pk.ToECDSA(), nil
}
// 返回一个二维数组 参数1 对应每一层偏移 参数2 1代表hardened 0普通
func parsePath(path string) [][]int {
l := strings.Split(path, "/")
var resList [][]int
// m开头或者/开头 去掉第一个
if l[0] == "m" || l[0] == "" {
l = l[1:]
}
// /结尾 去掉最后一个
if l[len(l)-1] == "" {
l = l[:len(l)-1]
}
for _, s := range l {
if strings.HasSuffix(s, "'") {
idx, _ := strconv.Atoi(s[:len(s)-1])
resList = append(resList, []int{idx, 1})
} else {
idx, _ := strconv.Atoi(s)
resList = append(resList, []int{idx, 0})
}
}
return resList
}
func nextFloor(key string, hardened bool, idx uint32) (*hdkeychain.ExtendedKey, error) {
key1, err := hdkeychain.NewKeyFromString(key)
if err != nil {
return nil, err
}
if hardened {
return key1.Child(hdkeychain.HardenedKeyStart + idx)
} else {
return key1.Child(idx)
}
}
package util
import (
"crypto/md5"
"fmt"
"math/big"
"time"
"golang.org/x/crypto/sha3"
)
func TextToHash(data []byte) []byte {
msg := fmt.Sprintf("\x19Ethereum Signed Mesage:\n%d%s", len(data), string(data))
hasher := sha3.NewLegacyKeccak256()
hasher.Write([]byte(msg))
return hasher.Sum(nil)
}
func GetUnixDay() *big.Int {
return big.NewInt((time.Now().Unix() + 8*3600) / 86400)
}
func GetNonce(user string) (nonce *big.Int) {
payload := append([]byte(user), GetUnixDay().Bytes()...)
payload = append(payload, []byte{0x01}...)
hash := md5.Sum(payload)
return new(big.Int).SetBytes(hash[:6])
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment