Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit tests #21

Merged
merged 18 commits into from
Sep 22, 2023
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
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,36 @@
Standalone relayer for cross-chain Avalanche Warp Message delivery.

## Usage
---

### Building

Build the relayer by running the included build script:
```

```bash
./scripts/build.sh
```

Build a Docker image by running the included build script:
```
./scripts/build-local-image.sh
```

### Running

The relayer binary accepts a path to a JSON configuration file as the sole argument. Command line configuration arguments are not currently supported.
```

```bash
./build/awm-relayer --config-file path-to-config
```

## Architecture
---

**Note:** The relayer in its current state supports Teleporter messages between `subnet-evm` instances. A handful of abstractions have been added to make the relayer extensible to other Warp message formats and VM types, but this work is ongoing.

### Components

The relayer consists of the following components:

- At the global level:
- *P2P App Network*: issues signature `AppRequests`
- *P-Chain client*: gets the validators for a subnet
Expand All @@ -34,6 +42,17 @@ The relayer consists of the following components:
- *Destination RPC client*: broadcasts transactions to the destination

### Data flow

<div align="center">
<img src="resources/relayer-diagram.png?raw=true">
</div>

## Testing

### Generate Mocks

We use [gomock](https://pkg.go.dev/go.uber.org/mock/gomock) to generate mocks for testing. To generate mocks, run the following command at the root of the project:

```bash
go generate ./...
```
81 changes: 55 additions & 26 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (

"github.com/ava-labs/awm-relayer/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand All @@ -39,7 +38,7 @@ var (
EncryptConnection: false,
MessageContracts: map[string]MessageProtocolConfig{
testAddress: {
MessageFormat: "teleporter",
MessageFormat: TELEPORTER.String(),
},
},
},
Expand All @@ -62,10 +61,12 @@ var (

func TestGetDestinationRPCEndpoint(t *testing.T) {
testCases := []struct {
name string
s DestinationSubnet
expectedResult string
}{
{
name: "No encrypt connection",
s: DestinationSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
Expand All @@ -76,6 +77,7 @@ func TestGetDestinationRPCEndpoint(t *testing.T) {
expectedResult: fmt.Sprintf("http://127.0.0.1:9650/ext/bc/%s/rpc", testChainID),
},
{
name: "Encrypt connection",
s: DestinationSubnet{
EncryptConnection: true,
APINodeHost: "127.0.0.1",
Expand All @@ -86,6 +88,7 @@ func TestGetDestinationRPCEndpoint(t *testing.T) {
expectedResult: fmt.Sprintf("https://127.0.0.1:9650/ext/bc/%s/rpc", testChainID),
},
{
name: "No port",
s: DestinationSubnet{
EncryptConnection: false,
APINodeHost: "api.avax.network",
Expand All @@ -96,6 +99,7 @@ func TestGetDestinationRPCEndpoint(t *testing.T) {
expectedResult: fmt.Sprintf("http://api.avax.network/ext/bc/%s/rpc", testChainID),
},
{
name: "Primary subnet",
s: DestinationSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
Expand All @@ -106,6 +110,7 @@ func TestGetDestinationRPCEndpoint(t *testing.T) {
expectedResult: "http://127.0.0.1:9650/ext/bc/C/rpc",
},
{
name: "Override with set rpc endpoint",
s: DestinationSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
Expand All @@ -118,19 +123,23 @@ func TestGetDestinationRPCEndpoint(t *testing.T) {
},
}

for i, testCase := range testCases {
res := testCase.s.GetNodeRPCEndpoint()
assert.Equal(t, testCase.expectedResult, res, fmt.Sprintf("test case %d failed", i))
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
res := testCase.s.GetNodeRPCEndpoint()
require.Equal(t, testCase.expectedResult, res)
})
}
}

func TestGetSourceSubnetEndpoints(t *testing.T) {
testCases := []struct {
name string
s SourceSubnet
expectedWsResult string
expectedRpcResult string
}{
{
name: "No encrypt connection",
s: SourceSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
Expand All @@ -142,6 +151,7 @@ func TestGetSourceSubnetEndpoints(t *testing.T) {
expectedRpcResult: fmt.Sprintf("http://127.0.0.1:9650/ext/bc/%s/rpc", testChainID),
},
{
name: "Encrypt connection",
s: SourceSubnet{
EncryptConnection: true,
APINodeHost: "127.0.0.1",
Expand All @@ -153,6 +163,7 @@ func TestGetSourceSubnetEndpoints(t *testing.T) {
expectedRpcResult: fmt.Sprintf("https://127.0.0.1:9650/ext/bc/%s/rpc", testChainID),
},
{
name: "No port",
s: SourceSubnet{
EncryptConnection: false,
APINodeHost: "api.avax.network",
Expand All @@ -164,6 +175,7 @@ func TestGetSourceSubnetEndpoints(t *testing.T) {
expectedRpcResult: fmt.Sprintf("http://api.avax.network/ext/bc/%s/rpc", testChainID),
},
{
name: "Primary subnet",
s: SourceSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
Expand All @@ -175,6 +187,7 @@ func TestGetSourceSubnetEndpoints(t *testing.T) {
expectedRpcResult: "http://127.0.0.1:9650/ext/bc/C/rpc",
},
{
name: "Override with set endpoints",
s: SourceSubnet{
EncryptConnection: false,
APINodeHost: "127.0.0.1",
Expand All @@ -189,9 +202,11 @@ func TestGetSourceSubnetEndpoints(t *testing.T) {
},
}

for i, testCase := range testCases {
assert.Equal(t, testCase.expectedWsResult, testCase.s.GetNodeWSEndpoint(), fmt.Sprintf("test case %d failed", i))
assert.Equal(t, testCase.expectedRpcResult, testCase.s.GetNodeRPCEndpoint(), fmt.Sprintf("test case %d failed", i))
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
require.Equal(t, testCase.expectedWsResult, testCase.s.GetNodeWSEndpoint())
require.Equal(t, testCase.expectedRpcResult, testCase.s.GetNodeRPCEndpoint())
})
}
}

Expand All @@ -203,11 +218,12 @@ func TestGetRelayerAccountInfo(t *testing.T) {
}

testCases := []struct {
name string
s DestinationSubnet
expectedResult retStruct
}{
// valid
{
name: "valid",
s: DestinationSubnet{
AccountPrivateKey: "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
},
Expand All @@ -219,8 +235,8 @@ func TestGetRelayerAccountInfo(t *testing.T) {
err: nil,
},
},
// invalid, with 0x prefix. Should be sanitized before being set in DestinationSubnet
{
name: "invalid 0x prefix",
s: DestinationSubnet{
AccountPrivateKey: "0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
},
Expand All @@ -232,8 +248,8 @@ func TestGetRelayerAccountInfo(t *testing.T) {
err: ErrInvalidPrivateKey,
},
},
// invalid
{
name: "invalid private key",
s: DestinationSubnet{
AccountPrivateKey: "invalid56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
},
Expand All @@ -247,13 +263,15 @@ func TestGetRelayerAccountInfo(t *testing.T) {
},
}

for i, testCase := range testCases {
pk, addr, err := testCase.s.GetRelayerAccountInfo()
assert.Equal(t, testCase.expectedResult.err, err, fmt.Sprintf("test case %d had unexpected error", i))
if err == nil {
assert.Equal(t, testCase.expectedResult.pk.D.Int64(), pk.D.Int64(), fmt.Sprintf("test case %d had mismatched pk", i))
assert.Equal(t, testCase.expectedResult.addr, addr, fmt.Sprintf("test case %d had mismatched address", i))
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
pk, addr, err := testCase.s.GetRelayerAccountInfo()
require.Equal(t, testCase.expectedResult.err, err)
if err == nil {
require.Equal(t, testCase.expectedResult.pk.D.Int64(), pk.D.Int64())
require.Equal(t, testCase.expectedResult.addr, addr)
}
})
}
}

Expand All @@ -270,12 +288,11 @@ type getRelayerAccountPrivateKeyTestCase struct {

// Sets up the config file temporary environment and runs the test case.
func runGetRelayerAccountPrivateKeyTest(t *testing.T, testCase getRelayerAccountPrivateKeyTestCase) {
require := require.New(t)
root := t.TempDir()

cfg := testCase.configModifier(testCase.baseConfig)
cfgBytes, err := json.Marshal(cfg)
require.NoError(err)
require.NoError(t, err)

configFile := setupConfigJSON(t, root, string(cfgBytes))

Expand All @@ -284,13 +301,12 @@ func runGetRelayerAccountPrivateKeyTest(t *testing.T, testCase getRelayerAccount

fs := BuildFlagSet()
v, err := BuildViper(fs, flags)
require.NoError(err)
require.NoError(t, err)
parsedCfg, optionOverwritten, err := BuildConfig(v)
require.NoError(err)
assert.Equal(t, optionOverwritten, testCase.expectedOverwritten)
if !testCase.resultVerifier(parsedCfg) {
t.Errorf("unexpected config.")
}
require.NoError(t, err)
require.Equal(t, optionOverwritten, testCase.expectedOverwritten)

require.True(t, testCase.resultVerifier(parsedCfg))
}

func TestGetRelayerAccountPrivateKey_set_pk_in_config(t *testing.T) {
Expand Down Expand Up @@ -381,3 +397,16 @@ func setupConfigJSON(t *testing.T, rootPath string, value string) string {
require.NoError(t, os.WriteFile(configFilePath, []byte(value), 0o600))
return configFilePath
}

func TestGetRelayerAccountInfoSkipChainConfigCheckCompatible(t *testing.T) {
accountPrivateKey := "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027"
expectedAddress := "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"

info := DestinationSubnet{
AccountPrivateKey: accountPrivateKey,
}
_, address, err := info.GetRelayerAccountInfo()

require.NoError(t, err)
require.Equal(t, expectedAddress, address.String())
}
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ require (
github.com/ava-labs/avalanchego v1.10.9
github.com/ava-labs/subnet-evm v0.5.4
github.com/ethereum/go-ethereum v1.12.0
github.com/golang/mock v1.6.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.16.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.16.0
github.com/stretchr/testify v1.8.4
go.uber.org/mock v0.2.0
go.uber.org/zap v1.25.0
)

Expand All @@ -30,13 +31,11 @@ require (
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
go.uber.org/mock v0.2.0 // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
Expand Down
3 changes: 0 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down Expand Up @@ -818,7 +816,6 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
2 changes: 2 additions & 0 deletions messages/message_manager.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

//go:generate mockgen -source=$GOFILE -destination=./mocks/mock_message_manager.go -package=mocks

package messages

import (
Expand Down
Loading
Loading