Skip to content

Commit 6ffa725

Browse files
authored
Merge branch 'main' into release/v1.1.23-donut
2 parents ca7fad1 + c0d515c commit 6ffa725

File tree

6 files changed

+112
-19
lines changed

6 files changed

+112
-19
lines changed

universalClient/chains/common/event_processor.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,8 @@ func (ep *EventProcessor) constructInbound(event *store.Event) (*uexecutortypes.
303303
}
304304
}
305305

306-
// Check if VerificationData is 0x and replace with TxHash
307-
if inboundMsg.UniversalPayload != nil && inboundMsg.UniversalPayload.VType == uexecutortypes.VerificationType_universalTxVerification {
306+
// Use event's VerificationData if present, otherwise fall back to txHash
307+
if eventData.VerificationData == "" || eventData.VerificationData == "0x" {
308308
inboundMsg.VerificationData = txHashHex
309309
} else {
310310
inboundMsg.VerificationData = eventData.VerificationData

universalClient/chains/svm/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ func (c *Client) initializeComponents() error {
259259
c.registryConfig.GatewayAddress,
260260
c.nodeHome,
261261
c.logger,
262+
c.chainConfig,
262263
)
263264
if err != nil {
264265
return fmt.Errorf("failed to create txBuilder: %w", err)

universalClient/chains/svm/tx_builder.go

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ import (
7272

7373
"github.com/ethereum/go-ethereum/crypto"
7474
"github.com/gagliardetto/solana-go"
75+
addresslookuptable "github.com/gagliardetto/solana-go/programs/address-lookup-table"
76+
"github.com/gagliardetto/solana-go/rpc"
7577
"github.com/rs/zerolog"
7678

7779
"github.com/pushchain/push-chain-node/universalClient/chains/common"
@@ -102,6 +104,8 @@ type TxBuilder struct {
102104
gatewayAddress solana.PublicKey
103105
nodeHome string
104106
logger zerolog.Logger
107+
protocolALT solana.PublicKey // Protocol ALT pubkey (zero if not configured)
108+
tokenALTs map[solana.PublicKey]solana.PublicKey // mint pubkey → token ALT pubkey
105109
}
106110

107111
// NewTxBuilder creates a new Solana transaction builder.
@@ -113,6 +117,7 @@ func NewTxBuilder(
113117
gatewayAddress string,
114118
nodeHome string,
115119
logger zerolog.Logger,
120+
chainConfig *config.ChainSpecificConfig,
116121
) (*TxBuilder, error) {
117122
if rpcClient == nil {
118123
return nil, fmt.Errorf("rpcClient is required")
@@ -129,13 +134,45 @@ func NewTxBuilder(
129134
return nil, fmt.Errorf("invalid gateway address: %w", err)
130135
}
131136

132-
return &TxBuilder{
137+
tb := &TxBuilder{
133138
rpcClient: rpcClient,
134139
chainID: chainID,
135140
gatewayAddress: addr,
136141
nodeHome: nodeHome,
137142
logger: logger.With().Str("component", "svm_tx_builder").Str("chain", chainID).Logger(),
138-
}, nil
143+
tokenALTs: make(map[solana.PublicKey]solana.PublicKey),
144+
}
145+
146+
// Parse ALT config if provided
147+
if chainConfig != nil {
148+
if chainConfig.ProtocolALT != "" {
149+
protocolALT, err := solana.PublicKeyFromBase58(chainConfig.ProtocolALT)
150+
if err != nil {
151+
tb.logger.Warn().Err(err).Str("address", chainConfig.ProtocolALT).Msg("invalid protocol ALT address, skipping")
152+
} else {
153+
tb.protocolALT = protocolALT
154+
tb.logger.Info().Str("protocol_alt", chainConfig.ProtocolALT).Msg("configured protocol ALT")
155+
}
156+
}
157+
for mint, altAddr := range chainConfig.TokenALTs {
158+
mintPubkey, err := solana.PublicKeyFromBase58(mint)
159+
if err != nil {
160+
tb.logger.Warn().Err(err).Str("mint", mint).Msg("invalid token ALT mint address, skipping")
161+
continue
162+
}
163+
altPubkey, err := solana.PublicKeyFromBase58(altAddr)
164+
if err != nil {
165+
tb.logger.Warn().Err(err).Str("alt", altAddr).Msg("invalid token ALT address, skipping")
166+
continue
167+
}
168+
tb.tokenALTs[mintPubkey] = altPubkey
169+
}
170+
if len(tb.tokenALTs) > 0 {
171+
tb.logger.Info().Int("count", len(tb.tokenALTs)).Msg("configured token ALTs")
172+
}
173+
}
174+
175+
return tb, nil
139176
}
140177

141178
// =============================================================================
@@ -464,6 +501,49 @@ func (tb *TxBuilder) BroadcastOutboundSigningRequest(
464501
return txHash, nil
465502
}
466503

504+
// fetchAddressTables fetches Address Lookup Table state for V0 transactions.
505+
// Always includes the protocol ALT (if configured). For SPL tokens, also includes
506+
// the token-specific ALT for the given mint (if configured).
507+
// Returns an empty map if no ALTs are configured (transaction falls back to legacy format).
508+
func (tb *TxBuilder) fetchAddressTables(ctx context.Context, mintPubkey solana.PublicKey, isNative bool) (map[solana.PublicKey]solana.PublicKeySlice, error) {
509+
addressTables := make(map[solana.PublicKey]solana.PublicKeySlice)
510+
511+
// Collect ALT pubkeys to fetch
512+
var altsToFetch []solana.PublicKey
513+
514+
if !tb.protocolALT.IsZero() {
515+
altsToFetch = append(altsToFetch, tb.protocolALT)
516+
}
517+
518+
if !isNative {
519+
if tokenALT, ok := tb.tokenALTs[mintPubkey]; ok {
520+
altsToFetch = append(altsToFetch, tokenALT)
521+
}
522+
}
523+
524+
if len(altsToFetch) == 0 {
525+
return addressTables, nil
526+
}
527+
528+
// Fetch each ALT state from chain using the RPC client's failover mechanism
529+
for _, altPubkey := range altsToFetch {
530+
altKey := altPubkey // capture for closure
531+
var altState *addresslookuptable.AddressLookupTableState
532+
err := tb.rpcClient.executeWithFailover(ctx, "getAddressLookupTable", func(client *rpc.Client) error {
533+
var fetchErr error
534+
altState, fetchErr = addresslookuptable.GetAddressLookupTable(ctx, client, altKey)
535+
return fetchErr
536+
})
537+
if err != nil {
538+
return nil, fmt.Errorf("failed to fetch ALT %s: %w", altKey.String(), err)
539+
}
540+
addressTables[altKey] = altState.Addresses
541+
}
542+
543+
tb.logger.Debug().Int("alt_count", len(addressTables)).Msg("fetched address lookup tables for V0 transaction")
544+
return addressTables, nil
545+
}
546+
467547
// BuildOutboundTransaction assembles a complete signed Solana transaction from the
468548
// TSS signature and event data, without broadcasting. Returns the transaction and
469549
// the instruction ID for logging. Use this for simulation or inspection.
@@ -763,11 +843,14 @@ func (tb *TxBuilder) BuildOutboundTransaction(
763843
return nil, 0, fmt.Errorf("failed to get recent blockhash: %w", err)
764844
}
765845

766-
tx, err := solana.NewTransaction(
767-
instructions,
768-
recentBlockhash,
769-
solana.TransactionPayer(relayerKeypair.PublicKey()),
770-
)
846+
opts := []solana.TransactionOption{solana.TransactionPayer(relayerKeypair.PublicKey())}
847+
addressTables, err := tb.fetchAddressTables(ctx, mintPubkey, isNative)
848+
if err != nil {
849+
tb.logger.Warn().Err(err).Msg("failed to fetch ALTs, falling back to legacy tx")
850+
} else if len(addressTables) > 0 {
851+
opts = append(opts, solana.TransactionAddressTables(addressTables))
852+
}
853+
tx, err := solana.NewTransaction(instructions, recentBlockhash, opts...)
771854
if err != nil {
772855
return nil, 0, fmt.Errorf("failed to create transaction: %w", err)
773856
}

universalClient/chains/svm/tx_builder_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const testGatewayAddress = "CFVSincHYbETh2k7w6u1ENEkjbSLtveRCEBupKidw2VS"
3434
func newTestBuilder(t *testing.T) *TxBuilder {
3535
t.Helper()
3636
logger := zerolog.Nop()
37-
builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger)
37+
builder, err := NewTxBuilder(&RPCClient{}, "solana:devnet", testGatewayAddress, "/tmp", logger, nil)
3838
require.NoError(t, err)
3939
return builder
4040
}
@@ -199,7 +199,7 @@ func TestNewTxBuilder(t *testing.T) {
199199

200200
for _, tt := range tests {
201201
t.Run(tt.name, func(t *testing.T) {
202-
builder, err := NewTxBuilder(tt.rpcClient, tt.chainID, tt.gatewayAddress, "/tmp", logger)
202+
builder, err := NewTxBuilder(tt.rpcClient, tt.chainID, tt.gatewayAddress, "/tmp", logger, nil)
203203
if tt.expectError {
204204
assert.Error(t, err)
205205
if tt.errorContains != "" {
@@ -1493,7 +1493,7 @@ func setupDevnetSimulation(t *testing.T) (*RPCClient, *TxBuilder) {
14931493
require.NoError(t, os.MkdirAll(relayerDir, 0o755))
14941494
require.NoError(t, os.WriteFile(filepath.Join(relayerDir, "solana.json"), []byte(testSolanaKeypairJSON), 0o600))
14951495

1496-
builder, err := NewTxBuilder(rpcClient, "solana:"+devnetGenesisHash, devnetGatewayAddress, tmpDir, logger)
1496+
builder, err := NewTxBuilder(rpcClient, "solana:"+devnetGenesisHash, devnetGatewayAddress, tmpDir, logger, nil)
14971497
require.NoError(t, err)
14981498

14991499
t.Logf("relayer pubkey: AdWDRaQfvWJqW4TaxTrXP5WogCWJMJBrtBfGjjHUDADM")

universalClient/config/default_config.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,14 @@
6565
"retention_period_seconds": 172800,
6666
"event_polling_interval_seconds": 5,
6767
"gas_price_interval_seconds": 30,
68-
"event_start_from": 403697270
68+
"event_start_from": 403697270,
69+
"protocol_alt": "6AdUqeKQMapkoMXP3wJ7FrjYfVreqaD7bq3HZyZEn8kN",
70+
"token_alts": {
71+
"G2ZLaRhpohW23KTEX3fBjZXtNTFFwemqCaWWnWVTj4TB": "BBx5byj5YABPyiEv2DmT7yNM8A51tAaQFHfCQgxuKBeV",
72+
"4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU": "An9R3Bm5DA1s4hW784fSwtKiUdYS8E3HLTAeJtHDEr7",
73+
"49FaRFR7TJrjujGCqxVRKhyUgTpi5wQjqAurwd7fMMk1": "92YQrMwzg2um9r2XscRt94xe5b4vY6H3asMsgFqen4y9",
74+
"EiXDnrAg9ea2Q6vEPV7E5TpTU1vh41jcuZqKjU5Dc4ZF": "7QeSTLdxwf8PWawyWU2P9UK7KB6uu48Cefspz9wsCvsv"
75+
}
6976
},
7077
"push_42101-1": {
7178
"cleanup_interval_seconds": 1800,

universalClient/config/types.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ type Config struct {
4646

4747
// ChainSpecificConfig holds per-chain configuration.
4848
type ChainSpecificConfig struct {
49-
RPCURLs []string `json:"rpc_urls,omitempty"`
50-
CleanupIntervalSeconds *int `json:"cleanup_interval_seconds,omitempty"`
51-
RetentionPeriodSeconds *int `json:"retention_period_seconds,omitempty"`
52-
EventPollingIntervalSeconds *int `json:"event_polling_interval_seconds,omitempty"`
53-
EventStartFrom *int64 `json:"event_start_from,omitempty"`
54-
GasPriceIntervalSeconds *int `json:"gas_price_interval_seconds,omitempty"`
49+
RPCURLs []string `json:"rpc_urls,omitempty"`
50+
CleanupIntervalSeconds *int `json:"cleanup_interval_seconds,omitempty"`
51+
RetentionPeriodSeconds *int `json:"retention_period_seconds,omitempty"`
52+
EventPollingIntervalSeconds *int `json:"event_polling_interval_seconds,omitempty"`
53+
EventStartFrom *int64 `json:"event_start_from,omitempty"`
54+
GasPriceIntervalSeconds *int `json:"gas_price_interval_seconds,omitempty"`
55+
ProtocolALT string `json:"protocol_alt,omitempty"` // Protocol ALT address (base58) for V0 transactions
56+
TokenALTs map[string]string `json:"token_alts,omitempty"` // mint address → token ALT address (base58)
5557
}
5658

5759
// GetChainCleanupSettings returns cleanup settings for a specific chain.

0 commit comments

Comments
 (0)