diff --git a/cmd/solana/main.go b/cmd/solana/main.go index d89c4f1a17..b062a3f31f 100644 --- a/cmd/solana/main.go +++ b/cmd/solana/main.go @@ -15,6 +15,7 @@ import ( "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" "github.com/near/borsh-go" + "github.com/zeta-chain/zetacore/pkg/chains" ) const ( @@ -163,7 +164,7 @@ func main() { } fmt.Println("recent blockhash:", recent.Value.Blockhash) - programId := solana.MustPublicKeyFromBase58("4Nt8tsYWQj3qC1TbunmmmDbzRXE4UQuzcGcqqgwy9bvX") + programId := solana.MustPublicKeyFromBase58("8J5Kxan9DTZTcHq6W5zj2UFuoARiwyLmUx87qPL1L7eP") seed := []byte("meta") pdaComputed, bump, err := solana.FindProgramAddress([][]byte{seed}, programId) if err != nil { @@ -255,7 +256,7 @@ func main() { Nonce uint64 } // fetch PDA account - programId := solana.MustPublicKeyFromBase58("4Nt8tsYWQj3qC1TbunmmmDbzRXE4UQuzcGcqqgwy9bvX") + programId := solana.MustPublicKeyFromBase58("8J5Kxan9DTZTcHq6W5zj2UFuoARiwyLmUx87qPL1L7eP") seed := []byte("meta") pdaComputed, bump, err := solana.FindProgramAddress([][]byte{seed}, programId) if err != nil { @@ -295,6 +296,7 @@ func main() { to := privkey.PublicKey() bytes := make([]byte, 8) nonce := pda.Nonce + binary.BigEndian.PutUint64(bytes, uint64(chains.SolanaLocalnet.ChainId)) binary.BigEndian.PutUint64(bytes, nonce) message = append(message, bytes...) binary.BigEndian.PutUint64(bytes, amount) diff --git a/cmd/zetaclientd/utils.go b/cmd/zetaclientd/utils.go index fb18c3e5b0..80883998cb 100644 --- a/cmd/zetaclientd/utils.go +++ b/cmd/zetaclientd/utils.go @@ -18,6 +18,8 @@ import ( evmsigner "github.com/zeta-chain/zetacore/zetaclient/chains/evm/signer" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" solanaobserver "github.com/zeta-chain/zetacore/zetaclient/chains/solana/observer" + solanasigner "github.com/zeta-chain/zetacore/zetaclient/chains/solana/signer" + "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/zetaclient/keys" @@ -110,6 +112,18 @@ func CreateSignerMap( } } + // FIXME: config this + solChain := chains.SolanaLocalnet + { + signer, err := solanasigner.NewSigner(solChain, appContext, tss, ts, logger) + if err != nil { + logger.Std.Error().Err(err).Msgf("NewSolanaSigner error for chain %s", solChain.String()) + } else { + logger.Std.Info().Msgf("NewSolanaSigner for chain %s", solChain.String()) + signerMap[solChain.ChainId] = signer + } + } + return signerMap, nil } diff --git a/e2e/e2etests/test_solana_deposit.go b/e2e/e2etests/test_solana_deposit.go index c8e8e9025b..e4e45e6915 100644 --- a/e2e/e2etests/test_solana_deposit.go +++ b/e2e/e2etests/test_solana_deposit.go @@ -119,6 +119,7 @@ func TestSolanaInitializeGateway(r *runner.E2ERunner, args []string) { Nonce uint64 TssAddress [20]byte Authority [32]byte + ChainID uint64 } pdaInfo, err := client.GetAccountInfo(context.TODO(), pdaComputed) if err != nil { @@ -128,7 +129,7 @@ func TestSolanaInitializeGateway(r *runner.E2ERunner, args []string) { var pda PdaInfo borsh.Deserialize(&pda, pdaInfo.Bytes()) - r.Logger.Print("PDA info Tss: %v", pda.TssAddress) + r.Logger.Print("PDA info Tss: %v, chain id %d", pda.TssAddress, pda.ChainID) } @@ -248,6 +249,7 @@ func TestSolanaDeposit(r *runner.E2ERunner, args []string) { func TestSolanaWithdraw(r *runner.E2ERunner, args []string) { r.Logger.Print("TestSolanaWithdraw...sol zrc20 %s", r.SOLZRC20Addr.String()) + privkey := solana.MustPrivateKeyFromBase58("4yqSQxDeTBvn86BuxcN5jmZb2gaobFXrBqu8kiE9rZxNkVMe3LfXmFigRsU4sRp7vk4vVP1ZCFiejDKiXBNWvs2C") solZRC20 := r.SOLZRC20 supply, err := solZRC20.BalanceOf(&bind.CallOpts{}, r.ZEVMAuth.From) @@ -265,10 +267,13 @@ func TestSolanaWithdraw(r *runner.E2ERunner, args []string) { receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequireTxSuccessful(r, receipt) - tx, err = r.SOLZRC20.Withdraw(r.ZEVMAuth, r.EVMAddress().Bytes(), amount) + tx, err = r.SOLZRC20.Withdraw(r.ZEVMAuth, []byte(privkey.PublicKey().String()), amount) require.NoError(r, err) r.Logger.EVMTransaction(*tx, "withdraw") receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequireTxSuccessful(r, receipt) - r.Logger.Info("Receipt txhash %s status %d", receipt.TxHash, receipt.Status) + r.Logger.Print("Receipt txhash %s status %d", receipt.TxHash, receipt.Status) + + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "withdraw") } diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go index 8ee344eef4..a843bdad35 100644 --- a/e2e/txserver/zeta_tx_server.go +++ b/e2e/txserver/zeta_tx_server.go @@ -375,11 +375,11 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20( IsSupported: true, GatewayAddress: "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d", BallotThreshold: sdktypes.MustNewDecFromStr("0.66"), - ConfirmationCount: 32, + ConfirmationCount: 16, GasPriceTicker: 100, InboundTicker: 5, OutboundTicker: 5, - OutboundScheduleInterval: 10, + OutboundScheduleInterval: 2, OutboundScheduleLookahead: 10, MinObserverDelegation: sdktypes.MustNewDecFromStr("1"), } diff --git a/zetaclient/chains/solana/observer/observer.go b/zetaclient/chains/solana/observer/observer.go index 5cb66dc169..55caf10cae 100644 --- a/zetaclient/chains/solana/observer/observer.go +++ b/zetaclient/chains/solana/observer/observer.go @@ -108,21 +108,23 @@ func NewObserver( return &ob, nil } +// IsOutboundProcessed returns included, confirmed, error func (o *Observer) IsOutboundProcessed(cctx *types.CrossChainTx, logger zerolog.Logger) (bool, bool, error) { //TODO implement me - panic("implement me") + //panic("implement me") + return false, false, nil } func (o *Observer) SetChainParams(params observertypes.ChainParams) { - //TODO implement me - panic("implement me") + o.Mu.Lock() + defer o.Mu.Unlock() + o.chainParams = params } func (o *Observer) GetChainParams() observertypes.ChainParams { - //TODO implement me - return observertypes.ChainParams{ - IsSupported: true, - } + o.Mu.Lock() + defer o.Mu.Unlock() + return o.chainParams } func (o *Observer) GetTxID(nonce uint64) string { @@ -279,6 +281,7 @@ func (o *Observer) WatchGasPrice() { o.logger.Err(err).Msg("GetSlot error") continue } + // FIXME: what's the fee rate of compute unit? How to query? txhash, err := o.zetacoreClient.PostGasPrice(o.chain, 1, "", slot) if err != nil { o.logger.Err(err).Msg("PostGasPrice error") diff --git a/zetaclient/chains/solana/signer/signer.go b/zetaclient/chains/solana/signer/signer.go new file mode 100644 index 0000000000..847d36255b --- /dev/null +++ b/zetaclient/chains/solana/signer/signer.go @@ -0,0 +1,279 @@ +package signer + +import ( + "context" + "encoding/binary" + "fmt" + + "github.com/davecgh/go-spew/spew" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + "github.com/near/borsh-go" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/chains/base" + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + clientcontext "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/metrics" + "github.com/zeta-chain/zetacore/zetaclient/outboundprocessor" +) + +// Signer deals with signing BTC transactions and implements the ChainSigner interface +type Signer struct { + *base.Signer + + // client is the RPC client to interact with the Bitcoin chain + client *rpc.Client +} + +// NewSigner creates a new Bitcoin signer +func NewSigner( + chain chains.Chain, + appContext *clientcontext.AppContext, + tss interfaces.TSSSigner, + ts *metrics.TelemetryServer, + logger base.Logger, + // client *rpc.Client, + // cfg config.BTCConfig +) (*Signer, error) { + // create base signer + baseSigner := base.NewSigner(chain, appContext, tss, ts, logger) + // FIXME: config RPC + client := rpc.New("http://solana:8899") + + return &Signer{ + Signer: baseSigner, + client: client, + }, nil +} + +var _ interfaces.ChainSigner = &Signer{} + +func (s *Signer) TryProcessOutbound(cctx *types.CrossChainTx, outboundProc *outboundprocessor.Processor, outboundID string, observer interfaces.ChainObserver, zetacoreClient interfaces.ZetacoreClient, height uint64) { + defer func() { + outboundProc.EndTryProcess(outboundID) + if err := recover(); err != nil { + s.Logger().Std.Error().Msgf("Solana TryProcessOutbound: %s, caught panic error: %v", cctx.Index, err) + } + }() + + logger := s.Logger().Std.With(). + Str("OutboundID", outboundID). + Str("SendHash", cctx.Index). + Logger() + + params := cctx.GetCurrentOutboundParam() + coinType := cctx.InboundParams.CoinType + if coinType == coin.CoinType_Zeta || coinType == coin.CoinType_ERC20 { + logger.Error().Msgf("Solana TryProcessOutbound: can only send SOL to a Solana network") + return + } + logger.Info(). + Msgf("Solana TryProcessOutbound: %s, value %d to %s", cctx.Index, params.Amount.BigInt(), params.Receiver) + + //solObserver, ok := observer.(*Observer) + //chain := solObserver.chain + outboundTssNonce := params.TssNonce + //signerAddress, err := zetacoreClient.GetKeys().GetAddress() + //if err != nil { + // logger.Error().Err(err).Msgf("cannot get signer address") + // return + //} + + // get size limit and gas price + //fee := 5000 // FIXME: this is the fixed fee (for signatures), explore priority fee for compute units + + //to, err := chains.DecodeBtcAddress(params.Receiver, params.ReceiverChainId) + // NOTE: withrawal event hook must validate the receiver address format + to := solana.MustPublicKeyFromBase58(params.Receiver) + amount := params.Amount.Uint64() + + { // TODO: refactor this piece out to a separate (withdraw) function + // FIXME: config this; right now it's the same privkey used by local e2e test_solana_*.go + privkey := solana.MustPrivateKeyFromBase58("4yqSQxDeTBvn86BuxcN5jmZb2gaobFXrBqu8kiE9rZxNkVMe3LfXmFigRsU4sRp7vk4vVP1ZCFiejDKiXBNWvs2C") + type WithdrawInstructionParams struct { + Discriminator [8]byte + Amount uint64 + Signature [64]byte + RecoveryID uint8 + MessageHash [32]byte + Nonce uint64 + } + chain, chainParams, ok := s.AppContext().GetSolanaChainParams() + if !ok { + s.Logger().Std.Error().Msg("cannot get chain params") + return + } + programId := solana.MustPublicKeyFromBase58(chainParams.GatewayAddress) + seed := []byte("meta") + pdaComputed, bump, err := solana.FindProgramAddress([][]byte{seed}, programId) + if err != nil { + panic(err) + } + fmt.Printf("computed pda: %s, bump %d\n", pdaComputed, bump) + type PdaInfo struct { + Discriminator [8]byte + Nonce uint64 + TssAddress [20]byte + Authority [32]byte + ChainID uint64 + } + pdaInfo, err := s.client.GetAccountInfo(context.TODO(), pdaComputed) + if err != nil { + panic(err) + } + fmt.Printf("pdainfo: %v\n", pdaInfo.Bytes()) + var pda PdaInfo + err = borsh.Deserialize(&pda, pdaInfo.Bytes()) + if err != nil { + panic(err) + } + fmt.Printf("pda parsed: %+v\n", pda) + + recent, err := s.client.GetRecentBlockhash(context.TODO(), rpc.CommitmentFinalized) + if err != nil { + panic(err) + } + fmt.Println("recent blockhash:", recent.Value.Blockhash) + var inst solana.GenericInstruction + + var message []byte + bytes := make([]byte, 8) + chainId := uint64(chain.ChainId) + nonce := outboundTssNonce + binary.BigEndian.PutUint64(bytes, chainId) + message = append(message, bytes...) + binary.BigEndian.PutUint64(bytes, nonce) + message = append(message, bytes...) + binary.BigEndian.PutUint64(bytes, amount) + message = append(message, bytes...) + message = append(message, to.Bytes()...) + messageHash := crypto.Keccak256Hash(message) + fmt.Printf("solana msghash: chainid %d, nonce %d, amount %d, to %s, hash %s", chainId, nonce, amount, to.String(), messageHash.String()) + // this sig will be 65 bytes; R || S || V, where V is 0 or 1 + signature, err := s.TSS().Sign(messageHash.Bytes(), height, nonce, chain.ChainId, "") + if err != nil { + s.Logger().Std.Error().Err(err).Msg("cannot sign message") + panic(err) + } + s.Logger().Std.Info(). + Msgf("Key-sign success: %d => %s, nonce %d", cctx.InboundParams.SenderChainId, chain.ChainName, outboundTssNonce) + + s.Logger().Std.Info().Msgf("recovery id %d", signature[64]) + var sig [64]byte + copy(sig[:], signature[:64]) + + inst.DataBytes, err = borsh.Serialize(WithdrawInstructionParams{ + Discriminator: [8]byte{183, 18, 70, 156, 148, 109, 161, 34}, + Amount: amount, + Signature: sig, + RecoveryID: signature[64], + MessageHash: messageHash, + Nonce: nonce, + }) + var accountSlice []*solana.AccountMeta + accountSlice = append(accountSlice, solana.Meta(privkey.PublicKey()).WRITE().SIGNER()) + accountSlice = append(accountSlice, solana.Meta(pdaComputed).WRITE()) + accountSlice = append(accountSlice, solana.Meta(to).WRITE()) + accountSlice = append(accountSlice, solana.Meta(programId)) + inst.ProgID = programId + inst.AccountValues = accountSlice + tx, err := solana.NewTransaction( + []solana.Instruction{&inst}, + recent.Value.Blockhash, + solana.TransactionPayer(privkey.PublicKey()), + ) + if err != nil { + panic(err) + } + _, err = tx.Sign( + func(key solana.PublicKey) *solana.PrivateKey { + if privkey.PublicKey().Equals(key) { + return &privkey + } + return nil + }, + ) + if err != nil { + panic(fmt.Errorf("unable to sign transaction: %w", err)) + } + spew.Dump(tx) + // FIXME: simulate before broadcast! + txsig, err := s.client.SendTransactionWithOpts( + context.TODO(), + tx, + rpc.TransactionOpts{}, + ) + //broadcast success! see + if err != nil { + panic(err) + } + spew.Dump(txsig) + } + + // FIXME: add prometheus metrics + //_, err = zetacoreClient.GetObserverList() + //if err != nil { + // logger.Warn(). + // Err(err). + // Msgf("unable to get observer list: chain %d observation %s", outboundTssNonce, observertypes.ObservationType_OutboundTx.String()) + //} + //if tx != nil { + // outboundHash := tx.TxHash().String() + // logger.Info(). + // Msgf("on chain %s nonce %d, outboundHash %s signer %s", chain.ChainName, outboundTssNonce, outboundHash, signerAddress) + // + // // try broacasting tx with increasing backoff (1s, 2s, 4s, 8s, 16s) in case of RPC error + // backOff := broadcastBackoff + // for i := 0; i < broadcastRetries; i++ { + // time.Sleep(backOff) + // err := signer.Broadcast(tx) + // if err != nil { + // logger.Warn(). + // Err(err). + // Msgf("broadcasting tx %s to chain %s: nonce %d, retry %d", outboundHash, chain.ChainName, outboundTssNonce, i) + // backOff *= 2 + // continue + // } + // logger.Info(). + // Msgf("Broadcast success: nonce %d to chain %s outboundHash %s", outboundTssNonce, chain.String(), outboundHash) + // zetaHash, err := zetacoreClient.AddOutboundTracker( + // chain.ChainId, + // outboundTssNonce, + // outboundHash, + // nil, + // "", + // -1, + // ) + // if err != nil { + // logger.Err(err). + // Msgf("Unable to add to tracker on zetacore: nonce %d chain %s outboundHash %s", outboundTssNonce, chain.ChainName, outboundHash) + // } + // logger.Info().Msgf("Broadcast to core successful %s", zetaHash) + // + // // Save successfully broadcasted transaction to btc chain observer + // btcObserver.SaveBroadcastedTx(outboundHash, outboundTssNonce) + // + // break // successful broadcast; no need to retry + // } + //} +} + +func (s *Signer) SetZetaConnectorAddress(address ethcommon.Address) { + panic("implement me") +} + +func (s *Signer) SetERC20CustodyAddress(address ethcommon.Address) { + panic("SetERC20CustodyAddress should not be called on Solana signer") +} + +func (s *Signer) GetZetaConnectorAddress() ethcommon.Address { + panic("GetZetaConnectorAddress should not be called on Solana signer") +} + +func (s *Signer) GetERC20CustodyAddress() ethcommon.Address { + panic("GetERC20CustodyAddress should not be called on Solana signer") +} diff --git a/zetaclient/context/app.go b/zetaclient/context/app.go index 4888443ea9..16acfa60a1 100644 --- a/zetaclient/context/app.go +++ b/zetaclient/context/app.go @@ -22,6 +22,7 @@ type AppContext struct { chainsEnabled []chains.Chain evmChainParams map[int64]*observertypes.ChainParams bitcoinChainParams *observertypes.ChainParams + solanaChainParams *observertypes.ChainParams currentTssPubkey string crosschainFlags observertypes.CrosschainFlags @@ -185,6 +186,23 @@ func (a *AppContext) GetBTCChainParams() (chains.Chain, *observertypes.ChainPara return chain, a.bitcoinChainParams, true } +// GetSolanaChainParams returns (chain, chain params, found) for solana chain +func (a *AppContext) GetSolanaChainParams() (chains.Chain, *observertypes.ChainParams, bool) { + a.mu.RLock() + defer a.mu.RUnlock() + + if a.solanaChainParams == nil { // solana is not enabled + return chains.Chain{}, nil, false + } + + chain, found := chains.GetChainFromChainID(a.solanaChainParams.ChainId, a.additionalChain) + if !found { + return chains.Chain{}, nil, false + } + + return chain, a.solanaChainParams, true +} + // GetCrossChainFlags returns crosschain flags func (a *AppContext) GetCrossChainFlags() observertypes.CrosschainFlags { a.mu.RLock() @@ -229,6 +247,7 @@ func (a *AppContext) Update( newChains []chains.Chain, evmChainParams map[int64]*observertypes.ChainParams, btcChainParams *observertypes.ChainParams, + solanaChainParams *observertypes.ChainParams, tssPubKey string, crosschainFlags observertypes.CrosschainFlags, additionalChains []chains.Chain, @@ -269,6 +288,8 @@ func (a *AppContext) Update( a.bitcoinChainParams = btcChainParams } + a.solanaChainParams = solanaChainParams + // update core params for evm chains we have configs in file for _, params := range evmChainParams { _, found := a.evmChainParams[params.ChainId] diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index b233c9d582..592e4b9382 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -9,6 +9,7 @@ import ( sdkmath "cosmossdk.io/math" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" + solanaobserver "github.com/zeta-chain/zetacore/zetaclient/chains/solana/observer" "github.com/zeta-chain/zetacore/pkg/chains" zetamath "github.com/zeta-chain/zetacore/pkg/math" @@ -182,6 +183,13 @@ func (oc *Orchestrator) GetUpdatedChainObserver( oc.logger.Std.Info().Msgf( "updated chain params for Bitcoin, new params: %v", *btcParams) } + } else if chains.IsSolanaChain(chainID, appContext.GetAdditionalChains()) { + _, solParams, found := appContext.GetSolanaChainParams() + if found && !observertypes.ChainParamsEqual(curParams, *solParams) { + observer.SetChainParams(*solParams) + oc.logger.Std.Info().Msgf( + "updated chain params for Solana, new params: %v", *solParams) + } } return observer, nil } @@ -323,6 +331,8 @@ func (oc *Orchestrator) StartCctxScheduler(appContext *context.AppContext) { oc.ScheduleCctxEVM(zetaHeight, c.ChainId, cctxList, ob, signer) } else if chains.IsBitcoinChain(c.ChainId, appContext.GetAdditionalChains()) { oc.ScheduleCctxBTC(zetaHeight, c.ChainId, cctxList, ob, signer) + } else if chains.IsSolanaChain(c.ChainId, appContext.GetAdditionalChains()) { + oc.ScheduleCctxSolana(zetaHeight, c.ChainId, cctxList, ob, signer) } else { oc.logger.Std.Error().Msgf("StartCctxScheduler: unsupported chain %d", c.ChainId) continue @@ -493,3 +503,66 @@ func (oc *Orchestrator) ScheduleCctxBTC( } } } + +// ScheduleCctxSolana schedules Solana outbound keysign on each ZetaChain block (the ticker) + +func (oc *Orchestrator) ScheduleCctxSolana( + zetaHeight uint64, + chainID int64, + cctxList []*types.CrossChainTx, + observer interfaces.ChainObserver, + signer interfaces.ChainSigner, +) { + oc.logger.Std.Info().Msgf("ScheduleCctxSolana: zetaHeight %d, chainID %d, cctxList %d", zetaHeight, chainID, len(cctxList)) + solObserver, ok := observer.(*solanaobserver.Observer) + if !ok { // should never happen + oc.logger.Std.Error().Msgf("ScheduleCctxSolana: chain observer is not a solana observer") + return + } + // #nosec G701 positive + interval := uint64(observer.GetChainParams().OutboundScheduleInterval) + //lookahead := observer.GetChainParams().OutboundScheduleLookahead + + // schedule at most one keysign per ticker + for idx, cctx := range cctxList { + _ = idx + params := cctx.GetCurrentOutboundParam() + nonce := params.TssNonce + outboundID := outboundprocessor.ToOutboundID(cctx.Index, params.ReceiverChainId, nonce) + + if params.ReceiverChainId != chainID { + oc.logger.Std.Error(). + Msgf("ScheduleCctxSolana: outbound %s chainid mismatch: want %d, got %d", outboundID, chainID, params.ReceiverChainId) + continue + } + // try confirming the outbound + included, confirmed, err := solObserver.IsOutboundProcessed(cctx, oc.logger.Std) + if err != nil { + oc.logger.Std.Error(). + Err(err). + Msgf("ScheduleCctxSolana: IsOutboundProcessed faild for chain %d nonce %d", chainID, nonce) + continue + } + if included || confirmed { + oc.logger.Std.Info(). + Msgf("ScheduleCctxSolana: outbound %s already included; do not schedule keysign", outboundID) + continue + } + oc.logger.Std.Info().Msgf("ScheduleCctxSolana: idx: %d; interval %d", idx, interval) + + // stop if lookahead is reached + //if int64( + // idx, + //) >= lookahead { // 2 bitcoin confirmations span is 20 minutes on average. We look ahead up to 100 pending cctx to target TPM of 5. + // oc.logger.Std.Warn(). + // Msgf("ScheduleCctxSolana: lookahead reached, signing %d, earliest pending %d", nonce, cctxList[0].GetCurrentOutboundParam().TssNonce) + // break + //} + // try confirming the outbound or scheduling a keysign + if nonce%interval == zetaHeight%interval && !oc.outboundProc.IsOutboundActive(outboundID) { + oc.outboundProc.StartTryProcess(outboundID) + oc.logger.Std.Debug().Msgf("ScheduleCctxSolana: sign outbound %s with value %d\n", outboundID, params.Amount) + go signer.TryProcessOutbound(cctx, oc.outboundProc, outboundID, observer, oc.zetacoreClient, zetaHeight) + } + } +} diff --git a/zetaclient/zetacore/client.go b/zetaclient/zetacore/client.go index 53d1d5958c..e0285770cb 100644 --- a/zetaclient/zetacore/client.go +++ b/zetaclient/zetacore/client.go @@ -222,6 +222,7 @@ func (c *Client) UpdateZetacoreContext(coreContext *context.AppContext, init boo newEVMParams := make(map[int64]*observertypes.ChainParams) var newBTCParams *observertypes.ChainParams + var newSolanaParams *observertypes.ChainParams // check and update chain params for each chain for _, chainParam := range chainParams { @@ -234,6 +235,8 @@ func (c *Client) UpdateZetacoreContext(coreContext *context.AppContext, init boo newBTCParams = chainParam } else if chains.IsEVMChain(chainParam.ChainId, additionalChains) { newEVMParams[chainParam.ChainId] = chainParam + } else if chains.IsSolanaChain(chainParam.ChainId, additionalChains) { + newSolanaParams = chainParam } } @@ -275,6 +278,7 @@ func (c *Client) UpdateZetacoreContext(coreContext *context.AppContext, init boo newChains, newEVMParams, newBTCParams, + newSolanaParams, tssPubKey, crosschainFlags, additionalChains,