Skip to content

Commit

Permalink
fixed core issue + bug where dest callers for evm destinations weren'…
Browse files Browse the repository at this point in the history
…t being decoded correctly
  • Loading branch information
bd21 committed Oct 25, 2023
1 parent 81278b0 commit 6f5bfbb
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 31 deletions.
20 changes: 9 additions & 11 deletions cmd/ethereum/broadcast.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,8 @@ func Broadcast(
msg.DestDomain,
msg.SourceTxHash))

// TODO Account sequence lock is implemented but gets out of sync with remote.
// accountSequence := sequenceMap.Next(cfg.Networks.Destination.Noble.DomainId)
_, err := GetEthereumAccountNonce(cfg.Networks.Destination.Ethereum.RPC, ethereumAddress)
if err != nil {
logger.Error("unable to retrieve ethereum account nonce")
continue
}
//auth.Nonce = big.NewInt(nonce)
nonce := sequenceMap.Next(cfg.Networks.Destination.Ethereum.DomainId)
auth.Nonce = big.NewInt(nonce)

// broadcast txn
tx, err := messageTransmitter.ReceiveMessage(
Expand All @@ -66,12 +60,16 @@ func Broadcast(
msg.Status = types.Complete
return tx, nil
} else {

logger.Error(fmt.Sprintf("error during broadcast: %s", err.Error()))
if parsedErr, ok := err.(JsonError); ok {
if parsedErr.ErrorCode() == 3 && parsedErr.Error() == "execution reverted: Nonce already used" {
msg.Status = types.Failed
return nil, errors.New(fmt.Sprintf("Nonce already used"))
nonce, err = GetEthereumAccountNonce(cfg.Networks.Destination.Ethereum.RPC, ethereumAddress)
if err != nil {
logger.Error("unable to retrieve account number")
}
logger.Debug(fmt.Sprintf("retrying with new nonce: %d", nonce))
sequenceMap.Put(cfg.Networks.Destination.Ethereum.DomainId, nonce)

}
}

Expand Down
4 changes: 1 addition & 3 deletions cmd/noble/broadcast.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,9 @@ func Broadcast(
logger.Error("unable to retrieve account number")
}
}

logger.Debug(fmt.Sprintf("error during broadcast: %s", rpcResponse.Log))
logger.Debug(fmt.Sprintf("retrying with new account sequence: %d", newAccountSequence))
sequenceMap.Put(4, newAccountSequence)

sequenceMap.Put(cfg.Networks.Destination.Noble.DomainId, newAccountSequence)
}
if err != nil {
logger.Error(fmt.Sprintf("error during broadcast: %s", err.Error()))
Expand Down
1 change: 0 additions & 1 deletion cmd/noble/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ func init() {

func TestStartListener(t *testing.T) {
cfg.Networks.Source.Noble.StartBlock = 3273557
cfg.Networks.Source.Noble.LookbackPeriod = 0
go noble.StartListener(cfg, logger, processingQueue)

time.Sleep(20 * time.Second)
Expand Down
41 changes: 28 additions & 13 deletions cmd/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"bytes"
"cosmossdk.io/log"
"encoding/hex"
"fmt"
"github.com/spf13/cobra"
"github.com/strangelove-ventures/noble-cctp-relayer/cmd/circle"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/strangelove-ventures/noble-cctp-relayer/config"
"github.com/strangelove-ventures/noble-cctp-relayer/types"
"os"
"strings"
"sync"
"time"
)
Expand Down Expand Up @@ -178,22 +180,35 @@ func filterDisabledCCTPRoutes(cfg config.Config, logger log.Logger, msg *types.M
// filterInvalidDestinationCallers returns true if the minter is not the destination caller for the specified domain
func filterInvalidDestinationCallers(cfg config.Config, logger log.Logger, msg *types.MessageState) bool {
zeroByteArr := make([]byte, 32)
bech32DestinationCaller, err := types.DecodeDestinationCaller(msg.DestinationCaller)

result := false
if err != nil {
result = true
}

//transformedDestinationCaller :=
if !bytes.Equal(msg.DestinationCaller, zeroByteArr) &&
bech32DestinationCaller != cfg.Networks.Minters[msg.DestDomain].MinterAddress {
result = true
}
switch msg.DestDomain {
case 4:
bech32DestinationCaller, err := types.DecodeDestinationCaller(msg.DestinationCaller)
if err != nil {
result = true
}
if !bytes.Equal(msg.DestinationCaller, zeroByteArr) &&
bech32DestinationCaller != cfg.Networks.Minters[msg.DestDomain].MinterAddress {
result = true
}
if result {
logger.Info(fmt.Sprintf("Filtered tx %s because the destination caller %s is specified and it's not the minter %s",
msg.SourceTxHash, msg.DestinationCaller, cfg.Networks.Minters[msg.DestDomain].MinterAddress))
}

if result {
logger.Info(fmt.Sprintf("Filtered tx %s because the destination caller %s is specified and it's not the minter %s",
msg.SourceTxHash, msg.DestinationCaller, cfg.Networks.Minters[msg.DestDomain].MinterAddress))
default: // minting to evm
decodedMinter, err := hex.DecodeString(strings.ReplaceAll(cfg.Networks.Minters[0].MinterAddress, "0x", ""))
if err != nil {
return !bytes.Equal(msg.DestinationCaller, zeroByteArr)
}

decodedMinterPadded := make([]byte, 32)
copy(decodedMinterPadded[12:], decodedMinter)

if !bytes.Equal(msg.DestinationCaller, zeroByteArr) && !bytes.Equal(msg.DestinationCaller, decodedMinterPadded) {
result = true
}
}

return result
Expand Down
1 change: 0 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ type Config struct {
RPC string `yaml:"rpc"`
RequestQueueSize uint32 `yaml:"request-queue-size"`
StartBlock uint64 `yaml:"start-block"`
LookbackPeriod uint64 `yaml:"lookback-period"`
Workers uint32 `yaml:"workers"`
Enabled bool `yaml:"enabled"`
} `yaml:"noble"`
Expand Down
192 changes: 192 additions & 0 deletions integration/eth_multi_send_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package integration_testing

import (
"context"
"cosmossdk.io/math"
"crypto/ecdsa"
"encoding/hex"
"fmt"
nobletypes "github.com/circlefin/noble-cctp/x/cctp/types"
sdkClient "github.com/cosmos/cosmos-sdk/client"
clientTx "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/types/bech32"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
xauthtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/strangelove-ventures/noble-cctp-relayer/cmd"
eth "github.com/strangelove-ventures/noble-cctp-relayer/cmd/ethereum"
"github.com/strangelove-ventures/noble-cctp-relayer/cmd/noble"
"github.com/strangelove-ventures/noble-cctp-relayer/types"
"github.com/stretchr/testify/require"
"testing"
"time"
)

// TestEthereumMultiSend broadcasts N depositForBurnWithCaller messages on Noble, and then tries to receive them all at once on Ethereum.
// We require a destination caller in this test so the deployed relayer doesn't pick it up.
//
// The point of this test is to verify that the Ethereum minter's account sequence is synced.
// A successful test means that all messages went through without retries (which are set to zero).
// We verify this result by checking the account balance at the end of the test.
func TestEthereumMultiSend(t *testing.T) {
setupTest()

// number of depositForBurn txns to send
n := 10

ethMultiSendCfg := Parse("../.ignore/eth_multi_send.yaml")

cfg.Networks.Source.Noble.StartBlock = getNobleLatestBlockHeight()
cfg.Networks.Destination.Ethereum.BroadcastRetries = 0 // don't rely on retries to broadcast txns

// the caller account functions both as the destination caller and minter
callerPrivKey := ethMultiSendCfg.Networks.Ethereum.PrivateKey
privateKeyBytes := common.FromHex(callerPrivKey)
privateKey, err := crypto.ToECDSA(privateKeyBytes)
require.Nil(t, err)
pubKey := privateKey.Public()
publicKeyECDSA, ok := pubKey.(*ecdsa.PublicKey)
require.True(t, ok)
caller := crypto.PubkeyToAddress(*publicKeyECDSA).String()

for i, minter := range cfg.Networks.Minters {
switch i {
case 0:
minter.MinterAddress = caller
minter.MinterPrivateKey = callerPrivKey
cfg.Networks.Minters[0] = minter
}
}

nonce, err := eth.GetEthereumAccountNonce(cfg.Networks.Destination.Ethereum.RPC, caller)
require.Nil(t, err)

sequenceMap = types.NewSequenceMap()
sequenceMap.Put(uint32(0), nonce)

fmt.Println(fmt.Sprintf("Building %d Noble depositForBurnWithCaller txns...", n))

ethMintRecipient := "0x971c54a6Eb782fAccD00bc3Ed5E934Cc5bD8e3Ef"
fmt.Println("Minting on Ethereum to https://goerli.etherscan.io/address/" + ethMintRecipient)

// verify original eth usdc amount
client, err := ethclient.Dial(testCfg.Networks.Ethereum.RPC)
require.Nil(t, err)
defer client.Close()
originalEthBalance := getEthBalance(client, ethMintRecipient)
fmt.Println(fmt.Sprintf("original usdc balance: %d", originalEthBalance))

// deposit for burn with caller
interfaceRegistry := codectypes.NewInterfaceRegistry()
nobletypes.RegisterInterfaces(interfaceRegistry)
cdc := codec.NewProtoCodec(interfaceRegistry)

sdkContext := sdkClient.Context{
TxConfig: xauthtx.NewTxConfig(cdc, xauthtx.DefaultSignModes),
}
txBuilder := sdkContext.TxConfig.NewTxBuilder()

// get priv key
keyBz, _ := hex.DecodeString(testCfg.Networks.Noble.PrivateKey)
privKey := secp256k1.PrivKey{Key: keyBz}
nobleAddress, err := bech32.ConvertAndEncode("noble", privKey.PubKey().Address())
require.Nil(t, err)

mintRecipient := make([]byte, 32)
copy(mintRecipient[12:], common.FromHex(ethMintRecipient))

destinationCaller := make([]byte, 32)
copy(destinationCaller[12:], common.FromHex(caller[2:]))

var burnAmount = math.NewInt(1)

// deposit for burn on noble
accountNumber, accountSequence, err := GetNobleAccountNumberSequence(cfg.Networks.Destination.Noble.API, nobleAddress)
require.Nil(t, err)

for i := 0; i < n; i++ {
burnMsg := nobletypes.NewMsgDepositForBurnWithCaller(
nobleAddress,
burnAmount,
uint32(0),
mintRecipient,
"uusdc",
destinationCaller,
)

err = txBuilder.SetMsgs(burnMsg)
require.Nil(t, err)

txBuilder.SetGasLimit(300000)

// sign + broadcast txn
rpcClient, err := NewRPCClient(testCfg.Networks.Noble.RPC, 10*time.Second)
require.Nil(t, err)

sigV2 := signing.SignatureV2{
PubKey: privKey.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: sdkContext.TxConfig.SignModeHandler().DefaultMode(),
Signature: nil,
},
Sequence: uint64(accountSequence),
}

signerData := xauthsigning.SignerData{
ChainID: cfg.Networks.Destination.Noble.ChainId,
AccountNumber: uint64(accountNumber),
Sequence: uint64(accountSequence),
}

txBuilder.SetSignatures(sigV2)
sigV2, err = clientTx.SignWithPrivKey(
sdkContext.TxConfig.SignModeHandler().DefaultMode(),
signerData,
txBuilder,
&privKey,
sdkContext.TxConfig,
uint64(accountSequence),
)

err = txBuilder.SetSignatures(sigV2)
require.Nil(t, err)

// Generated Protobuf-encoded bytes.
txBytes, err := sdkContext.TxConfig.TxEncoder()(txBuilder.GetTx())
require.Nil(t, err)

rpcResponse, err := rpcClient.BroadcastTxSync(context.Background(), txBytes)
require.Nil(t, err)
fmt.Printf("Update pending: https://testnet.mintscan.io/noble-testnet/txs/%s\n", rpcResponse.Hash.String())

accountSequence++
}

fmt.Println("Waiting 60 seconds for attestations...")
time.Sleep(60 * time.Second)

fmt.Println("Starting relayer...")

processingQueue := make(chan *types.MessageState, 100)

go noble.StartListener(cfg, logger, processingQueue)
go cmd.StartProcessor(cfg, logger, processingQueue, sequenceMap)

fmt.Println("Checking eth wallet...")
for i := 0; i < 60; i++ {
if originalEthBalance+burnAmount.Uint64()*uint64(n) == getEthBalance(client, ethMintRecipient) {
fmt.Println(fmt.Sprintf("New eth balance: %d", getEthBalance(client, ethMintRecipient)))
fmt.Println(fmt.Sprintf("Successfully minted %d times at https://goerli.etherscan.io/address/%s", n, ethMintRecipient))
return
}
time.Sleep(1 * time.Second)
}

require.Equal(t, originalEthBalance+burnAmount.Uint64()*uint64(n), getEthBalance(client, ethMintRecipient))
}
1 change: 0 additions & 1 deletion integration/noble_burn_to_eth_mint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ func TestNobleBurnToEthMint(t *testing.T) {

// start up relayer
cfg.Networks.Source.Noble.StartBlock = getNobleLatestBlockHeight()
cfg.Networks.Source.Noble.LookbackPeriod = 0

fmt.Println("Starting relayer...")
processingQueue := make(chan *types.MessageState, 10)
Expand Down
3 changes: 2 additions & 1 deletion integration/noble_multi_send_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestNobleMultiSend(t *testing.T) {
cfg.Networks.Source.Ethereum.LookbackPeriod = 5
cfg.Networks.Destination.Noble.BroadcastRetries = 0 // don't rely on retries to broadcast txns

fmt.Println(fmt.Sprintf("Building %d Ethereum depositForBurnWithMetadata txns...", n))
fmt.Println(fmt.Sprintf("Building %d Ethereum depositForBurnWithCaller txns...", n))

_, _, cosmosAddress := testdata.KeyTestPubAddr()
nobleAddress, _ := bech32.ConvertAndEncode("noble", cosmosAddress)
Expand Down Expand Up @@ -135,4 +135,5 @@ func TestNobleMultiSend(t *testing.T) {
}
time.Sleep(1 * time.Second)
}
require.Equal(t, originalNobleBalance+burnAmount.Uint64()*uint64(n), getNobleBalance(nobleAddress))
}

0 comments on commit 6f5bfbb

Please sign in to comment.