Skip to content

Commit

Permalink
Merge branch 'main' into e2e-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cam-schultz committed Sep 25, 2023
2 parents 53f3833 + 074e8aa commit a4d2cf5
Show file tree
Hide file tree
Showing 20 changed files with 1,288 additions and 113 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ jobs:

# Needed for multi-platform builds
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3

# Needed for multi-platform builds
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and Push to Docker Hub
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/arm64
push: true
Expand Down
26 changes: 21 additions & 5 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,7 @@ 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>
Expand Down Expand Up @@ -70,4 +79,11 @@ Then, in the root of the `awm-relayer` project, run:
AVALANCHEGO_BUILD_PATH=~/tmp/e2e-test/avalanchego DATA_DIR=~/tmp/e2e-test/data ./scripts/e2e_test.sh
```

Note that any additional E2E tests that run VMs other than `subnet-evm` will need to install and setup the VM binary in the same way.
Note that any additional E2E tests that run VMs other than `subnet-evm` will need to install and setup the VM binary in the same way.
### 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 ./...
```
79 changes: 54 additions & 25 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 Down Expand Up @@ -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())
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
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
go.uber.org/zap v1.26.0
)

require (
Expand Down
5 changes: 2 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ github.com/ava-labs/coreth v0.12.5-rc.3/go.mod h1:HI+jTIflnDFBd0bledgkgid1Uurwr8
github.com/ava-labs/subnet-evm v0.5.4 h1:4+UHva8rhGlvH4gDYpI0Lt6/J5ie1DqQa6kEmbebArI=
github.com/ava-labs/subnet-evm v0.5.4/go.mod h1:PAyhfYnECzA17N62i7OAdKazjfSsN2l8KR5nOspg39I=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
Expand Down Expand Up @@ -615,8 +614,8 @@ go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU=
go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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

0 comments on commit a4d2cf5

Please sign in to comment.