Skip to content

Commit

Permalink
feat(bitcoin): add bridge contract invoke
Browse files Browse the repository at this point in the history
1. add bridge logic
2. add bitcoin ChainParams func parse bitcoin network
3. fix log error print
4. bitcoin config new way to read env
  • Loading branch information
oxf71 committed Nov 23, 2023
1 parent 8b137d1 commit 02bc46b
Show file tree
Hide file tree
Showing 9 changed files with 340 additions and 21 deletions.
141 changes: 141 additions & 0 deletions bitcoin/bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package bitcoin

import (
"bytes"
"context"
"crypto/ecdsa"
"fmt"
"math/big"
"net/url"
"os"
"path"

"github.com/ethereum/go-ethereum/accounts/abi"
"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"
"github.com/ethereum/go-ethereum/ethclient"
)

type Bridge struct {
EthRpcUrl string

Check warning on line 22 in bitcoin/bridge.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

var-naming: struct field EthRpcUrl should be EthRPCURL (revive)
EthPrivKey *ecdsa.PrivateKey
ContractAddress common.Address
ABI string
GasLimit uint64
}

// NewBridge new bridge
func NewBridge(bridgeCfg BridgeConfig, abiFileDir string) (*Bridge, error) {
rpcUrl, err := url.ParseRequestURI(bridgeCfg.EthRpcUrl)

Check warning on line 31 in bitcoin/bridge.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

var-naming: var rpcUrl should be rpcURL (revive)
if err != nil {
return nil, err
}

privateKey, err := crypto.HexToECDSA(bridgeCfg.EthPrivKey)
if err != nil {
return nil, err
}

abi, err := os.ReadFile(path.Join(abiFileDir, bridgeCfg.ABI))
if err != nil {
return nil, err
}

return &Bridge{
EthRpcUrl: rpcUrl.String(),
ContractAddress: common.HexToAddress(bridgeCfg.ContractAddress),
EthPrivKey: privateKey,
ABI: string(abi),
GasLimit: bridgeCfg.GasLimit,
}, nil
}

// Deposit to ethereum
// TODO: partition method and add test
func (b *Bridge) Deposit(bitcoinAddress string, amount int64) error {
if bitcoinAddress == "" {
return fmt.Errorf("bitcoin address is empty")
}

ctx := context.Background()
// dail ethereum rpc
client, err := ethclient.Dial(b.EthRpcUrl)
if err != nil {
return err
}

publicKey := b.EthPrivKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("error casting public key to ECDSA")
}
nonce, err := client.PendingNonceAt(ctx, crypto.PubkeyToAddress(*publicKeyECDSA))
if err != nil {
return err
}
gasPrice, err := client.SuggestGasPrice(ctx)
if err != nil {
return err
}

contractAbi, err := abi.JSON(bytes.NewReader([]byte(b.ABI)))
if err != nil {
return err
}

toAddress, err := b.BitcoinAddressToEthAddress(bitcoinAddress)
if err != nil {
return err
}

data, err := contractAbi.Pack("deposit", common.HexToAddress(toAddress), new(big.Int).SetInt64(amount))
if err != nil {
return err
}

tx := types.NewTx(&types.LegacyTx{
Nonce: nonce,
To: &b.ContractAddress,
Value: big.NewInt(0),
Gas: b.GasLimit,
GasPrice: gasPrice,
Data: data,
})

chainID, err := client.ChainID(ctx)
if err != nil {
return err
}
// sign tx
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), b.EthPrivKey)
if err != nil {
return err
}

// send tx
err = client.SendTransaction(ctx, signedTx)
if err != nil {
return err
}

// wait tx confirm
receipt, err := bind.WaitMined(ctx, client, signedTx)
if err != nil {
return err
}

if receipt.Status != 1 {
return fmt.Errorf("tx failed, receipt:%v", receipt)
}

return nil
}

// BitcoinAddressToEthAddress bitcoin address to eth address
// TODO: implementation
func (b *Bridge) BitcoinAddressToEthAddress(bitcoinAddress string) (string, error) {
// TODO: wait aa finished
return bitcoinAddress, nil
}
43 changes: 43 additions & 0 deletions bitcoin/bridge_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package bitcoin_test

