Skip to content

Commit fbd5fa1

Browse files
tclemospraetoriansentryleovct
authored
feat!: ulxly integration with agglayer bridge service (#697)
* ulxly: refactoring bridge service to allow multiple service implementations * ulxly: add agglayer bridge service integration * fix typos * small fixes * ulxly: refactoring agglayer bridge service; fix typos * WIP: ulxly agglayer service integration * ulxly: change agglayer bridge service to be compatible with the future changes for globalIndex * WIP: ulxly avoid checks claim when using agglayer service; claim-proof broken * ulxly: generate global index while service doesnt return it * update test tracking * ulxly: update test report with complete native token bridging process * make lint fixes * ulxly: add new test evidence bridging native token from L1 to A, A to B, B to C, then from A, B and C to L1 * replace latest by fixed versions; fix gen-go-bindings * ulxly: add ger support for claims via legacy bridge service * make gen-doc * ulxly: add legacy bridge service test evidences * fix: issue where the to address in the claim comes from the private key rather than the deposit * ulxly: adds bridge ERC20 evidence for agglayer bridge service * ulxly: make legacy bridge service as default * fix plural * gen-doc * ulxly: rename agglayer to aggkit; remove commented code; * ulxly: review proof-ger flag description * fix get l1 info tree index retry logic * review str to stringer logs * adds big int from string checks * rename HashArrayToBytesArray to HashSliceToBytesArray; adds warning log when truncating * fix http body leak * check message before logging it * replace panic by return error * replace Msgf(%s,errMsg) by Msg(errMsg) * review str to stringer logs * improve proof error message * adds big int from string checks * simplify generateGlobalIndex * make gen-doc * fix linter issue --------- Co-authored-by: John Hilliard <[email protected]> Co-authored-by: Léo Vincent <[email protected]>
1 parent 976e556 commit fbd5fa1

22 files changed

+2987
-268
lines changed

cmd/fixnoncegap/fixnoncegap.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,18 @@ func fixNonceGap(cmd *cobra.Command, args []string) error {
185185
strings.Contains(err.Error(), "INTERNAL_ERROR: could not replace existing tx") {
186186
if replace {
187187
txTemplateCopy := *txTemplate
188-
oldGasPrice := txTemplate.GasPrice
188+
oldGasPrice := tx.GasPrice()
189189
// increase TX gas price by 10% and retry
190-
txTemplateCopy.GasPrice = new(big.Int).Mul(txTemplate.GasPrice, big.NewInt(11))
190+
txTemplateCopy.GasPrice = new(big.Int).Mul(oldGasPrice, big.NewInt(11))
191191
txTemplateCopy.GasPrice = new(big.Int).Div(txTemplateCopy.GasPrice, big.NewInt(10))
192+
// if gas price didn't increase, this means the value is really small and 10% was smaller than 1.
193+
// This can be the case for local networks running for a long time without transactions.
194+
// We just add 1 wei to force gasPrice to move up to allow tx replacement
195+
if txTemplateCopy.GasPrice.Cmp(oldGasPrice) == 0 {
196+
txTemplateCopy.GasPrice = new(big.Int).Add(txTemplateCopy.GasPrice, big.NewInt(1))
197+
}
192198
tx = types.NewTx(&txTemplateCopy)
193-
log.Info().Stringer("hash", signedTx.Hash()).Msgf("tx with nonce %d is underpriced, increasing fee by 10%%. From %d To %d", txTemplate.Nonce, oldGasPrice, txTemplateCopy.GasPrice)
199+
log.Info().Stringer("hash", signedTx.Hash()).Msgf("tx with nonce %d is underpriced, increasing fee. From %d To %d", txTemplate.Nonce, oldGasPrice, txTemplateCopy.GasPrice)
194200
time.Sleep(time.Second)
195201
continue
196202
} else {

cmd/loadtest/loadtest.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ func mainLoop(ctx context.Context, c *ethclient.Client, rpc *ethrpc.Client) erro
707707
if err != nil {
708708
return err
709709
}
710-
log.Debug().Str("ltAddr", ltAddr.String()).Msg("Obtained load test contract address")
710+
log.Debug().Stringer("ltAddr", ltAddr).Msg("Obtained load test contract address")
711711
}
712712

713713
var erc20Addr ethcommon.Address
@@ -717,7 +717,7 @@ func mainLoop(ctx context.Context, c *ethclient.Client, rpc *ethrpc.Client) erro
717717
if err != nil {
718718
return err
719719
}
720-
log.Debug().Str("erc20Addr", erc20Addr.String()).Msg("Obtained erc 20 contract address")
720+
log.Debug().Stringer("erc20Addr", erc20Addr).Msg("Obtained erc 20 contract address")
721721
}
722722

723723
var erc721Addr ethcommon.Address
@@ -727,7 +727,7 @@ func mainLoop(ctx context.Context, c *ethclient.Client, rpc *ethrpc.Client) erro
727727
if err != nil {
728728
return err
729729
}
730-
log.Debug().Str("erc721Addr", erc721Addr.String()).Msg("Obtained erc 721 contract address")
730+
log.Debug().Stringer("erc721Addr", erc721Addr).Msg("Obtained erc 721 contract address")
731731
}
732732

733733
var recallTransactions []rpctypes.PolyTransaction
@@ -1695,7 +1695,7 @@ func loadTestRPC(ctx context.Context, c *ethclient.Client, ia *IndexedActivity)
16951695

16961696
log.Trace().
16971697
Str("erc20str", erc20Str).
1698-
Str("erc20addr", erc20Addr.String()).
1698+
Stringer("erc20addr", erc20Addr).
16991699
Msg("Retrieve contract addresses")
17001700
cops := new(bind.CallOpts)
17011701
cops.Context = ctx
@@ -1723,7 +1723,7 @@ func loadTestRPC(ctx context.Context, c *ethclient.Client, ia *IndexedActivity)
17231723

17241724
log.Trace().
17251725
Str("erc721str", erc721Str).
1726-
Str("erc721addr", erc721Addr.String()).
1726+
Stringer("erc721addr", erc721Addr).
17271727
Msg("Retrieve contract addresses")
17281728
cops := new(bind.CallOpts)
17291729
cops.Context = ctx

cmd/publish/script/filegen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func main() {
7474
acc, err := newAccount()
7575
checkErr(err, "failed to create new account")
7676
log.Info().
77-
Str("address", acc.addr.String()).
77+
Stringer("address", acc.addr).
7878
Str("privateKey", acc.keyHex()).
7979
Msg("new account created")
8080

cmd/signer/signer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,7 @@ func (g *GCPKMS) Sign(ctx context.Context, tx *ethtypes.Transaction) error {
720720
log.Info().
721721
Str("recoveredPub", hex.EncodeToString(pubKey)).
722722
Str("gcpPub", hex.EncodeToString(gcpPubKey.PublicKey.Bytes)).
723-
Str("ethAddress", pubKeyAddr.String()).
723+
Stringer("ethAddress", pubKeyAddr).
724724
Msg("Recovered pub key")
725725

726726
signedTx, err := tx.WithSignature(signer, ethSig)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package aggkit
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
"strings"
7+
8+
"github.com/0xPolygon/polygon-cli/cmd/ulxly/bridge_service"
9+
"github.com/ethereum/go-ethereum/common"
10+
)
11+
12+
type getBridgesResponse struct {
13+
Bridges []bridgeResponse `json:"bridges"`
14+
Count int `json:"count"`
15+
}
16+
17+
type bridgeResponse struct {
18+
LeafType uint8 `json:"leaf_type"`
19+
20+
OrigNet uint32 `json:"origin_network"`
21+
OrigAddr string `json:"origin_address"`
22+
Amount string `json:"amount"`
23+
DestNet uint32 `json:"destination_network"`
24+
DestAddr string `json:"destination_address"`
25+
BlockNum uint64 `json:"block_num"`
26+
DepositCnt uint32 `json:"deposit_count"`
27+
TxHash string `json:"tx_hash"`
28+
Metadata string `json:"metadata"`
29+
30+
// Additional fields from the Aggkit API
31+
BridgeHash string `json:"bridge_hash"`
32+
BlockPos uint64 `json:"block_pos"`
33+
BlockTimestamp uint64 `json:"block_timestamp"`
34+
Calldata string `json:"calldata"`
35+
FromAddress string `json:"from_address"`
36+
}
37+
38+
func (r *bridgeResponse) ToDeposit(networkID uint32) (*bridge_service.Deposit, error) {
39+
d := &bridge_service.Deposit{}
40+
d.BlockNum = r.BlockNum
41+
42+
d.GlobalIndex = r.generateGlobalIndex(networkID)
43+
44+
d.Amount = new(big.Int)
45+
_, ok := d.Amount.SetString(r.Amount, 10)
46+
if !ok {
47+
return nil, fmt.Errorf("invalid amount: %s", r.Amount)
48+
}
49+
50+
d.TxHash = common.HexToHash(r.TxHash)
51+
52+
d.OrigAddr = common.HexToAddress(r.OrigAddr)
53+
d.DestAddr = common.HexToAddress(r.DestAddr)
54+
55+
d.Metadata = common.Hex2Bytes(strings.TrimPrefix(r.Metadata, "0x"))
56+
57+
d.LeafType = r.LeafType
58+
d.OrigNet = r.OrigNet
59+
d.DestNet = r.DestNet
60+
d.NetworkID = networkID
61+
d.DepositCnt = r.DepositCnt
62+
63+
return d, nil
64+
}
65+
66+
func (r *bridgeResponse) generateGlobalIndex(networkID uint32) *big.Int {
67+
result := new(big.Int)
68+
if networkID == 0 {
69+
// Mainnet: set bit 64 (add 2^64)
70+
result.SetBit(result, 64, 1)
71+
} else {
72+
// Non-mainnet: (networkID - 1) shifted left by 32 bits
73+
result.SetUint64(uint64(networkID-1) << 32)
74+
}
75+
// Add deposit count to the lower 32 bits
76+
result.Add(result, big.NewInt(int64(r.DepositCnt)))
77+
return result
78+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package aggkit
2+
3+
type errorResponse struct {
4+
Error string `json:"error"`
5+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package aggkit
2+
3+
import (
4+
"github.com/0xPolygon/polygon-cli/cmd/ulxly/bridge_service"
5+
"github.com/ethereum/go-ethereum/common"
6+
)
7+
8+
type getClaimProofResponse struct {
9+
L1InfoTreeLeafResponse l1InfoTreeLeafResponse `json:"l1_info_tree_leaf"`
10+
ProofLocalExitRoot []string `json:"proof_local_exit_root"`
11+
ProofRollupExitRoot []string `json:"proof_rollup_exit_root"`
12+
}
13+
14+
type l1InfoTreeLeafResponse struct {
15+
BlockNum uint64 `json:"block_num"`
16+
BlockPos uint64 `json:"block_pos"`
17+
GlobalExitRoot string `json:"global_exit_root"`
18+
Hash string `json:"hash"`
19+
L1InfoTreeIndex uint64 `json:"l1_info_tree_index"`
20+
MainnetExitRoot string `json:"mainnet_exit_root"`
21+
PreviousBlockHash string `json:"previous_block_hash"`
22+
RollupExitRoot string `json:"rollup_exit_root"`
23+
Timestamp uint64 `json:"timestamp"`
24+
}
25+
26+
func (r *getClaimProofResponse) ToProof() *bridge_service.Proof {
27+
p := &bridge_service.Proof{}
28+
29+
var merkleProof = make([]common.Hash, len(r.ProofLocalExitRoot))
30+
for i, p := range r.ProofLocalExitRoot {
31+
merkleProof[i] = common.HexToHash(p)
32+
}
33+
p.MerkleProof = merkleProof
34+
35+
var rollupMerkleProof = make([]common.Hash, len(r.ProofRollupExitRoot))
36+
for i, p := range r.ProofRollupExitRoot {
37+
rollupMerkleProof[i] = common.HexToHash(p)
38+
}
39+
p.RollupMerkleProof = rollupMerkleProof
40+
41+
if len(r.L1InfoTreeLeafResponse.MainnetExitRoot) > 0 {
42+
mainExitRoot := common.HexToHash(r.L1InfoTreeLeafResponse.MainnetExitRoot)
43+
p.MainExitRoot = &mainExitRoot
44+
}
45+
46+
if len(r.L1InfoTreeLeafResponse.RollupExitRoot) > 0 {
47+
rollupExitRoot := common.HexToHash(r.L1InfoTreeLeafResponse.RollupExitRoot)
48+
p.RollupExitRoot = &rollupExitRoot
49+
}
50+
51+
return p
52+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package aggkit
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
"strings"
8+
"time"
9+
10+
"github.com/0xPolygon/polygon-cli/cmd/ulxly/bridge_service"
11+
"github.com/0xPolygon/polygon-cli/cmd/ulxly/bridge_service/httpjson"
12+
"github.com/ethereum/go-ethereum/common"
13+
"github.com/rs/zerolog/log"
14+
)
15+
16+
const urlPath = "bridge/v1"
17+
18+
type BridgeService struct {
19+
bridge_service.BridgeServiceBase
20+
httpClient *http.Client
21+
}
22+
23+
// NewBridgeService creates an instance of the BridgeService.
24+
func NewBridgeService(url string, insecure bool) (*BridgeService, error) {
25+
return &BridgeService{
26+
BridgeServiceBase: bridge_service.NewBridgeServiceBase(url),
27+
httpClient: httpjson.NewHTTPClient(insecure),
28+
}, nil
29+
}
30+
31+
func (s *BridgeService) GetDeposit(depositNetwork, depositCount uint32) (*bridge_service.Deposit, error) {
32+
bridgeEndpoint := fmt.Sprintf("%s/%s/bridges?network_id=%d&deposit_count=%d", s.BridgeServiceBase.Url(), urlPath, depositNetwork, depositCount)
33+
bridgeResp, bridgeRespError, statusCode, err := httpjson.HTTPGetWithError[getBridgesResponse, errorResponse](s.httpClient, bridgeEndpoint)
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
if statusCode != http.StatusOK {
39+
errMsg := "unable to retrieve bridge deposit"
40+
log.Warn().Int("code", statusCode).Str("message", bridgeRespError.Error).Msg(errMsg)
41+
return nil, bridge_service.ErrNotFound
42+
}
43+
44+
if len(bridgeResp.Bridges) == 0 {
45+
return nil, bridge_service.ErrNotFound
46+
}
47+
48+
deposit, err := bridgeResp.Bridges[0].ToDeposit(depositNetwork)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
return deposit, nil
54+
}
55+
56+
func (s *BridgeService) GetDeposits(destinationAddress string, offset, limit int) ([]bridge_service.Deposit, int, error) {
57+
return nil, 0, fmt.Errorf("GetDeposits is not supported by aggkit bridge service yet")
58+
}
59+
60+
func (s *BridgeService) GetProof(depositNetwork, depositCount uint32, ger *common.Hash) (*bridge_service.Proof, error) {
61+
var l1InfoTreeIndex uint32
62+
63+
if ger != nil {
64+
return nil, errors.New("getting proof by ger is not supported yet by Aggkit bridge service")
65+
}
66+
67+
timeout := time.After(time.Minute)
68+
out:
69+
for {
70+
idx, err := s.getL1InfoTreeIndex(depositNetwork, depositCount)
71+
if err != nil && !errors.Is(err, bridge_service.ErrNotFound) {
72+
return nil, err
73+
} else if err == nil {
74+
l1InfoTreeIndex = *idx
75+
break out
76+
}
77+
select {
78+
case <-timeout:
79+
return nil, fmt.Errorf("timeout waiting for l1 info tree index")
80+
default:
81+
time.Sleep(time.Second)
82+
}
83+
}
84+
85+
endpoint := fmt.Sprintf("%s/%s/claim-proof?network_id=%d&leaf_index=%d&deposit_count=%d", s.BridgeServiceBase.Url(), urlPath, depositNetwork, l1InfoTreeIndex, depositCount)
86+
resp, respError, statusCode, err := httpjson.HTTPGetWithError[getClaimProofResponse, errorResponse](s.httpClient, endpoint)
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
if statusCode != http.StatusOK {
92+
if statusCode == http.StatusNotFound {
93+
return nil, bridge_service.ErrNotFound
94+
}
95+
errMsg := "unable to retrieve proof"
96+
log.Warn().Int("code", statusCode).Str("message", respError.Error).Msg(errMsg)
97+
return nil, fmt.Errorf("unable to get proof: %s", respError.Error)
98+
}
99+
100+
proof := resp.ToProof()
101+
return proof, nil
102+
}
103+
104+
func (s *BridgeService) getL1InfoTreeIndex(depositNetwork, depositCount uint32) (*uint32, error) {
105+
l1InfoTreeIndexEndpoint := fmt.Sprintf("%s/%s/l1-info-tree-index?network_id=%d&deposit_count=%d", s.BridgeServiceBase.Url(), urlPath, depositNetwork, depositCount)
106+
l1InfoTreeIndex, l1InfoTreeIndexRespError, statusCode, err := httpjson.HTTPGetWithError[uint32, errorResponse](s.httpClient, l1InfoTreeIndexEndpoint)
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
if statusCode != http.StatusOK {
112+
if statusCode == http.StatusNotFound {
113+
return nil, bridge_service.ErrNotFound
114+
}
115+
if statusCode == http.StatusInternalServerError {
116+
if strings.HasSuffix(l1InfoTreeIndexRespError.Error, "error: this bridge has not been included on the L1 Info Tree yet") ||
117+
strings.HasSuffix(l1InfoTreeIndexRespError.Error, "error: not found") {
118+
return nil, bridge_service.ErrNotFound
119+
}
120+
}
121+
errMsg := "unable to retrieve l1 info tree index"
122+
log.Warn().Int("code", statusCode).Str("message", l1InfoTreeIndexRespError.Error).Msg(errMsg)
123+
return nil, fmt.Errorf("%s: %s", errMsg, l1InfoTreeIndexRespError.Error)
124+
}
125+
126+
return &l1InfoTreeIndex, nil
127+
}

0 commit comments

Comments
 (0)