Skip to content

Commit

Permalink
feat: bridge_getTokenMappings endpoint (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
Stefan-Ethernal authored Feb 19, 2025
1 parent 061c6dd commit ee07ffc
Show file tree
Hide file tree
Showing 29 changed files with 27,666 additions and 232 deletions.
136 changes: 136 additions & 0 deletions agglayer.log

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions bridgesync/bridgesync.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bridgesync

import (
"context"
"errors"
"fmt"
"time"

Expand All @@ -19,6 +20,14 @@ const (
downloadBufferSize = 1000
)

var (
// ErrInvalidPageSize indicates that the page size is invalid
ErrInvalidPageSize = errors.New("page size must be greater than 0")

// ErrInvalidPageNumber indicates that the page number is invalid
ErrInvalidPageNumber = errors.New("page number must be greater than 0")
)

type ReorgDetector interface {
sync.ReorgDetector
}
Expand Down Expand Up @@ -246,6 +255,22 @@ func (s *BridgeSync) GetBridgesPublished(ctx context.Context, fromBlock, toBlock
return s.processor.GetBridgesPublished(ctx, fromBlock, toBlock)
}

func (s *BridgeSync) GetTokenMappings(ctx context.Context, pageNumber, pageSize uint32) ([]*TokenMapping, int, error) {
if s.processor.isHalted() {
return nil, 0, sync.ErrInconsistentState
}

if pageNumber == 0 {
return nil, 0, ErrInvalidPageNumber
}

if pageSize == 0 {
return nil, 0, ErrInvalidPageSize
}

return s.processor.GetTokenMappings(ctx, pageNumber, pageSize)
}

func (s *BridgeSync) GetProof(ctx context.Context, depositCount uint32, localExitRoot common.Hash) (tree.Proof, error) {
if s.processor.isHalted() {
return tree.Proof{}, sync.ErrInconsistentState
Expand Down
185 changes: 151 additions & 34 deletions bridgesync/bridgesync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,21 @@ package bridgesync

import (
"context"
"errors"
"fmt"
"path"
"testing"
"time"

mocksbridgesync "github.com/agglayer/aggkit/bridgesync/mocks"
"github.com/agglayer/aggkit/db"
"github.com/agglayer/aggkit/etherman"
"github.com/agglayer/aggkit/sync"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

// Mock implementations for the interfaces
type MockEthClienter struct {
mock.Mock
}

type MockBridgeContractor struct {
mock.Mock
}

func TestNewLx(t *testing.T) {
ctx := context.Background()
dbPath := path.Join(t.TempDir(), "TestNewLx.sqlite")
bridge := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
const (
syncBlockChunkSize = uint64(100)
initialBlock = uint64(0)
Expand All @@ -37,7 +25,13 @@ func TestNewLx(t *testing.T) {
maxRetryAttemptsAfterError = 3
originNetwork = uint32(1)
)
var blockFinalityType = etherman.SafeBlock

var (
blockFinalityType = etherman.SafeBlock
ctx = context.Background()
dbPath = path.Join(t.TempDir(), "TestNewLx.sqlite")
bridge = common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
)

mockEthClient := mocksbridgesync.NewEthClienter(t)
mockEthClient.EXPECT().CallContract(mock.Anything, mock.Anything, mock.Anything).Return(
Expand All @@ -63,10 +57,10 @@ func TestNewLx(t *testing.T) {
false,
)

assert.NoError(t, err)
assert.NotNil(t, l1BridgeSync)
assert.Equal(t, originNetwork, l1BridgeSync.OriginNetwork())
assert.Equal(t, blockFinalityType, l1BridgeSync.BlockFinality())
require.NoError(t, err)
require.NotNil(t, l1BridgeSync)
require.Equal(t, originNetwork, l1BridgeSync.OriginNetwork())
require.Equal(t, blockFinalityType, l1BridgeSync.BlockFinality())

l2BridgdeSync, err := NewL2(
ctx,
Expand All @@ -84,10 +78,10 @@ func TestNewLx(t *testing.T) {
false,
)

assert.NoError(t, err)
assert.NotNil(t, l1BridgeSync)
assert.Equal(t, originNetwork, l2BridgdeSync.OriginNetwork())
assert.Equal(t, blockFinalityType, l2BridgdeSync.BlockFinality())
require.NoError(t, err)
require.NotNil(t, l1BridgeSync)
require.Equal(t, originNetwork, l2BridgdeSync.OriginNetwork())
require.Equal(t, blockFinalityType, l2BridgdeSync.BlockFinality())

// Fails the sanity check of the contract address
mockEthClient = mocksbridgesync.NewEthClienter(t)
Expand All @@ -109,60 +103,183 @@ func TestNewLx(t *testing.T) {
false,
)
t.Log(err)
assert.Error(t, err)
assert.Nil(t, l2BridgdeSyncErr)
require.Error(t, err)
require.Nil(t, l2BridgdeSyncErr)
}

func TestGetLastProcessedBlock(t *testing.T) {
s := BridgeSync{processor: &processor{halted: true}}
_, err := s.GetLastProcessedBlock(context.Background())
require.True(t, errors.Is(err, sync.ErrInconsistentState))
require.ErrorIs(t, err, sync.ErrInconsistentState)
}

func TestGetBridgeRootByHash(t *testing.T) {
s := BridgeSync{processor: &processor{halted: true}}
_, err := s.GetBridgeRootByHash(context.Background(), common.Hash{})
require.True(t, errors.Is(err, sync.ErrInconsistentState))
require.ErrorIs(t, err, sync.ErrInconsistentState)
}

func TestGetBridges(t *testing.T) {
s := BridgeSync{processor: &processor{halted: true}}
_, err := s.GetBridges(context.Background(), 0, 0)
require.True(t, errors.Is(err, sync.ErrInconsistentState))
require.ErrorIs(t, err, sync.ErrInconsistentState)
}

func TestGetProof(t *testing.T) {
s := BridgeSync{processor: &processor{halted: true}}
_, err := s.GetProof(context.Background(), 0, common.Hash{})
require.True(t, errors.Is(err, sync.ErrInconsistentState))
require.ErrorIs(t, err, sync.ErrInconsistentState)
}

func TestGetBlockByLER(t *testing.T) {
s := BridgeSync{processor: &processor{halted: true}}
_, err := s.GetBlockByLER(context.Background(), common.Hash{})
require.True(t, errors.Is(err, sync.ErrInconsistentState))
require.ErrorIs(t, err, sync.ErrInconsistentState)
}

func TestGetRootByLER(t *testing.T) {
s := BridgeSync{processor: &processor{halted: true}}
_, err := s.GetRootByLER(context.Background(), common.Hash{})
require.True(t, errors.Is(err, sync.ErrInconsistentState))
require.ErrorIs(t, err, sync.ErrInconsistentState)
}

func TestGetExitRootByIndex(t *testing.T) {
s := BridgeSync{processor: &processor{halted: true}}
_, err := s.GetExitRootByIndex(context.Background(), 0)
require.True(t, errors.Is(err, sync.ErrInconsistentState))
require.ErrorIs(t, err, sync.ErrInconsistentState)
}

func TestGetClaims(t *testing.T) {
s := BridgeSync{processor: &processor{halted: true}}
_, err := s.GetClaims(context.Background(), 0, 0)
require.True(t, errors.Is(err, sync.ErrInconsistentState))
require.ErrorIs(t, err, sync.ErrInconsistentState)
}

func TestGetBridgesPublishedTopLevel(t *testing.T) {
s := BridgeSync{processor: &processor{halted: true}}
_, err := s.GetBridgesPublished(context.Background(), 0, 0)
require.True(t, errors.Is(err, sync.ErrInconsistentState))
require.ErrorIs(t, err, sync.ErrInconsistentState)
}

func TestGetTokenMappings(t *testing.T) {
const (
syncBlockChunkSize = uint64(100)
initialBlock = uint64(0)
waitForNewBlocksPeriod = time.Second * 10
retryAfterErrorPeriod = time.Second * 5
maxRetryAttemptsAfterError = 3
originNetwork = uint32(1)
tokenMappingsCount = 20
blockNum = uint64(1)
)

var (
blockFinalityType = etherman.SafeBlock
ctx = context.Background()
dbPath = path.Join(t.TempDir(), "TestGetTokenMappings.sqlite")
bridge = common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
)

mockEthClient := mocksbridgesync.NewEthClienter(t)
mockEthClient.EXPECT().CallContract(mock.Anything, mock.Anything, mock.Anything).Return(
common.FromHex("0x000000000000000000000000000000000000000000000000000000000000002a"), nil).Once()
mockReorgDetector := mocksbridgesync.NewReorgDetector(t)

mockReorgDetector.EXPECT().Subscribe(mock.Anything).Return(nil, nil)
mockReorgDetector.EXPECT().GetFinalizedBlockType().Return(blockFinalityType)
mockReorgDetector.EXPECT().String().Return("mockReorgDetector")

s, err := NewL2(
ctx,
dbPath,
bridge,
syncBlockChunkSize,
blockFinalityType,
mockReorgDetector,
mockEthClient,
initialBlock,
waitForNewBlocksPeriod,
retryAfterErrorPeriod,
maxRetryAttemptsAfterError,
originNetwork,
false,
)
require.NoError(t, err)

allTokenMappings := make([]*TokenMapping, 0, tokenMappingsCount)
genericEvts := make([]interface{}, 0, tokenMappingsCount)

for i := tokenMappingsCount - 1; i >= 0; i-- {
tokenMappingEvt := &TokenMapping{
BlockNum: blockNum,
BlockPos: uint64(i),
OriginNetwork: uint32(i),
OriginTokenAddress: common.HexToAddress(fmt.Sprintf("%d", i)),
WrappedTokenAddress: common.HexToAddress(fmt.Sprintf("%d", i+1)),
}

allTokenMappings = append(allTokenMappings, tokenMappingEvt)
genericEvts = append(genericEvts, Event{TokenMapping: tokenMappingEvt})
}

block := sync.Block{
Num: blockNum,
Events: genericEvts,
}

err = s.processor.ProcessBlock(context.Background(), block)
require.NoError(t, err)

t.Run("retrieve all mappings", func(t *testing.T) {
tokenMappings, totalTokenMappings, err := s.GetTokenMappings(context.Background(), 1, tokenMappingsCount)
require.NoError(t, err)
require.Equal(t, tokenMappingsCount, totalTokenMappings)
require.Equal(t, allTokenMappings, tokenMappings)
})

t.Run("retrieve paginated mappings", func(t *testing.T) {
pageSize := uint32(5)

for page := uint32(1); page <= 4; page++ {
tokenMappings, totalTokenMappings, err := s.GetTokenMappings(context.Background(), page, pageSize)
require.NoError(t, err)
require.Equal(t, tokenMappingsCount, totalTokenMappings)

startIndex := (page - 1) * pageSize
endIndex := startIndex + pageSize
require.Equal(t, allTokenMappings[startIndex:endIndex], tokenMappings)
}
})

t.Run("retrieve non-existent page", func(t *testing.T) {
pageSize := uint32(5)
pageNum := uint32(5)

tokenMappings, totalTokenMappings, err := s.GetTokenMappings(context.Background(), pageNum, pageSize)
require.ErrorIs(t, err, db.ErrNotFound)
require.Equal(t, 0, totalTokenMappings)
require.Nil(t, tokenMappings)
})

t.Run("provide invalid page number", func(t *testing.T) {
pageSize := uint32(0)
pageNum := uint32(0)

_, _, err := s.GetTokenMappings(context.Background(), pageNum, pageSize)
require.ErrorIs(t, err, ErrInvalidPageNumber)
})

t.Run("provide invalid page size", func(t *testing.T) {
pageSize := uint32(0)
pageNum := uint32(4)

_, _, err := s.GetTokenMappings(context.Background(), pageNum, pageSize)
require.ErrorIs(t, err, ErrInvalidPageSize)
})

t.Run("inconsistent state", func(t *testing.T) {
s.processor.halted = true
_, _, err := s.GetTokenMappings(context.Background(), 0, 0)
require.ErrorIs(t, err, sync.ErrInconsistentState)
})
}
25 changes: 25 additions & 0 deletions bridgesync/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var (
))
claimEventSignature = crypto.Keccak256Hash([]byte("ClaimEvent(uint256,uint32,address,address,uint256)"))
claimEventSignaturePreEtrog = crypto.Keccak256Hash([]byte("ClaimEvent(uint32,uint32,address,address,uint256)"))
tokenMappingEventSignature = crypto.Keccak256Hash([]byte("NewWrappedToken(uint32,address,address,bytes)"))
methodIDClaimAsset = common.Hex2Bytes("ccaa2d11")
methodIDClaimMessage = common.Hex2Bytes("f5efcd79")
)
Expand All @@ -46,10 +47,12 @@ func buildAppender(client EthClienter, bridge common.Address, syncFullClaims boo
if err != nil {
return nil, err
}

bridgeContractV2, err := polygonzkevmbridgev2.NewPolygonzkevmbridgev2(bridge, client)
if err != nil {
return nil, err
}

appender := make(sync.LogAppenderMap)

appender[bridgeEventSignature] = func(b *sync.EVMBlock, l types.Log) error {
Expand Down Expand Up @@ -128,6 +131,28 @@ func buildAppender(client EthClienter, bridge common.Address, syncFullClaims boo
return nil
}

appender[tokenMappingEventSignature] = func(b *sync.EVMBlock, l types.Log) error {
tokenMapping, err := bridgeContractV2.ParseNewWrappedToken(l)
if err != nil {
return fmt.Errorf(
"error parsing log %+v using d.bridgeContractV2.ParseNewWrappedToken: %w",
l, err,
)
}

b.Events = append(b.Events, Event{TokenMapping: &TokenMapping{
BlockNum: b.Num,
BlockPos: uint64(l.Index),
BlockTimestamp: b.Timestamp,
TxHash: l.TxHash,
OriginNetwork: tokenMapping.OriginNetwork,
OriginTokenAddress: tokenMapping.OriginTokenAddress,
WrappedTokenAddress: tokenMapping.WrappedTokenAddress,
Metadata: tokenMapping.Metadata,
}})
return nil
}

return appender, nil
}

Expand Down
Loading

0 comments on commit ee07ffc

Please sign in to comment.