import (
"os"
"path"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/evmos/ethermint/bitcoin"
"github.com/stretchr/testify/assert"
)

func TestNewBridge(t *testing.T) {
abiPath := path.Join("./testdata")

abi, err := os.ReadFile(path.Join("./testdata", "abi.json"))
if err != nil {
t.Fatal(err)
}

privateKey, err := crypto.HexToECDSA("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
if err != nil {
t.Fatal(err)
}

bridgeCfg := bitcoin.BridgeConfig{
EthRpcUrl: "http://localhost:8545",
ContractAddress: "0x123456789abcdef",
EthPrivKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
ABI: "abi.json",
GasLimit: 1000000,
}

bridge, err := bitcoin.NewBridge(bridgeCfg, abiPath)
assert.NoError(t, err)
assert.NotNil(t, bridge)
assert.Equal(t, bridgeCfg.EthRpcUrl, bridge.EthRpcUrl)
assert.Equal(t, common.HexToAddress("0x123456789abcdef"), bridge.ContractAddress)
assert.Equal(t, privateKey, bridge.EthPrivKey)
assert.Equal(t, string(abi), bridge.ABI)
assert.Equal(t, bridgeCfg.GasLimit, bridge.GasLimit)
}
37 changes: 35 additions & 2 deletions bitcoin/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package bitcoin
import (
"os"
"path"
"strings"

"github.com/btcsuite/btcd/chaincfg"
"github.com/spf13/viper"
)

// BitconConfig defines the bitcoin config
// TODO: defined different config group eg: bitcoin, bridge, indexer, commiter
type BitconConfig struct {
// NetworkName defines the bitcoin network name
NetworkName string `mapstructure:"network-name"`
Expand All @@ -26,6 +30,16 @@ type BitconConfig struct {
EnableIndexer bool `mapstructure:"enable-indexer"`
// IndexerListenAddress defines the address to listen on
IndexerListenAddress string `mapstructure:"indexer-listen-address"`
// Bridge defines the bridge config
Bridge BridgeConfig `mapstructure:"bridge"`
}

type BridgeConfig struct {
EthRpcUrl string `mapstructure:"eth-rpc-url"`

Check warning on line 38 in bitcoin/config.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

var-naming: struct field EthRpcUrl should be EthRPCURL (revive)
EthPrivKey string `mapstructure:"eth-priv-key"`
ContractAddress string `mapstructure:"contract-address"`
ABI string `mapstructure:"abi"`
GasLimit uint64 `mapstructure:"gas-limit"`
}

const (
Expand Down Expand Up @@ -56,8 +70,9 @@ func LoadBitcoinConfig(homePath string) (*BitconConfig, error) {

v := viper.New()
v.SetConfigFile(configFile)

// TODO: set env prifix
v.AutomaticEnv()
v.SetEnvPrefix("BITCOIN")
v.SetEnvKeyReplacer((strings.NewReplacer(".", "_", "-", "_")))

if err := v.ReadInConfig(); err != nil {
return nil, err
Expand All @@ -69,3 +84,21 @@ func LoadBitcoinConfig(homePath string) (*BitconConfig, error) {
}
return &config, nil
}

// ChainParams get chain params by network name
func ChainParams(network string) *chaincfg.Params {
switch network {
case "mainnet":
return &chaincfg.MainNetParams
case "testnet":
return &chaincfg.TestNet3Params
case "signet":
return &chaincfg.SigNetParams
case "simnet":
return &chaincfg.SimNetParams
case "regtest":
return &chaincfg.RegressionNetParams
default:
return &chaincfg.TestNet3Params
}
}
81 changes: 81 additions & 0 deletions bitcoin/config_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package bitcoin_test

import (
"os"
"testing"

"github.com/btcsuite/btcd/chaincfg"
"github.com/evmos/ethermint/bitcoin"
"github.com/stretchr/testify/require"
)
Expand All @@ -19,4 +21,83 @@ func TestConfig(t *testing.T) {
require.Equal(t, "tb1qgm39cu009lyvq93afx47pp4h9wxq5x92lxxgnz", config.Destination)
require.Equal(t, true, config.EnableIndexer)
require.Equal(t, "tb1qfhhxljfajcppfhwa09uxwty5dz4xwfptnqmvtv", config.IndexerListenAddress)
require.Equal(t, "localhost:8545", config.Bridge.EthRpcUrl)
require.Equal(t, "0xB457BF68D71a17Fa5030269Fb895e29e6cD2DFF2", config.Bridge.ContractAddress)
require.Equal(t, "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", config.Bridge.EthPrivKey)
require.Equal(t, "abi.json", config.Bridge.ABI)
require.Equal(t, uint64(3000), config.Bridge.GasLimit)
}

func TestConfigEnv(t *testing.T) {
os.Setenv("BITCOIN_NETWORK_NAME", "testnet")
os.Setenv("BITCOIN_RPC_HOST", "127.0.0.1")
os.Setenv("BITCOIN_RPC_PORT", "8888")
os.Setenv("BITCOIN_RPC_USER", "abc")
os.Setenv("BITCOIN_RPC_PASS", "abcd")
os.Setenv("BITCOIN_WALLET_NAME", "b2node")
os.Setenv("BITCOIN_DESTINATION", "tb1qfhhxljfajcppfhwa09uxwty5dz4xwfptnqmvtv")
os.Setenv("BITCOIN_ENABLE_INDEXER", "false")
os.Setenv("BITCOIN_INDEXER_LISTEN_ADDRESS", "tb1qgm39cu009lyvq93afx47pp4h9wxq5x92lxxgnz")
os.Setenv("BITCOIN_BRIDGE_ETH_RPC_URL", "127.0.0.1:8545")
os.Setenv("BITCOIN_BRIDGE_CONTRACT_ADDRESS", "0xB457BF68D71a17Fa5030269Fb895e29e6cD2DF22")
os.Setenv("BITCOIN_BRIDGE_ETH_PRIV_KEY", "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
os.Setenv("BITCOIN_BRIDGE_ABI", "aaa.abi")
os.Setenv("BITCOIN_BRIDGE_GAS_LIMIT", "23333")
config, err := bitcoin.LoadBitcoinConfig("./testdata")
require.NoError(t, err)
require.Equal(t, "testnet", config.NetworkName)
require.Equal(t, "127.0.0.1", config.RPCHost)
require.Equal(t, "8888", config.RPCPort)
require.Equal(t, "abc", config.RPCUser)
require.Equal(t, "abcd", config.RPCPass)
require.Equal(t, "b2node", config.WalletName)
require.Equal(t, "tb1qfhhxljfajcppfhwa09uxwty5dz4xwfptnqmvtv", config.Destination)
require.Equal(t, false, config.EnableIndexer)
require.Equal(t, "tb1qgm39cu009lyvq93afx47pp4h9wxq5x92lxxgnz", config.IndexerListenAddress)
require.Equal(t, "127.0.0.1:8545", config.Bridge.EthRpcUrl)
require.Equal(t, "0xB457BF68D71a17Fa5030269Fb895e29e6cD2DF22", config.Bridge.ContractAddress)
require.Equal(t, "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", config.Bridge.EthPrivKey)
require.Equal(t, "aaa.abi", config.Bridge.ABI)
require.Equal(t, uint64(23333), config.Bridge.GasLimit)
}

func TestChainParams(t *testing.T) {
testCases := []struct {
network string
params *chaincfg.Params
}{
{
network: "mainnet",
params: &chaincfg.MainNetParams,
},
{
network: "testnet",
params: &chaincfg.TestNet3Params,
},
{
network: "signet",
params: &chaincfg.SigNetParams,
},
{
network: "simnet",
params: &chaincfg.SimNetParams,
},
{
network: "regtest",
params: &chaincfg.RegressionNetParams,
},
{
network: "invalid",
params: &chaincfg.TestNet3Params,
},
}

for _, tc := range testCases {
t.Run(tc.network, func(t *testing.T) {
result := bitcoin.ChainParams(tc.network)
if result == nil || result != tc.params {
t.Errorf("ChainParams(%s) = %v, expected %v", tc.network, result, tc.params)
}
})
}
}
Loading

0 comments on commit 02bc46b

Please sign in to comment.