Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SendExternalMessageWaitTransaction api for APIClient #226

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ton/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type APIClientWrapped interface {
GetMasterchainInfo(ctx context.Context) (*BlockIDExt, error)
GetAccount(ctx context.Context, block *BlockIDExt, addr *address.Address) (*tlb.Account, error)
SendExternalMessage(ctx context.Context, msg *tlb.ExternalMessage) error
SendExternalMessageWaitTransaction(ctx context.Context, msg *tlb.ExternalMessage) (*tlb.Transaction, *BlockIDExt, []byte, error)
RunGetMethod(ctx context.Context, blockInfo *BlockIDExt, addr *address.Address, method string, params ...interface{}) (*ExecutionResult, error)
ListTransactions(ctx context.Context, addr *address.Address, num uint32, lt uint64, txHash []byte) ([]*tlb.Transaction, error)
GetTransaction(ctx context.Context, block *BlockIDExt, addr *address.Address, lt uint64) (*tlb.Transaction, error)
Expand Down
127 changes: 127 additions & 0 deletions ton/sendmessagewait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package ton

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

"github.com/xssnick/tonutils-go/tlb"
)

var ErrTxWasNotConfirmed = errors.New("transaction was not confirmed in a given deadline, but it may still be confirmed later")

func (api *APIClient) SendExternalMessageWaitTransaction(ctx context.Context, ext *tlb.ExternalMessage) (*tlb.Transaction, *BlockIDExt, []byte, error) {
block, err := api.CurrentMasterchainInfo(ctx)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to get block: %w", err)
}

acc, err := api.WaitForBlock(block.SeqNo).GetAccount(ctx, block, ext.DstAddr)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to get account state: %w", err)
}

inMsgHash := ext.Body.Hash()

if err = api.SendExternalMessage(ctx, ext); err != nil {
return nil, nil, nil, fmt.Errorf("failed to send message: %w", err)
}

tx, block, err := api.waitConfirmation(ctx, block, acc, ext)
if err != nil {
return nil, nil, nil, err
}

return tx, block, inMsgHash, nil
}

func (api *APIClient) waitConfirmation(ctx context.Context, block *BlockIDExt, acc *tlb.Account, ext *tlb.ExternalMessage) (*tlb.Transaction, *BlockIDExt, error) {
if _, hasDeadline := ctx.Deadline(); !hasDeadline {
// fallback timeout to not stuck forever with background context
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
}
till, _ := ctx.Deadline()

ctx = api.Client().StickyContext(ctx)

for time.Now().Before(till) {
blockNew, err := api.WaitForBlock(block.SeqNo + 1).GetMasterchainInfo(ctx)
if err != nil {
continue
}

accNew, err := api.WaitForBlock(blockNew.SeqNo).GetAccount(ctx, blockNew, ext.DstAddr)
if err != nil {
continue
}
block = blockNew

if accNew.LastTxLT == acc.LastTxLT {
// if not in block, maybe LS lost our message, send it again
if err = api.SendExternalMessage(ctx, ext); err != nil {
continue
}

continue
}

lastLt, lastHash := accNew.LastTxLT, accNew.LastTxHash

// it is possible that > 5 new not related transactions will happen, and we should not lose our scan offset,
// to prevent this we will scan till we reach last seen offset.
for time.Now().Before(till) {
// we try to get last 5 transactions, and check if we have our new there.
txList, err := api.WaitForBlock(block.SeqNo).ListTransactions(ctx, ext.DstAddr, 5, lastLt, lastHash)
if err != nil {
continue
}

sawLastTx := false
for i, transaction := range txList {
if i == 0 {
// get previous of the oldest tx, in case if we need to scan deeper
lastLt, lastHash = txList[0].PrevTxLT, txList[0].PrevTxHash
}

if !sawLastTx && transaction.PrevTxLT == acc.LastTxLT &&
bytes.Equal(transaction.PrevTxHash, acc.LastTxHash) {
sawLastTx = true
}

if transaction.IO.In != nil && transaction.IO.In.MsgType == tlb.MsgTypeExternalIn {
extIn := transaction.IO.In.AsExternalIn()
if ext.StateInit != nil {
if extIn.StateInit == nil {
continue
}

if !bytes.Equal(ext.StateInit.Data.Hash(), extIn.StateInit.Data.Hash()) {
continue
}

if !bytes.Equal(ext.StateInit.Code.Hash(), extIn.StateInit.Code.Hash()) {
continue
}
}

if !bytes.Equal(extIn.Body.Hash(), ext.Body.Hash()) {
continue
}

return transaction, block, nil
}
}

if sawLastTx {
break
}
}
acc = accNew
}

return nil, nil, ErrTxWasNotConfirmed
}
43 changes: 24 additions & 19 deletions ton/wallet/wallet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,25 +462,26 @@ func checkHighloadV2R2(t *testing.T, p *cell.Slice, w *Wallet, intMsg *tlb.Inter
}

