Skip to content

Commit

Permalink
feat: include aggsender to release 0.4.0 (#181)
Browse files Browse the repository at this point in the history
* feat: unpack and log agglayer errors (#158)

* feat: unpack and log agglayer errors

* feat: agglayer error unpacking

* fix: lint and UT

* feat: epoch notifier (#144)

- Send certificates after a percentage of epoch
- Require epoch configuration to AggLayer
- Change config of `aggsender` adding: `BlockFinality` and `EpochNotificationPercentage`

* refact: GetSequence method (#169)

* feat: remove sanity check (#178) (#179)

---------

Co-authored-by: Goran Rojovic <[email protected]>
Co-authored-by: Rachit Sonthalia <[email protected]>
Co-authored-by: Toni Ramírez <[email protected]>
  • Loading branch information
4 people authored Nov 12, 2024
1 parent b480dd4 commit 0ed309e
Show file tree
Hide file tree
Showing 56 changed files with 4,884 additions and 1,433 deletions.
30 changes: 29 additions & 1 deletion agglayer/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,22 @@ import (

const errCodeAgglayerRateLimitExceeded int = -10007

var ErrAgglayerRateLimitExceeded = fmt.Errorf("agglayer rate limit exceeded")
var (
ErrAgglayerRateLimitExceeded = fmt.Errorf("agglayer rate limit exceeded")
jSONRPCCall = rpc.JSONRPCCall
)

type AggLayerClientGetEpochConfiguration interface {
GetEpochConfiguration() (*ClockConfiguration, error)
}

// AgglayerClientInterface is the interface that defines the methods that the AggLayerClient will implement
type AgglayerClientInterface interface {
SendTx(signedTx SignedTx) (common.Hash, error)
WaitTxToBeMined(hash common.Hash, ctx context.Context) error
SendCertificate(certificate *SignedCertificate) (common.Hash, error)
GetCertificateHeader(certificateHash common.Hash) (*CertificateHeader, error)
AggLayerClientGetEpochConfiguration
}

// AggLayerClient is the client that will be used to interact with the AggLayer
Expand Down Expand Up @@ -130,3 +138,23 @@ func (c *AggLayerClient) GetCertificateHeader(certificateHash common.Hash) (*Cer

return result, nil
}

// GetEpochConfiguration returns the clock configuration of AggLayer
func (c *AggLayerClient) GetEpochConfiguration() (*ClockConfiguration, error) {
response, err := jSONRPCCall(c.url, "interop_getEpochConfiguration")
if err != nil {
return nil, err
}

if response.Error != nil {
return nil, fmt.Errorf("GetEpochConfiguration code=%d msg=%s", response.Error.Code, response.Error.Message)
}

var result *ClockConfiguration
err = json.Unmarshal(response.Result, &result)
if err != nil {
return nil, err
}

return result, nil
}
76 changes: 76 additions & 0 deletions agglayer/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package agglayer

import (
"fmt"
"testing"

"github.com/0xPolygon/cdk-rpc/rpc"
"github.com/stretchr/testify/require"
)

const (
testURL = "http://localhost:8080"
)

func TestExploratoryClient(t *testing.T) {
t.Skip("This test is for exploratory purposes only")
sut := NewAggLayerClient("http://127.0.0.1:32853")
config, err := sut.GetEpochConfiguration()
require.NoError(t, err)
require.NotNil(t, config)
fmt.Printf("Config: %s", config.String())
}

func TestGetEpochConfigurationResponseWithError(t *testing.T) {
sut := NewAggLayerClient(testURL)
response := rpc.Response{
Error: &rpc.ErrorObject{},
}
jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) {
return response, nil
}
clockConfig, err := sut.GetEpochConfiguration()
require.Nil(t, clockConfig)
require.Error(t, err)
}

func TestGetEpochConfigurationResponseBadJson(t *testing.T) {
sut := NewAggLayerClient(testURL)
response := rpc.Response{
Result: []byte(`{`),
}
jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) {
return response, nil
}
clockConfig, err := sut.GetEpochConfiguration()
require.Nil(t, clockConfig)
require.Error(t, err)
}

func TestGetEpochConfigurationErrorResponse(t *testing.T) {
sut := NewAggLayerClient(testURL)

jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) {
return rpc.Response{}, fmt.Errorf("unittest error")
}
clockConfig, err := sut.GetEpochConfiguration()
require.Nil(t, clockConfig)
require.Error(t, err)
}

func TestGetEpochConfigurationOkResponse(t *testing.T) {
sut := NewAggLayerClient(testURL)
response := rpc.Response{
Result: []byte(`{"epoch_duration": 1, "genesis_block": 1}`),
}
jSONRPCCall = func(url, method string, params ...interface{}) (rpc.Response, error) {
return response, nil
}
clockConfig, err := sut.GetEpochConfiguration()
require.NotNil(t, clockConfig)
require.NoError(t, err)
require.Equal(t, ClockConfiguration{
EpochDuration: 1,
GenesisBlock: 1,
}, *clockConfig)
}
270 changes: 270 additions & 0 deletions agglayer/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
package agglayer

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func TestErrorVectors(t *testing.T) {
t.Parallel()

type testCase struct {
TestName string `json:"test_name"`
ExpectedError string `json:"expected_error"`
CertificateHeaderJSON string `json:"certificate_header"`
}

files, err := filepath.Glob("testdata/*/*.json")
require.NoError(t, err)

for _, file := range files {
file := file

t.Run(file, func(t *testing.T) {
t.Parallel()

data, err := os.ReadFile(file)
require.NoError(t, err)

var testCases []*testCase

require.NoError(t, json.Unmarshal(data, &testCases))

for _, tc := range testCases {
certificateHeader := &CertificateHeader{}
err = json.Unmarshal([]byte(tc.CertificateHeaderJSON), certificateHeader)

if tc.ExpectedError == "" {
require.NoError(t, err, "Test: %s not expected any unmarshal error, but got: %v", tc.TestName, err)
require.NotNil(t, certificateHeader.Error, "Test: %s unpacked error is nil", tc.TestName)
fmt.Println(certificateHeader.Error.String())
} else {
require.ErrorContains(t, err, tc.ExpectedError, "Test: %s expected error: %s. Got: %v", tc.TestName, tc.ExpectedError, err)
}
}
})
}
}

func TestConvertMapValue_String(t *testing.T) {
t.Parallel()

tests := []struct {
name string
data map[string]interface{}
key string
want string
errString string
}{
{
name: "Key exists and type matches",
data: map[string]interface{}{
"key1": "value1",
},
key: "key1",
want: "value1",
},
{
name: "Key exists but type does not match",
data: map[string]interface{}{
"key1": 1,
},
key: "key1",
want: "",
errString: "is not of type",
},
{
name: "Key does not exist",
data: map[string]interface{}{
"key1": "value1",
},
key: "key2",
want: "",
errString: "key key2 not found in map",
},
}

for _, tt := range tests {
tt := tt

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got, err := convertMapValue[string](tt.data, tt.key)
if tt.errString != "" {
require.ErrorContains(t, err, tt.errString)
} else {
require.Equal(t, tt.want, got)
}
})
}
}

//nolint:dupl
func TestConvertMapValue_Uint32(t *testing.T) {
t.Parallel()

tests := []struct {
name string
data map[string]interface{}
key string
want uint32
errString string
}{
{
name: "Key exists and type matches",
data: map[string]interface{}{
"key1": uint32(123),
},
key: "key1",
want: uint32(123),
},
{
name: "Key exists but type does not match",
data: map[string]interface{}{
"key1": "value1",
},
key: "key1",
want: 0,
errString: "is not of type",
},
{
name: "Key does not exist",
data: map[string]interface{}{
"key1": uint32(123),
},
key: "key2",
want: 0,
errString: "key key2 not found in map",
},
}

for _, tt := range tests {
tt := tt

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got, err := convertMapValue[uint32](tt.data, tt.key)
if tt.errString != "" {
require.ErrorContains(t, err, tt.errString)
} else {
require.Equal(t, tt.want, got)
}
})
}
}

