From 53e7e6c18ab1e24e2dd79b607e29d18ead2b59d2 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Wed, 20 May 2026 13:11:57 +0700 Subject: [PATCH 1/6] adjust logic to select top endpoint first if block is in range --- relayer/chains/evm/client.go | 53 ++++++++++++++++++++++--------- relayer/chains/flow/client.go | 54 ++++++++++++++++++++++---------- relayer/chains/icon/client.go | 42 ++++++++++++++++++------- relayer/chains/secret/client.go | 45 +++++++++++++++++++------- relayer/chains/soroban/client.go | 50 ++++++++++++++++++++--------- relayer/chains/xrpl/client.go | 42 ++++++++++++++++++------- 6 files changed, 207 insertions(+), 79 deletions(-) diff --git a/relayer/chains/evm/client.go b/relayer/chains/evm/client.go index 89e85fb7..61f8866c 100644 --- a/relayer/chains/evm/client.go +++ b/relayer/chains/evm/client.go @@ -51,10 +51,11 @@ type Client interface { // Client is the struct that handles interactions with the EVM chain. type client struct { - ChainName string - Endpoints []string - QueryTimeout time.Duration - ExecuteTimeout time.Duration + ChainName string + Endpoints []string + QueryTimeout time.Duration + ExecuteTimeout time.Duration + BlockConfirmation uint64 Log logger.Logger @@ -90,13 +91,14 @@ func dialEVMEndpoint(ctx context.Context, endpoint string) (*ethclient.Client, e // NewClient creates a new EVM client from config file and load keys. func NewClient(chainName string, cfg *EVMChainProviderConfig, log logger.Logger, alert alert.Alert) *client { return &client{ - ChainName: chainName, - Endpoints: cfg.Endpoints, - QueryTimeout: cfg.QueryTimeout, - ExecuteTimeout: cfg.ExecuteTimeout, - Log: log.With("chain_name", chainName), - alert: alert, - clients: NewEVMClients(), + ChainName: chainName, + Endpoints: cfg.Endpoints, + QueryTimeout: cfg.QueryTimeout, + ExecuteTimeout: cfg.ExecuteTimeout, + BlockConfirmation: cfg.BlockConfirmation, + Log: log.With("chain_name", chainName), + alert: alert, + clients: NewEVMClients(), } } @@ -504,7 +506,10 @@ func (c *client) BroadcastTx(ctx context.Context, tx *gethtypes.Transaction) (st return tx.Hash().Hex(), nil } -// getClientWithMaxHeight connects to the endpoint that has the highest block height. +// getClientWithMaxHeight selects an endpoint using first-come-first-serve within +// the confirmed block range. It collects block heights from all endpoints, then +// picks the first one (by arrival order) whose block height is within +// [maxHeight - BlockConfirmation, maxHeight]. func (c *client) getClientWithMaxHeight(ctx context.Context) (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.Endpoints)) @@ -554,16 +559,34 @@ func (c *client) getClientWithMaxHeight(ctx context.Context) (ClientConnectionRe }(endpoint) } - var result ClientConnectionResult + // Collect all results in arrival order and track the maximum block height. + results := make([]ClientConnectionResult, 0, len(c.Endpoints)) + var maxHeight uint64 for i := 0; i < len(c.Endpoints); i++ { r := <-ch if r.Client != nil { - if r.BlockHeight > result.BlockHeight || (r.Endpoint == c.clients.GetSelectedEndpoint() && r.BlockHeight == result.BlockHeight) { - result = r + results = append(results, r) + if r.BlockHeight > maxHeight { + maxHeight = r.BlockHeight } } } + // Determine the minimum acceptable block height based on BlockConfirmation. + var minHeight uint64 + if maxHeight >= c.BlockConfirmation { + minHeight = maxHeight - c.BlockConfirmation + } + + // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + var result ClientConnectionResult + for _, r := range results { + if r.BlockHeight >= minHeight { + result = r + break + } + } + if result.Client == nil { alert.HandleAlert( c.alert, diff --git a/relayer/chains/flow/client.go b/relayer/chains/flow/client.go index 0b809997..ab5eb6f5 100644 --- a/relayer/chains/flow/client.go +++ b/relayer/chains/flow/client.go @@ -47,10 +47,11 @@ var _ Client = (*client)(nil) // client is the concrete implementation that handles Flow HTTP interactions. type client struct { - ChainName string - Endpoints []string - QueryTimeout time.Duration - ExecuteTimeout time.Duration + ChainName string + Endpoints []string + QueryTimeout time.Duration + ExecuteTimeout time.Duration + BlockConfirmation uint64 Log logger.Logger alert alert.Alert @@ -61,13 +62,14 @@ type client struct { // NewClient creates a new Flow client from config. func NewClient(chainName string, cfg *FlowChainProviderConfig, log logger.Logger, a alert.Alert) Client { return &client{ - ChainName: chainName, - Endpoints: cfg.Endpoints, - QueryTimeout: cfg.QueryTimeout, - ExecuteTimeout: cfg.ExecuteTimeout, - Log: log.With("chain_name", chainName), - alert: a, - clients: NewFlowClients(), + ChainName: chainName, + Endpoints: cfg.Endpoints, + QueryTimeout: cfg.QueryTimeout, + ExecuteTimeout: cfg.ExecuteTimeout, + BlockConfirmation: 5, + Log: log.With("chain_name", chainName), + alert: a, + clients: NewFlowClients(), } } @@ -123,7 +125,10 @@ func (c *client) Connect(_ context.Context) error { return nil } -// getClientWithMaxHeight selects the endpoint with the highest sealed block height. +// getClientWithMaxHeight selects an endpoint using first-come-first-serve within +// the confirmed block range. It collects block heights from all endpoints, then +// picks the first one (by arrival order) whose block height is within +// [maxHeight - BlockConfirmation, maxHeight]. func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.Endpoints)) @@ -163,17 +168,34 @@ func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { }(endpoint) } - var result ClientConnectionResult + // Collect all results in arrival order and track the maximum block height. + results := make([]ClientConnectionResult, 0, len(c.Endpoints)) + var maxHeight uint64 for range c.Endpoints { r := <-ch if r.Client != nil { - if r.BlockHeight > result.BlockHeight || - (r.Endpoint == c.clients.GetSelectedEndpoint() && r.BlockHeight == result.BlockHeight) { - result = r + results = append(results, r) + if r.BlockHeight > maxHeight { + maxHeight = r.BlockHeight } } } + // Determine the minimum acceptable block height based on BlockConfirmation. + var minHeight uint64 + if maxHeight >= c.BlockConfirmation { + minHeight = maxHeight - c.BlockConfirmation + } + + // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + var result ClientConnectionResult + for _, r := range results { + if r.BlockHeight >= minHeight { + result = r + break + } + } + if result.Client == nil { alert.HandleAlert( c.alert, diff --git a/relayer/chains/icon/client.go b/relayer/chains/icon/client.go index 1d4fa221..f8cdbeb1 100644 --- a/relayer/chains/icon/client.go +++ b/relayer/chains/icon/client.go @@ -38,8 +38,9 @@ type Client interface { // Client is the struct that handles interactions with the Icon chain. type client struct { - ChainName string - Endpoints []string + ChainName string + Endpoints []string + BlockConfirmation uint64 Log logger.Logger @@ -50,11 +51,12 @@ type client struct { // NewClient creates a new Icon client from config file and load keys. func NewClient(chainName string, cfg *IconChainProviderConfig, log logger.Logger, alert alert.Alert) *client { return &client{ - ChainName: chainName, - Endpoints: cfg.Endpoints, - Log: log.With("chain_name", chainName), - alert: alert, - clients: NewIconClients(), + ChainName: chainName, + Endpoints: cfg.Endpoints, + BlockConfirmation: 5, + Log: log.With("chain_name", chainName), + alert: alert, + clients: NewIconClients(), } } @@ -112,6 +114,7 @@ func (c *client) StartLivelinessCheck(ctx context.Context, interval time.Duratio } // getClientWithMaxHeight connects to the endpoint that has the highest block height. +// It uses first-come-first-serve selection within [maxHeight - BlockConfirmation, maxHeight]. func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.Endpoints)) @@ -158,17 +161,34 @@ func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { }(endpoint) } - var result ClientConnectionResult + // Collect all results in arrival order and track the maximum block height. + results := make([]ClientConnectionResult, 0, len(c.Endpoints)) + var maxHeight uint64 for i := 0; i < len(c.Endpoints); i++ { r := <-ch if r.Client != nil { - if r.BlockHeight > result.BlockHeight || - (r.Endpoint == c.clients.GetSelectedEndpoint() && r.BlockHeight == result.BlockHeight) { - result = r + results = append(results, r) + if r.BlockHeight > maxHeight { + maxHeight = r.BlockHeight } } } + // Determine the minimum acceptable block height based on BlockConfirmation. + var minHeight uint64 + if maxHeight >= c.BlockConfirmation { + minHeight = maxHeight - c.BlockConfirmation + } + + // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + var result ClientConnectionResult + for _, r := range results { + if r.BlockHeight >= minHeight { + result = r + break + } + } + if result.Client == nil { alert.HandleAlert( c.alert, diff --git a/relayer/chains/secret/client.go b/relayer/chains/secret/client.go index 5ca4fc2a..6d5b8ea0 100644 --- a/relayer/chains/secret/client.go +++ b/relayer/chains/secret/client.go @@ -58,9 +58,10 @@ type typesBlockResult struct { var _ Client = (*client)(nil) type client struct { - chainName string - endpoints []string - denom string + chainName string + endpoints []string + denom string + blockConfirmation int64 log logger.Logger alert alert.Alert @@ -69,12 +70,13 @@ type client struct { func NewClient(chainName string, cpc *SecretChainProviderConfig, log logger.Logger, alert alert.Alert) *client { return &client{ - chainName: chainName, - endpoints: cpc.Endpoints, - denom: cpc.Denom, - log: log.With("chain_name", chainName), - alert: alert, - clients: chains.NewClientPool[sdkclient.Context](), + chainName: chainName, + endpoints: cpc.Endpoints, + denom: cpc.Denom, + blockConfirmation: 5, + log: log.With("chain_name", chainName), + alert: alert, + clients: chains.NewClientPool[sdkclient.Context](), } } @@ -338,6 +340,7 @@ func (c *client) GetBlockByHeight(ctx context.Context, height *big.Int) (*typesB } // getClientWithMaxHeight connects to the endpoint that has the highest block height. +// It uses first-come-first-serve selection within [maxHeight - blockConfirmation, maxHeight]. func (c *client) getClientWithMaxHeight(ctx context.Context) (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.endpoints)) @@ -402,16 +405,34 @@ func (c *client) getClientWithMaxHeight(ctx context.Context) (ClientConnectionRe }(endpoint) } - var result ClientConnectionResult + // Collect all results in arrival order and track the maximum block height. + results := make([]ClientConnectionResult, 0, len(c.endpoints)) + var maxHeight int64 for i := 0; i < len(c.endpoints); i++ { r := <-ch if r.Client != nil { - if r.BlockHeight > result.BlockHeight || (r.Endpoint == c.clients.GetSelectedEndpoint() && r.BlockHeight == result.BlockHeight) { - result = r + results = append(results, r) + if r.BlockHeight > maxHeight { + maxHeight = r.BlockHeight } } } + // Determine the minimum acceptable block height based on blockConfirmation. + var minHeight int64 + if maxHeight >= c.blockConfirmation { + minHeight = maxHeight - c.blockConfirmation + } + + // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + var result ClientConnectionResult + for _, r := range results { + if r.BlockHeight >= minHeight { + result = r + break + } + } + if result.Client == nil { alert.HandleAlert( c.alert, diff --git a/relayer/chains/soroban/client.go b/relayer/chains/soroban/client.go index 4087d4e6..e17345f8 100644 --- a/relayer/chains/soroban/client.go +++ b/relayer/chains/soroban/client.go @@ -46,9 +46,10 @@ type Client interface { } type client struct { - ChainName string - HorizonEndpoints []string - QueryTimeout time.Duration + ChainName string + HorizonEndpoints []string + QueryTimeout time.Duration + BlockConfirmation uint64 Log logger.Logger alert alert.Alert @@ -58,12 +59,13 @@ type client struct { func NewClient(chainName string, cfg *SorobanChainProviderConfig, log logger.Logger, alert alert.Alert) Client { return &client{ - ChainName: chainName, - HorizonEndpoints: cfg.HorizonEndpoints, - QueryTimeout: cfg.QueryTimeout, - Log: log.With("chain_name", chainName), - alert: alert, - clients: NewHorizonClients(), + ChainName: chainName, + HorizonEndpoints: cfg.HorizonEndpoints, + QueryTimeout: cfg.QueryTimeout, + BlockConfirmation: 5, + Log: log.With("chain_name", chainName), + alert: alert, + clients: NewHorizonClients(), } } @@ -151,7 +153,10 @@ func (c *client) StartLivelinessCheck(ctx context.Context, interval time.Duratio } } -// getClientWithMaxLedger returns the connected client with the highest ingested ledger sequence. +// getClientWithMaxLedger selects an endpoint using first-come-first-serve within +// the confirmed ledger range. It collects ledger sequences from all endpoints, then +// picks the first one (by arrival order) whose sequence is within +// [maxLedger - BlockConfirmation, maxLedger]. func (c *client) getClientWithMaxLedger(ctx context.Context) (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.HorizonEndpoints)) @@ -198,17 +203,34 @@ func (c *client) getClientWithMaxLedger(ctx context.Context) (ClientConnectionRe }(endpoint) } - var result ClientConnectionResult + // Collect all results in arrival order and track the maximum ledger sequence. + results := make([]ClientConnectionResult, 0, len(c.HorizonEndpoints)) + var maxLedger uint64 for i := 0; i < len(c.HorizonEndpoints); i++ { r := <-ch if r.Client != nil { - if r.LedgerSequence > result.LedgerSequence || - (r.Endpoint == c.clients.GetSelectedEndpoint() && r.LedgerSequence == result.LedgerSequence) { - result = r + results = append(results, r) + if r.LedgerSequence > maxLedger { + maxLedger = r.LedgerSequence } } } + // Determine the minimum acceptable ledger sequence based on BlockConfirmation. + var minLedger uint64 + if maxLedger >= c.BlockConfirmation { + minLedger = maxLedger - c.BlockConfirmation + } + + // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + var result ClientConnectionResult + for _, r := range results { + if r.LedgerSequence >= minLedger { + result = r + break + } + } + if result.Client == nil { alert.HandleAlert( c.alert, diff --git a/relayer/chains/xrpl/client.go b/relayer/chains/xrpl/client.go index 81219922..9ff560ce 100644 --- a/relayer/chains/xrpl/client.go +++ b/relayer/chains/xrpl/client.go @@ -46,8 +46,9 @@ var _ Client = (*client)(nil) // client is the concrete implementation that handles XRPL JSON-RPC interactions. type client struct { - ChainName string - Endpoints []string + ChainName string + Endpoints []string + BlockConfirmation uint32 Log logger.Logger alert alert.Alert @@ -64,11 +65,12 @@ type TxResult struct { // NewClient creates a new XRPL client from config. func NewClient(chainName string, cfg *XRPLChainProviderConfig, log logger.Logger, alert alert.Alert) Client { return &client{ - ChainName: chainName, - Endpoints: cfg.Endpoints, - Log: log.With("chain_name", chainName), - alert: alert, - clients: NewXRPLClients(), + ChainName: chainName, + Endpoints: cfg.Endpoints, + BlockConfirmation: 5, + Log: log.With("chain_name", chainName), + alert: alert, + clients: NewXRPLClients(), } } @@ -134,6 +136,7 @@ func (c *client) Connect(_ context.Context) error { } // getClientWithMaxHeight connects to the endpoint that has the highest ledger index. +// It uses first-come-first-serve selection within [maxLedger - BlockConfirmation, maxLedger]. func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.Endpoints)) @@ -170,17 +173,34 @@ func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { }(endpoint) } - var result ClientConnectionResult + // Collect all results in arrival order and track the maximum ledger index. + results := make([]ClientConnectionResult, 0, len(c.Endpoints)) + var maxLedger uint32 for range c.Endpoints { r := <-ch if r.Client != nil { - if r.LedgerIndex > result.LedgerIndex || - (r.Endpoint == c.clients.GetSelectedEndpoint() && r.LedgerIndex == result.LedgerIndex) { - result = r + results = append(results, r) + if r.LedgerIndex > maxLedger { + maxLedger = r.LedgerIndex } } } + // Determine the minimum acceptable ledger index based on BlockConfirmation. + var minLedger uint32 + if maxLedger >= c.BlockConfirmation { + minLedger = maxLedger - c.BlockConfirmation + } + + // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + var result ClientConnectionResult + for _, r := range results { + if r.LedgerIndex >= minLedger { + result = r + break + } + } + if result.Client == nil { alert.HandleAlert( c.alert, From 8367949d143f3f1798eb61877a5d8b83ad8b664f Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Wed, 20 May 2026 13:31:45 +0700 Subject: [PATCH 2/6] fix timeout on xrpl --- relayer/chains/xrpl/client.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/relayer/chains/xrpl/client.go b/relayer/chains/xrpl/client.go index 9ff560ce..d992b586 100644 --- a/relayer/chains/xrpl/client.go +++ b/relayer/chains/xrpl/client.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "net/http" "sync" "time" @@ -22,6 +23,11 @@ import ( const RippleEpochOffset = 946684800 +// idleConnTimeout is shorter than the typical load-balancer idle timeout +// (e.g. AWS ALB default 60s) to prevent "connection reset by peer" errors +// from stale keep-alive connections being reused after the LB closes them. +const idleConnTimeout = 30 * time.Second + // XRPLClients holds XRPL RPC clients and the selected endpoint. type XRPLClients = chains.ClientPool[rpc.Client] @@ -93,7 +99,11 @@ func (c *client) Connect(_ context.Context) error { wg.Add(1) go func(endpoint string) { defer wg.Done() - opts := []rpc.ConfigOpt{} + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.IdleConnTimeout = idleConnTimeout + opts := []rpc.ConfigOpt{ + rpc.WithHTTPClient(&http.Client{Transport: transport}), + } cfg, err := rpc.NewClientConfig(endpoint, opts...) if err != nil { c.Log.Warn("XRPL endpoint config error", "endpoint", endpoint, err) From 44b595fde0dac422649af765613c5c8138276aad Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Wed, 20 May 2026 13:44:01 +0700 Subject: [PATCH 3/6] fix use order first --- relayer/chains/evm/client.go | 12 ++++++------ relayer/chains/flow/client.go | 12 ++++++------ relayer/chains/icon/client.go | 12 ++++++------ relayer/chains/secret/client.go | 12 ++++++------ relayer/chains/soroban/client.go | 12 ++++++------ relayer/chains/xrpl/client.go | 12 ++++++------ 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/relayer/chains/evm/client.go b/relayer/chains/evm/client.go index 61f8866c..61a6c453 100644 --- a/relayer/chains/evm/client.go +++ b/relayer/chains/evm/client.go @@ -559,13 +559,13 @@ func (c *client) getClientWithMaxHeight(ctx context.Context) (ClientConnectionRe }(endpoint) } - // Collect all results in arrival order and track the maximum block height. - results := make([]ClientConnectionResult, 0, len(c.Endpoints)) + // Collect all results into a map and track the maximum block height. + resultMap := make(map[string]ClientConnectionResult, len(c.Endpoints)) var maxHeight uint64 for i := 0; i < len(c.Endpoints); i++ { r := <-ch if r.Client != nil { - results = append(results, r) + resultMap[r.Endpoint] = r if r.BlockHeight > maxHeight { maxHeight = r.BlockHeight } @@ -578,10 +578,10 @@ func (c *client) getClientWithMaxHeight(ctx context.Context) (ClientConnectionRe minHeight = maxHeight - c.BlockConfirmation } - // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + // Pick the first endpoint in config order that is within the confirmed range. var result ClientConnectionResult - for _, r := range results { - if r.BlockHeight >= minHeight { + for _, endpoint := range c.Endpoints { + if r, ok := resultMap[endpoint]; ok && r.BlockHeight >= minHeight { result = r break } diff --git a/relayer/chains/flow/client.go b/relayer/chains/flow/client.go index ab5eb6f5..5e41b893 100644 --- a/relayer/chains/flow/client.go +++ b/relayer/chains/flow/client.go @@ -168,13 +168,13 @@ func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { }(endpoint) } - // Collect all results in arrival order and track the maximum block height. - results := make([]ClientConnectionResult, 0, len(c.Endpoints)) + // Collect all results into a map and track the maximum block height. + resultMap := make(map[string]ClientConnectionResult, len(c.Endpoints)) var maxHeight uint64 for range c.Endpoints { r := <-ch if r.Client != nil { - results = append(results, r) + resultMap[r.Endpoint] = r if r.BlockHeight > maxHeight { maxHeight = r.BlockHeight } @@ -187,10 +187,10 @@ func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { minHeight = maxHeight - c.BlockConfirmation } - // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + // Pick the first endpoint in config order that is within the confirmed range. var result ClientConnectionResult - for _, r := range results { - if r.BlockHeight >= minHeight { + for _, endpoint := range c.Endpoints { + if r, ok := resultMap[endpoint]; ok && r.BlockHeight >= minHeight { result = r break } diff --git a/relayer/chains/icon/client.go b/relayer/chains/icon/client.go index f8cdbeb1..ee6279c8 100644 --- a/relayer/chains/icon/client.go +++ b/relayer/chains/icon/client.go @@ -161,13 +161,13 @@ func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { }(endpoint) } - // Collect all results in arrival order and track the maximum block height. - results := make([]ClientConnectionResult, 0, len(c.Endpoints)) + // Collect all results into a map and track the maximum block height. + resultMap := make(map[string]ClientConnectionResult, len(c.Endpoints)) var maxHeight uint64 for i := 0; i < len(c.Endpoints); i++ { r := <-ch if r.Client != nil { - results = append(results, r) + resultMap[r.Endpoint] = r if r.BlockHeight > maxHeight { maxHeight = r.BlockHeight } @@ -180,10 +180,10 @@ func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { minHeight = maxHeight - c.BlockConfirmation } - // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + // Pick the first endpoint in config order that is within the confirmed range. var result ClientConnectionResult - for _, r := range results { - if r.BlockHeight >= minHeight { + for _, endpoint := range c.Endpoints { + if r, ok := resultMap[endpoint]; ok && r.BlockHeight >= minHeight { result = r break } diff --git a/relayer/chains/secret/client.go b/relayer/chains/secret/client.go index 6d5b8ea0..8a31c95f 100644 --- a/relayer/chains/secret/client.go +++ b/relayer/chains/secret/client.go @@ -405,13 +405,13 @@ func (c *client) getClientWithMaxHeight(ctx context.Context) (ClientConnectionRe }(endpoint) } - // Collect all results in arrival order and track the maximum block height. - results := make([]ClientConnectionResult, 0, len(c.endpoints)) + // Collect all results into a map and track the maximum block height. + resultMap := make(map[string]ClientConnectionResult, len(c.endpoints)) var maxHeight int64 for i := 0; i < len(c.endpoints); i++ { r := <-ch if r.Client != nil { - results = append(results, r) + resultMap[r.Endpoint] = r if r.BlockHeight > maxHeight { maxHeight = r.BlockHeight } @@ -424,10 +424,10 @@ func (c *client) getClientWithMaxHeight(ctx context.Context) (ClientConnectionRe minHeight = maxHeight - c.blockConfirmation } - // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + // Pick the first endpoint in config order that is within the confirmed range. var result ClientConnectionResult - for _, r := range results { - if r.BlockHeight >= minHeight { + for _, endpoint := range c.endpoints { + if r, ok := resultMap[endpoint]; ok && r.BlockHeight >= minHeight { result = r break } diff --git a/relayer/chains/soroban/client.go b/relayer/chains/soroban/client.go index e17345f8..f20c8423 100644 --- a/relayer/chains/soroban/client.go +++ b/relayer/chains/soroban/client.go @@ -203,13 +203,13 @@ func (c *client) getClientWithMaxLedger(ctx context.Context) (ClientConnectionRe }(endpoint) } - // Collect all results in arrival order and track the maximum ledger sequence. - results := make([]ClientConnectionResult, 0, len(c.HorizonEndpoints)) + // Collect all results into a map and track the maximum ledger sequence. + resultMap := make(map[string]ClientConnectionResult, len(c.HorizonEndpoints)) var maxLedger uint64 for i := 0; i < len(c.HorizonEndpoints); i++ { r := <-ch if r.Client != nil { - results = append(results, r) + resultMap[r.Endpoint] = r if r.LedgerSequence > maxLedger { maxLedger = r.LedgerSequence } @@ -222,10 +222,10 @@ func (c *client) getClientWithMaxLedger(ctx context.Context) (ClientConnectionRe minLedger = maxLedger - c.BlockConfirmation } - // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + // Pick the first endpoint in config order that is within the confirmed range. var result ClientConnectionResult - for _, r := range results { - if r.LedgerSequence >= minLedger { + for _, endpoint := range c.HorizonEndpoints { + if r, ok := resultMap[endpoint]; ok && r.LedgerSequence >= minLedger { result = r break } diff --git a/relayer/chains/xrpl/client.go b/relayer/chains/xrpl/client.go index d992b586..5ee86c5a 100644 --- a/relayer/chains/xrpl/client.go +++ b/relayer/chains/xrpl/client.go @@ -183,13 +183,13 @@ func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { }(endpoint) } - // Collect all results in arrival order and track the maximum ledger index. - results := make([]ClientConnectionResult, 0, len(c.Endpoints)) + // Collect all results into a map and track the maximum ledger index. + resultMap := make(map[string]ClientConnectionResult, len(c.Endpoints)) var maxLedger uint32 for range c.Endpoints { r := <-ch if r.Client != nil { - results = append(results, r) + resultMap[r.Endpoint] = r if r.LedgerIndex > maxLedger { maxLedger = r.LedgerIndex } @@ -202,10 +202,10 @@ func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { minLedger = maxLedger - c.BlockConfirmation } - // First-come-first-serve: pick the first arrived endpoint within the confirmed range. + // Pick the first endpoint in config order that is within the confirmed range. var result ClientConnectionResult - for _, r := range results { - if r.LedgerIndex >= minLedger { + for _, endpoint := range c.Endpoints { + if r, ok := resultMap[endpoint]; ok && r.LedgerIndex >= minLedger { result = r break } From 9960c70981d3d722640bcd6c66240a4c99d90ce2 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Wed, 20 May 2026 13:49:20 +0700 Subject: [PATCH 4/6] patch version --- Dockerfile | 2 +- README.md | 34 +++++++++++++++++----------------- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Dockerfile b/Dockerfile index d6252374..5af3424c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # ============================ Build Stage ============================ -FROM --platform=$BUILDPLATFORM golang:1.24.2-alpine3.20 AS build +FROM --platform=$BUILDPLATFORM golang:1.25.3-alpine3.21 AS build LABEL org.opencontainers.image.source="https://github.com/bandprotocol/falcon" diff --git a/README.md b/README.md index 38cbe25e..a07321a8 100644 --- a/README.md +++ b/README.md @@ -111,11 +111,11 @@ sudo apt-get update && \ sudo apt-get upgrade -y && \ sudo apt-get install -y build-essential curl wget jq ``` -- Install Go 1.24.2 +- Install Go 1.25.3 ```shell -# Install Go 1.24.2 -wget https://go.dev/dl/go1.24.2.linux-amd64.tar.gz -tar xf go1.24.2.linux-amd64.tar.gz +# Install Go 1.25.3 +wget https://go.dev/dl/go1.25.3.linux-amd64.tar.gz +tar xf go1.25.3.linux-amd64.tar.gz sudo mv go /usr/local/go # Set Go path to $PATH variable @@ -247,48 +247,48 @@ Enter your private key If you already have a mnemonic and want to retrieve key from it, you can choose `Mnemonic` option. ``` Enter your mnemonic -> +> Enter a coin type Coin type number for HD derivation (default: 60; leave empty to use default) -> Enter an account -Account number in the HD derivation path (default: 0; leave empty to use default) > +Account number in the HD derivation path (default: 0; leave empty to use default) Enter an index Index number for the specific address within an account in the HD derivation path (default: 0; leave empty to use default) -> +> ``` If you want to generate a new address, choose the `Generate new address` option. ``` +> Enter a coin type Coin type number for HD derivation (default: 60; leave empty to use default) -> Enter an account Account number in the HD derivation path (default: 0; leave empty to use default) -> Enter an index +> Index number for the specific address within an account in the HD derivation path (default: 0; leave empty to use default) -> ``` ### 7. Check that the keys for the configured chains are funded +> You can query the balance of each configured key by running: ``` shell falcon q balance testkey +> ``` ### 8. Start to relay packet Starts all tunnels that `falcon query tunnels` can query ``` shell falcon start ``` -> NOTE: You can choose which tunnels do you want to relay. +NOTE: You can choose which tunnels do you want to relay. ``` shell falcon start 1 2 3 ``` @@ -296,7 +296,7 @@ falcon start 1 2 3 ## Generate Go Protobuf Code Falcon uses gRPC and Protocol Buffers for its internal APIs. If you modify any `.proto` files under the `proto/` directory, regenerate the Go code by running: - +> ```sh make proto ``` @@ -320,16 +320,16 @@ go install github.com/pressly/goose/v3/cmd/goose@latest ``` #### Run -> Use `-dir` before the driver/DSN. -> Supported drivers: `postgres`, `sqlite`. +Use `-dir` before the driver/DSN. +Supported drivers: `postgres`, `sqlite`. ##### Up (apply all pending) ```sh # Postgres goose -dir relayer/db/migrations/postgres postgres "postgres://user:password@localhost:5432/falcon?sslmode=disable" up -# SQLite -goose -dir relayer/db/migrations/sqlite sqlite "gorm.db" up +> # SQLite +> goose -dir relayer/db/migrations/sqlite sqlite "gorm.db" up ``` ##### Down (revert one step) diff --git a/go.mod b/go.mod index 3e1fc6ca..4c39d95d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/bandprotocol/falcon -go 1.24.3 +go 1.25.3 require ( cosmossdk.io/math v1.4.0 @@ -30,7 +30,7 @@ require ( go.uber.org/mock v0.6.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.46.0 - google.golang.org/grpc v1.77.0 + google.golang.org/grpc v1.79.3 google.golang.org/protobuf v1.36.11 gorm.io/driver/postgres v1.6.0 gorm.io/driver/sqlite v1.6.0 @@ -156,7 +156,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/pgx/v5 v5.9.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -241,9 +241,9 @@ require ( go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect + go.opentelemetry.io/otel/trace v1.41.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.48.0 // indirect diff --git a/go.sum b/go.sum index 99b004dc..d0f4acfd 100644 --- a/go.sum +++ b/go.sum @@ -621,8 +621,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/pgx/v5 v5.9.0 h1:T/dI+2TvmI2H8s/KH1/lXIbz1CUFk3gn5oTjr0/mBsE= +github.com/jackc/pgx/v5 v5.9.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -1094,16 +1094,16 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1488,8 +1488,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From dd3d5c83e0c442902b12ca395f42efaf48f3e050 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Wed, 20 May 2026 13:51:10 +0700 Subject: [PATCH 5/6] update config --- relayer/chains/evm/client.go | 6 +++--- relayer/chains/flow/client.go | 9 +++++---- relayer/chains/icon/client.go | 6 ++++-- relayer/chains/secret/client.go | 6 ++++-- relayer/chains/soroban/client.go | 9 +++++---- relayer/chains/xrpl/client.go | 9 ++++++--- 6 files changed, 27 insertions(+), 18 deletions(-) diff --git a/relayer/chains/evm/client.go b/relayer/chains/evm/client.go index 61a6c453..ba1b6326 100644 --- a/relayer/chains/evm/client.go +++ b/relayer/chains/evm/client.go @@ -506,9 +506,9 @@ func (c *client) BroadcastTx(ctx context.Context, tx *gethtypes.Transaction) (st return tx.Hash().Hex(), nil } -// getClientWithMaxHeight selects an endpoint using first-come-first-serve within -// the confirmed block range. It collects block heights from all endpoints, then -// picks the first one (by arrival order) whose block height is within +// getClientWithMaxHeight selects an endpoint by config order within the confirmed +// block range. It collects block heights from all endpoints in parallel, then +// picks the first endpoint (in config order) whose block height is within // [maxHeight - BlockConfirmation, maxHeight]. func (c *client) getClientWithMaxHeight(ctx context.Context) (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.Endpoints)) diff --git a/relayer/chains/flow/client.go b/relayer/chains/flow/client.go index 5e41b893..693873ea 100644 --- a/relayer/chains/flow/client.go +++ b/relayer/chains/flow/client.go @@ -73,7 +73,8 @@ func NewClient(chainName string, cfg *FlowChainProviderConfig, log logger.Logger } } -// Connect connects to all endpoints and selects the one with the highest block height. +// Connect connects to all endpoints and selects the first eligible one by config order +// within the confirmed block range. func (c *client) Connect(_ context.Context) error { var wg sync.WaitGroup for _, endpoint := range c.Endpoints { @@ -125,9 +126,9 @@ func (c *client) Connect(_ context.Context) error { return nil } -// getClientWithMaxHeight selects an endpoint using first-come-first-serve within -// the confirmed block range. It collects block heights from all endpoints, then -// picks the first one (by arrival order) whose block height is within +// getClientWithMaxHeight selects an endpoint by config order within the confirmed +// block range. It collects block heights from all endpoints in parallel, then +// picks the first endpoint (in config order) whose block height is within // [maxHeight - BlockConfirmation, maxHeight]. func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.Endpoints)) diff --git a/relayer/chains/icon/client.go b/relayer/chains/icon/client.go index ee6279c8..899543bb 100644 --- a/relayer/chains/icon/client.go +++ b/relayer/chains/icon/client.go @@ -113,8 +113,10 @@ func (c *client) StartLivelinessCheck(ctx context.Context, interval time.Duratio } } -// getClientWithMaxHeight connects to the endpoint that has the highest block height. -// It uses first-come-first-serve selection within [maxHeight - BlockConfirmation, maxHeight]. +// getClientWithMaxHeight selects an endpoint by config order within the confirmed +// block range. It collects block heights from all endpoints in parallel, then +// picks the first endpoint (in config order) whose block height is within +// [maxHeight - BlockConfirmation, maxHeight]. func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.Endpoints)) diff --git a/relayer/chains/secret/client.go b/relayer/chains/secret/client.go index 8a31c95f..cabdd1ae 100644 --- a/relayer/chains/secret/client.go +++ b/relayer/chains/secret/client.go @@ -339,8 +339,10 @@ func (c *client) GetBlockByHeight(ctx context.Context, height *big.Int) (*typesB return &typesBlockResult{Time: resBlock.Block.Time.UTC()}, nil } -// getClientWithMaxHeight connects to the endpoint that has the highest block height. -// It uses first-come-first-serve selection within [maxHeight - blockConfirmation, maxHeight]. +// getClientWithMaxHeight selects an endpoint by config order within the confirmed +// block range. It collects block heights from all endpoints in parallel, then +// picks the first endpoint (in config order) whose block height is within +// [maxHeight - blockConfirmation, maxHeight]. func (c *client) getClientWithMaxHeight(ctx context.Context) (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.endpoints)) diff --git a/relayer/chains/soroban/client.go b/relayer/chains/soroban/client.go index f20c8423..1db81a43 100644 --- a/relayer/chains/soroban/client.go +++ b/relayer/chains/soroban/client.go @@ -69,7 +69,8 @@ func NewClient(chainName string, cfg *SorobanChainProviderConfig, log logger.Log } } -// Connect connects to all Horizon endpoints in parallel and selects the one with the highest ledger. +// Connect connects to all Horizon endpoints in parallel and selects the first eligible +// one by config order within the confirmed ledger range. func (c *client) Connect(ctx context.Context) error { var wg sync.WaitGroup for _, endpoint := range c.HorizonEndpoints { @@ -153,9 +154,9 @@ func (c *client) StartLivelinessCheck(ctx context.Context, interval time.Duratio } } -// getClientWithMaxLedger selects an endpoint using first-come-first-serve within -// the confirmed ledger range. It collects ledger sequences from all endpoints, then -// picks the first one (by arrival order) whose sequence is within +// getClientWithMaxLedger selects an endpoint by config order within the confirmed +// ledger range. It collects ledger sequences from all endpoints in parallel, then +// picks the first endpoint (in config order) whose sequence is within // [maxLedger - BlockConfirmation, maxLedger]. func (c *client) getClientWithMaxLedger(ctx context.Context) (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.HorizonEndpoints)) diff --git a/relayer/chains/xrpl/client.go b/relayer/chains/xrpl/client.go index 5ee86c5a..fbc1b61b 100644 --- a/relayer/chains/xrpl/client.go +++ b/relayer/chains/xrpl/client.go @@ -87,7 +87,8 @@ type ClientConnectionResult struct { LedgerIndex uint32 } -// Connect selects a responsive endpoint with the highest ledger index. +// Connect connects to all endpoints and selects the first eligible one by config order +// within the confirmed ledger range. func (c *client) Connect(_ context.Context) error { var wg sync.WaitGroup for _, endpoint := range c.Endpoints { @@ -145,8 +146,10 @@ func (c *client) Connect(_ context.Context) error { return nil } -// getClientWithMaxHeight connects to the endpoint that has the highest ledger index. -// It uses first-come-first-serve selection within [maxLedger - BlockConfirmation, maxLedger]. +// getClientWithMaxHeight selects an endpoint by config order within the confirmed +// ledger range. It collects ledger indices from all endpoints in parallel, then +// picks the first endpoint (in config order) whose ledger index is within +// [maxLedger - BlockConfirmation, maxLedger]. func (c *client) getClientWithMaxHeight() (ClientConnectionResult, error) { ch := make(chan ClientConnectionResult, len(c.Endpoints)) From 6adaa93b5dd7cc1eef28f9a4fe8ff8c6dc84cf61 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Wed, 20 May 2026 13:58:03 +0700 Subject: [PATCH 6/6] patch --- .github/workflows/golangci-lint.yml | 4 ++-- README.md | 13 ++----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index d090ccb5..c0796f28 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -7,12 +7,12 @@ jobs: steps: - uses: actions/setup-go@v5 with: - go-version: 1.24.2 + go-version: 1.25.3 - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v7 with: - version: v2.1.6 + version: v2.12.2 args: --timeout=5m0s diff --git a/README.md b/README.md index a07321a8..0aefb8a8 100644 --- a/README.md +++ b/README.md @@ -248,22 +248,18 @@ If you already have a mnemonic and want to retrieve key from it, you can choose ``` Enter your mnemonic -> Enter a coin type Coin type number for HD derivation (default: 60; leave empty to use default) Enter an account -> Account number in the HD derivation path (default: 0; leave empty to use default) Enter an index Index number for the specific address within an account in the HD derivation path (default: 0; leave empty to use default) -> ``` If you want to generate a new address, choose the `Generate new address` option. ``` -> Enter a coin type Coin type number for HD derivation (default: 60; leave empty to use default) @@ -271,17 +267,13 @@ Enter an account Account number in the HD derivation path (default: 0; leave empty to use default) Enter an index -> Index number for the specific address within an account in the HD derivation path (default: 0; leave empty to use default) ``` ### 7. Check that the keys for the configured chains are funded -> - You can query the balance of each configured key by running: ``` shell falcon q balance testkey -> ``` ### 8. Start to relay packet Starts all tunnels that `falcon query tunnels` can query @@ -296,7 +288,6 @@ falcon start 1 2 3 ## Generate Go Protobuf Code Falcon uses gRPC and Protocol Buffers for its internal APIs. If you modify any `.proto` files under the `proto/` directory, regenerate the Go code by running: -> ```sh make proto ``` @@ -328,8 +319,8 @@ Supported drivers: `postgres`, `sqlite`. # Postgres goose -dir relayer/db/migrations/postgres postgres "postgres://user:password@localhost:5432/falcon?sslmode=disable" up -> # SQLite -> goose -dir relayer/db/migrations/sqlite sqlite "gorm.db" up +# SQLite +goose -dir relayer/db/migrations/sqlite sqlite "gorm.db" up ``` ##### Down (revert one step)