type WaiterMock struct {
MGetTime func(ctx context.Context) (uint32, error)
MLookupBlock func(ctx context.Context, workchain int32, shard int64, seqno uint32) (*ton.BlockIDExt, error)
MGetBlockData func(ctx context.Context, block *ton.BlockIDExt) (*tlb.Block, error)
MGetBlockTransactionsV2 func(ctx context.Context, block *ton.BlockIDExt, count uint32, after ...*ton.TransactionID3) ([]ton.TransactionShortInfo, bool, error)
MGetBlockShardsInfo func(ctx context.Context, master *ton.BlockIDExt) ([]*ton.BlockIDExt, error)
MGetBlockchainConfig func(ctx context.Context, block *ton.BlockIDExt, onlyParams ...int32) (*ton.BlockchainConfig, error)
MGetMasterchainInfo func(ctx context.Context) (*ton.BlockIDExt, error)
MGetAccount func(ctx context.Context, block *ton.BlockIDExt, addr *address.Address) (*tlb.Account, error)
MSendExternalMessage func(ctx context.Context, msg *tlb.ExternalMessage) error
MRunGetMethod func(ctx context.Context, blockInfo *ton.BlockIDExt, addr *address.Address, method string, params ...interface{}) (*ton.ExecutionResult, error)
MListTransactions func(ctx context.Context, addr *address.Address, num uint32, lt uint64, txHash []byte) ([]*tlb.Transaction, error)
MGetTransaction func(ctx context.Context, block *ton.BlockIDExt, addr *address.Address, lt uint64) (*tlb.Transaction, error)
MWaitForBlock func(seqno uint32) ton.APIClientWrapped
MWithRetry func(x ...int) ton.APIClientWrapped
MWithTimeout func(timeout time.Duration) ton.APIClientWrapped
MCurrentMasterchainInfo func(ctx context.Context) (_ *ton.BlockIDExt, err error)
MGetBlockProof func(ctx context.Context, known, target *ton.BlockIDExt) (*ton.PartialBlockProof, error)
MFindLastTransactionByInMsgHash func(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error)
MFindLastTransactionByOutMsgHash func(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error)
MGetTime func(ctx context.Context) (uint32, error)
MLookupBlock func(ctx context.Context, workchain int32, shard int64, seqno uint32) (*ton.BlockIDExt, error)
MGetBlockData func(ctx context.Context, block *ton.BlockIDExt) (*tlb.Block, error)
MGetBlockTransactionsV2 func(ctx context.Context, block *ton.BlockIDExt, count uint32, after ...*ton.TransactionID3) ([]ton.TransactionShortInfo, bool, error)
MGetBlockShardsInfo func(ctx context.Context, master *ton.BlockIDExt) ([]*ton.BlockIDExt, error)
MGetBlockchainConfig func(ctx context.Context, block *ton.BlockIDExt, onlyParams ...int32) (*ton.BlockchainConfig, error)
MGetMasterchainInfo func(ctx context.Context) (*ton.BlockIDExt, error)
MGetAccount func(ctx context.Context, block *ton.BlockIDExt, addr *address.Address) (*tlb.Account, error)
MSendExternalMessage func(ctx context.Context, msg *tlb.ExternalMessage) error
MRunGetMethod func(ctx context.Context, blockInfo *ton.BlockIDExt, addr *address.Address, method string, params ...interface{}) (*ton.ExecutionResult, error)
MListTransactions func(ctx context.Context, addr *address.Address, num uint32, lt uint64, txHash []byte) ([]*tlb.Transaction, error)
MGetTransaction func(ctx context.Context, block *ton.BlockIDExt, addr *address.Address, lt uint64) (*tlb.Transaction, error)
MWaitForBlock func(seqno uint32) ton.APIClientWrapped
MWithRetry func(x ...int) ton.APIClientWrapped
MWithTimeout func(timeout time.Duration) ton.APIClientWrapped
MCurrentMasterchainInfo func(ctx context.Context) (_ *ton.BlockIDExt, err error)
MGetBlockProof func(ctx context.Context, known, target *ton.BlockIDExt) (*ton.PartialBlockProof, error)
MFindLastTransactionByInMsgHash func(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error)
MFindLastTransactionByOutMsgHash func(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error)
MSendExternalMessageWaitTransaction func(ctx context.Context, msg *tlb.ExternalMessage) (*tlb.Transaction, *ton.BlockIDExt, []byte, error)
}

func (w WaiterMock) FindLastTransactionByInMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) {
Expand Down Expand Up @@ -521,6 +522,10 @@ func (w WaiterMock) Client() ton.LiteClient {
panic("implement me")
}

func (w WaiterMock) SendExternalMessageWaitTransaction(ctx context.Context, msg *tlb.ExternalMessage) (*tlb.Transaction, *ton.BlockIDExt, []byte, error) {
return w.MSendExternalMessageWaitTransaction(ctx, msg)
}

func (w WaiterMock) CurrentMasterchainInfo(ctx context.Context) (_ *ton.BlockIDExt, err error) {
return w.MCurrentMasterchainInfo(ctx)
}
Expand Down