//nolint:dupl
func TestConvertMapValue_Uint64(t *testing.T) {
t.Parallel()

tests := []struct {
name string
data map[string]interface{}
key string
want uint64
errString string
}{
{
name: "Key exists and type matches",
data: map[string]interface{}{
"key1": uint64(3411),
},
key: "key1",
want: uint64(3411),
},
{
name: "Key exists but type does not match",
data: map[string]interface{}{
"key1": "not a number",
},
key: "key1",
want: 0,
errString: "is not of type",
},
{
name: "Key does not exist",
data: map[string]interface{}{
"key1": uint64(123555),
},
key: "key22",
want: 0,
errString: "key key22 not found in map",
},
}

for _, tt := range tests {
tt := tt

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got, err := convertMapValue[uint64](tt.data, tt.key)
if tt.errString != "" {
require.ErrorContains(t, err, tt.errString)
} else {
require.Equal(t, tt.want, got)
}
})
}
}

func TestConvertMapValue_Bool(t *testing.T) {
t.Parallel()

tests := []struct {
name string
data map[string]interface{}
key string
want bool
errString string
}{
{
name: "Key exists and type matches",
data: map[string]interface{}{
"key1": true,
},
key: "key1",
want: true,
},
{
name: "Key exists but type does not match",
data: map[string]interface{}{
"key1": "value1",
},
key: "key1",
want: false,
errString: "is not of type",
},
{
name: "Key does not exist",
data: map[string]interface{}{
"key1": true,
},
key: "key2",
want: false,
errString: "key key2 not found in map",
},
}

for _, tt := range tests {
tt := tt

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got, err := convertMapValue[bool](tt.data, tt.key)
if tt.errString != "" {
require.ErrorContains(t, err, tt.errString)
} else {
require.Equal(t, tt.want, got)
}
})
}
}
Loading

0 comments on commit 0ed309e

Please sign in to comment.