From e3e497ccac879745eb5db04f5a96d2a7b3f27f81 Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Fri, 26 Jul 2024 20:42:25 +0800 Subject: [PATCH 01/31] SetCustomQueryIDFetcherWithContext Signed-off-by: s.klimov --- ton/wallet/highloadv2r2.go | 4 ++-- ton/wallet/regular.go | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ton/wallet/highloadv2r2.go b/ton/wallet/highloadv2r2.go index 4078d6cd..599b959f 100644 --- a/ton/wallet/highloadv2r2.go +++ b/ton/wallet/highloadv2r2.go @@ -22,7 +22,7 @@ type SpecHighloadV2R2 struct { SpecQuery } -func (s *SpecHighloadV2R2) BuildMessage(_ context.Context, messages []*Message) (*cell.Cell, error) { +func (s *SpecHighloadV2R2) BuildMessage(ctx context.Context, messages []*Message) (*cell.Cell, error) { if len(messages) > 254 { return nil, errors.New("for this type of wallet max 254 messages can be sent in the same time") } @@ -47,7 +47,7 @@ func (s *SpecHighloadV2R2) BuildMessage(_ context.Context, messages []*Message) var ttl, queryID uint32 if s.customQueryIDFetcher != nil { - ttl, queryID = s.customQueryIDFetcher() + ttl, queryID = s.customQueryIDFetcher(ctx) } else { queryID = randUint32() ttl = uint32(timeNow().Add(time.Duration(s.messagesTTL) * time.Second).UTC().Unix()) diff --git a/ton/wallet/regular.go b/ton/wallet/regular.go index cfd84683..54d9d89c 100644 --- a/ton/wallet/regular.go +++ b/ton/wallet/regular.go @@ -54,9 +54,15 @@ type SpecQuery struct { // Do not set ttl to high if you are sending many messages, // unexpired executed messages will be cached in contract, // and it may become too expensive to make transactions. - customQueryIDFetcher func() (ttl uint32, randPart uint32) + customQueryIDFetcher func(context.Context) (ttl uint32, randPart uint32) } func (s *SpecQuery) SetCustomQueryIDFetcher(fetcher func() (ttl uint32, randPart uint32)) { + s.SetCustomQueryIDFetcherWithContext(func(ctx context.Context) (ttl uint32, randPart uint32) { + return fetcher() + }) +} + +func (s *SpecQuery) SetCustomQueryIDFetcherWithContext(fetcher func(ctx context.Context) (ttl uint32, randPart uint32)) { s.customQueryIDFetcher = fetcher } From b76c122b8cd6693af79500499ec8445cfbd2607d Mon Sep 17 00:00:00 2001 From: Oleg Baranov Date: Fri, 26 Jul 2024 22:53:04 +0400 Subject: [PATCH 02/31] RLDP Packets delay tunning --- adnl/rldp/client.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/adnl/rldp/client.go b/adnl/rldp/client.go index 9f3e0da1..94e9c667 100644 --- a/adnl/rldp/client.go +++ b/adnl/rldp/client.go @@ -357,6 +357,9 @@ func (r *RLDP) sendMessageParts(ctx context.Context, transferId, data []byte) er if symbolsSent > fastSymbols { x := (symbolsSent - fastSymbols) / 2 + if x > 70 { // 7 ms max delay + x = 70 + } select { case <-ctx.Done(): @@ -365,7 +368,7 @@ func (r *RLDP) sendMessageParts(ctx context.Context, transferId, data []byte) er case <-ch: // we got complete from receiver, finish sending return nil - case <-time.After(time.Duration(x) * _PacketWaitTime): + case <-time.After(time.Duration(x) * (time.Millisecond / 10)): // send additional FEC recovery parts until complete } } From f838b6f897747fb9b17bebc4dcff3bc5448eaee1 Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Mon, 29 Jul 2024 01:58:06 +0800 Subject: [PATCH 03/31] add subwallet to highload v2 fetcher Signed-off-by: s.klimov --- ton/wallet/highloadv2r2.go | 6 +++++- ton/wallet/highloadv3.go | 2 +- ton/wallet/regular.go | 9 +++++---- ton/wallet/wallet.go | 2 ++ 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ton/wallet/highloadv2r2.go b/ton/wallet/highloadv2r2.go index 599b959f..9d47e289 100644 --- a/ton/wallet/highloadv2r2.go +++ b/ton/wallet/highloadv2r2.go @@ -47,7 +47,11 @@ func (s *SpecHighloadV2R2) BuildMessage(ctx context.Context, messages []*Message var ttl, queryID uint32 if s.customQueryIDFetcher != nil { - ttl, queryID = s.customQueryIDFetcher(ctx) + var err error + ttl, queryID, err = s.customQueryIDFetcher(ctx, s.wallet.subwallet) + if err != nil { + return nil, fmt.Errorf("failed to fetch queryID: %w", err) + } } else { queryID = randUint32() ttl = uint32(timeNow().Add(time.Duration(s.messagesTTL) * time.Second).UTC().Unix()) diff --git a/ton/wallet/highloadv3.go b/ton/wallet/highloadv3.go index ead72ca0..e158de38 100644 --- a/ton/wallet/highloadv3.go +++ b/ton/wallet/highloadv3.go @@ -43,7 +43,7 @@ func (s *SpecHighloadV3) BuildMessage(ctx context.Context, messages []*Message) queryID, createdAt, err := s.config.MessageBuilder(ctx, s.wallet.subwallet) if err != nil { - return nil, fmt.Errorf("failed to convert msg to cell: %w", err) + return nil, fmt.Errorf("failed to fetch queryID: %w", err) } if queryID >= 1<<23 { diff --git a/ton/wallet/regular.go b/ton/wallet/regular.go index 54d9d89c..1507c263 100644 --- a/ton/wallet/regular.go +++ b/ton/wallet/regular.go @@ -54,15 +54,16 @@ type SpecQuery struct { // Do not set ttl to high if you are sending many messages, // unexpired executed messages will be cached in contract, // and it may become too expensive to make transactions. - customQueryIDFetcher func(context.Context) (ttl uint32, randPart uint32) + customQueryIDFetcher func(ctx context.Context, subWalletId uint32) (ttl uint32, randPart uint32, err error) } func (s *SpecQuery) SetCustomQueryIDFetcher(fetcher func() (ttl uint32, randPart uint32)) { - s.SetCustomQueryIDFetcherWithContext(func(ctx context.Context) (ttl uint32, randPart uint32) { - return fetcher() + s.SetCustomQueryIDFetcherWithContext(func(ctx context.Context, subWalletId uint32) (ttl uint32, randPart uint32, err error) { + ttl, randPart = fetcher() + return ttl, randPart, nil }) } -func (s *SpecQuery) SetCustomQueryIDFetcherWithContext(fetcher func(ctx context.Context) (ttl uint32, randPart uint32)) { +func (s *SpecQuery) SetCustomQueryIDFetcherWithContext(fetcher func(ctx context.Context, subWalletId uint32) (ttl uint32, randPart uint32, err error)) { s.customQueryIDFetcher = fetcher } diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index ba30e287..d01c4f66 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -68,6 +68,8 @@ func (v Version) String() string { return fmt.Sprintf("highload V2R2") case HighloadV2Verified: return fmt.Sprintf("highload V2R2 verified") + case HighloadV3: + return fmt.Sprintf("highload V3") } if v/100 == 2 { From 0ce0d252a288a03d1fc69b28bcbd1472da52fdf3 Mon Sep 17 00:00:00 2001 From: Oleg Baranov Date: Mon, 29 Jul 2024 21:27:33 +0400 Subject: [PATCH 04/31] ADNL & RLDP-HTTP improvements and fixes --- adnl/adnl.go | 29 +++++++++++++++++++++++------ adnl/gateway.go | 21 ++++++++++----------- adnl/packet.go | 4 ++-- adnl/rldp/http/client.go | 6 +++--- adnl/rldp/http/server.go | 11 ++++++++--- 5 files changed, 46 insertions(+), 25 deletions(-) diff --git a/adnl/adnl.go b/adnl/adnl.go index 0595a77d..54cb67b8 100644 --- a/adnl/adnl.go +++ b/adnl/adnl.go @@ -146,6 +146,24 @@ func (c *Channel) process(buf []byte) error { func (a *ADNL) processPacket(packet *PacketContent, ch *Channel) (err error) { a.mx.Lock() + + if packet.DstReinitDate != nil && *packet.DstReinitDate > 0 && *packet.DstReinitDate < a.reinitTime { + if packet.ReinitDate != nil { + a.dstReinit = *packet.ReinitDate + } + a.mx.Unlock() + + buf, err := a.buildRequest(ch, MessageNop{}) + if err != nil { + return fmt.Errorf("failed to create packet: %w", err) + } + if err = a.send(context.Background(), buf); err != nil { + return fmt.Errorf("failed to send ping reinit: %w", err) + } + + return nil + } + seqno := uint64(*packet.Seqno) a.lastReceiveAt = time.Now() @@ -161,17 +179,14 @@ func (a *ADNL) processPacket(packet *PacketContent, ch *Channel) (err error) { a.confirmSeqno = seqno } - if packet.ReinitDate != nil && *packet.ReinitDate > a.dstReinit { + if (packet.ReinitDate != nil && *packet.ReinitDate > a.dstReinit) && + (packet.DstReinitDate != nil && *packet.DstReinitDate == a.reinitTime) { // reset their seqno even if it is lower, // because other side could lose counter a.confirmSeqno = seqno a.loss = 0 - // a.dstReinit = *packet.ReinitDate - // a.seqno = 0 - // a.channel = nil - // a.confirmSeqno = 0 - // a.reinitTime = a.dstReinit + a.dstReinit = *packet.ReinitDate } if packet.RecvPriorityAddrListVersion != nil { @@ -329,6 +344,8 @@ func (a *ADNL) processMessage(message any, ch *Channel) error { return fmt.Errorf("failed to handle custom message: %w", err) } } + case MessageNop: + return nil default: return fmt.Errorf("skipped unprocessable message of type %s", reflect.TypeOf(message).String()) } diff --git a/adnl/gateway.go b/adnl/gateway.go index d7cfeaf5..e8aaf73d 100644 --- a/adnl/gateway.go +++ b/adnl/gateway.go @@ -279,7 +279,7 @@ func (g *Gateway) listen(rootId []byte) { g.mx.RUnlock() if proc == nil { - Logger("no processor for ADNL packet from", hex.EncodeToString(id)) + Logger("no processor for ADNL packet from", addr.String(), hex.EncodeToString(id)) continue } @@ -384,18 +384,17 @@ func (g *Gateway) registerClient(addr net.Addr, key ed25519.PublicKey, id string closer: ch.adnl.Close, } g.mx.Unlock() + }) - if oldId == "" { // connection = first channel initialisation - connHandler := g.connHandler - if connHandler != nil { - err := connHandler(peer) - if err != nil { - // close connection if connection handler reports an error - ch.adnl.Close() - } + connHandler := g.connHandler + if connHandler != nil { + go func() { + if err := connHandler(peer); err != nil { + // close connection if connection handler reports an error + a.Close() } - } - }) + }() + } return peer, nil } diff --git a/adnl/packet.go b/adnl/packet.go index 033f756f..2646e4f3 100644 --- a/adnl/packet.go +++ b/adnl/packet.go @@ -162,9 +162,9 @@ func parsePacket(data []byte) (_ *PacketContent, err error) { data = data[4:] packet.ReinitDate = &reinit - reinit = int32(binary.LittleEndian.Uint32(data)) + dstReinit := int32(binary.LittleEndian.Uint32(data)) data = data[4:] - packet.DstReinitDate = &reinit + packet.DstReinitDate = &dstReinit } if flags&_FlagSignature != 0 { diff --git a/adnl/rldp/http/client.go b/adnl/rldp/http/client.go index e0d9e765..62262286 100644 --- a/adnl/rldp/http/client.go +++ b/adnl/rldp/http/client.go @@ -112,7 +112,7 @@ func (t *Transport) connectRLDP(ctx context.Context, key ed25519.PublicKey, addr } rCap := GetCapabilities{ - Capabilities: CapabilityRLDP2, + Capabilities: 0, } var caps Capabilities @@ -126,7 +126,7 @@ func (t *Transport) connectRLDP(ctx context.Context, key ed25519.PublicKey, addr switch query.Data.(type) { case GetCapabilities: ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - err := a.Answer(ctx, query.ID, &Capabilities{Value: CapabilityRLDP2}) + err := a.Answer(ctx, query.ID, &Capabilities{Value: 0}) cancel() if err != nil { return fmt.Errorf("failed to send capabilities answer: %w", err) @@ -284,7 +284,7 @@ func (t *Transport) RoundTrip(request *http.Request) (_ *http.Response, err erro req := Request{ ID: qid, Method: request.Method, - URL: request.URL.String(), + URL: request.URL.RequestURI(), Version: "HTTP/1.1", Headers: []Header{ { diff --git a/adnl/rldp/http/server.go b/adnl/rldp/http/server.go index 0ddaa080..ad187d2d 100644 --- a/adnl/rldp/http/server.go +++ b/adnl/rldp/http/server.go @@ -157,7 +157,7 @@ func (s *Server) ListenAndServe(listenAddr string) error { switch query.Data.(type) { case GetCapabilities: ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - err := client.Answer(ctx, query.ID, &Capabilities{Value: CapabilityRLDP2}) + err := client.Answer(ctx, query.ID, &Capabilities{Value: 0}) // CapabilityRLDP2 cancel() if err != nil { return fmt.Errorf("failed to send capabilities answer: %w", err) @@ -236,11 +236,15 @@ func (s *Server) handle(client RLDP, adnlId, addr string) func(transferId []byte if err != nil { return fmt.Errorf("failed to parse url `%s`: %w", uri, err) } + uri.Scheme = "http" contentLen := int64(-1) headers := http.Header{} for _, header := range req.Headers { - if header.Name == "Content-Length" { + name := http.CanonicalHeaderKey(header.Name) + if name == "Host" { + uri.Host = header.Value + } else if name == "Content-Length" { contentLen, err = strconv.ParseInt(header.Value, 10, 64) if err != nil { return fmt.Errorf("failed to parse content len `%s`: %w", header.Value, err) @@ -250,7 +254,8 @@ func (s *Server) handle(client RLDP, adnlId, addr string) func(transferId []byte return fmt.Errorf("failed to parse content len: should be >= 0") } } - headers[header.Name] = append(headers[header.Name], header.Value) + + headers[name] = append(headers[name], header.Value) } headers.Set("X-Adnl-Ip", netAddr.IP.String()) headers.Set("X-Adnl-Id", adnlId) From 22b0bfed2b31148c19e146471f58087c7328ec60 Mon Sep 17 00:00:00 2001 From: Dan Volkov Date: Wed, 31 Jul 2024 21:36:46 +0300 Subject: [PATCH 05/31] feat: add SendExternalMessageWaitTransaction api for APIClient --- ton/api.go | 1 + ton/sendmessagewait.go | 127 ++++++++++++++++++++++++++++++++++++++ ton/wallet/wallet_test.go | 43 +++++++------ 3 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 ton/sendmessagewait.go diff --git a/ton/api.go b/ton/api.go index 8ded6d8a..1e61d21a 100644 --- a/ton/api.go +++ b/ton/api.go @@ -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) diff --git a/ton/sendmessagewait.go b/ton/sendmessagewait.go new file mode 100644 index 00000000..5ba36fe4 --- /dev/null +++ b/ton/sendmessagewait.go @@ -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 +} diff --git a/ton/wallet/wallet_test.go b/ton/wallet/wallet_test.go index 47aca381..1d3cf3ac 100644 --- a/ton/wallet/wallet_test.go +++ b/ton/wallet/wallet_test.go @@ -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) { @@ -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) } From 68736d4f8b03000468f0d1e73ee9090f63537768 Mon Sep 17 00:00:00 2001 From: Nima Date: Thu, 1 Aug 2024 14:06:52 +0200 Subject: [PATCH 06/31] feat: implement BroadcastTransactionsAndWait to get tx and block from single transaction --- ton/wallet/integration_test.go | 20 ++++++++++++++++++++ ton/wallet/wallet.go | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/ton/wallet/integration_test.go b/ton/wallet/integration_test.go index 80ce3fab..1086d5bb 100644 --- a/ton/wallet/integration_test.go +++ b/ton/wallet/integration_test.go @@ -342,6 +342,26 @@ func TestWallet_TransferEncrypted(t *testing.T) { } } +func TestWallet_BroadcastTransactionsAndWait(t *testing.T) { + seed := strings.Split(_seed, " ") + ctx := api.Client().StickyContext(context.Background()) + + // init wallet + w, err := FromSeed(api, seed, HighloadV2R2) + if err != nil { + t.Fatal("FromSeed err:", err.Error()) + } + t.Logf("wallet address: %s", w.Address().String()) + + tx, block, err := w.BroadcastTransactionsAndWait(ctx, address.MustParseAddr("EQC9bWZd29foipyPOGWlVNVCQzpGAjvi1rGWF7EbNcSVClpA"), tlb.MustFromTON("0.005"), "Hello from tonutils-go!") + if err != nil { + t.Fatal("transfer err:", err) + } + + t.Logf("Transaction: %v", tx) + t.Logf("Block: %v", block) +} + func TestGetWalletVersion(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index ba30e287..e9336538 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -465,6 +465,32 @@ func (w *Wallet) SendWaitTransaction(ctx context.Context, message *Message) (*tl return w.SendManyWaitTransaction(ctx, []*Message{message}) } +// BroadcastTransactionsAndWait broadcasts the transaction and waits for confirmation, returning the transaction, block, and any errors. +func (w *Wallet) BroadcastTransactionsAndWait(ctx context.Context, addr *address.Address, amount tlb.Coins, comment string) (*tlb.Transaction, *ton.BlockIDExt, error) { + var body *cell.Cell + var err error + + if comment != "" { + body, err = CreateCommentCell(comment) + if err != nil { + return nil, nil, err + } + } + + msg := &Message{ + Mode: PayGasSeparately + IgnoreErrors, + InternalMessage: &tlb.InternalMessage{ + IHRDisabled: true, + Bounce: false, + DstAddr: addr, + Amount: amount, + Body: body, + }, + } + + return w.SendManyWaitTransaction(ctx, []*Message{msg}) +} + func (w *Wallet) sendMany(ctx context.Context, messages []*Message, waitConfirmation ...bool) (tx *tlb.Transaction, block *ton.BlockIDExt, inMsgHash []byte, err error) { block, err = w.api.CurrentMasterchainInfo(ctx) if err != nil { From af55f559e925bed7659277f166811178ce7ea7e0 Mon Sep 17 00:00:00 2001 From: Nima Date: Thu, 1 Aug 2024 14:21:57 +0200 Subject: [PATCH 07/31] fix: optional bounce --- ton/wallet/integration_test.go | 2 +- ton/wallet/wallet.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ton/wallet/integration_test.go b/ton/wallet/integration_test.go index 1086d5bb..33b05d29 100644 --- a/ton/wallet/integration_test.go +++ b/ton/wallet/integration_test.go @@ -353,7 +353,7 @@ func TestWallet_BroadcastTransactionsAndWait(t *testing.T) { } t.Logf("wallet address: %s", w.Address().String()) - tx, block, err := w.BroadcastTransactionsAndWait(ctx, address.MustParseAddr("EQC9bWZd29foipyPOGWlVNVCQzpGAjvi1rGWF7EbNcSVClpA"), tlb.MustFromTON("0.005"), "Hello from tonutils-go!") + tx, block, err := w.BroadcastTransactionsAndWait(ctx, address.MustParseAddr("EQC9bWZd29foipyPOGWlVNVCQzpGAjvi1rGWF7EbNcSVClpA"), tlb.MustFromTON("0.005"), true, "Hello from tonutils-go!") if err != nil { t.Fatal("transfer err:", err) } diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index e9336538..88eb4a8a 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -466,7 +466,7 @@ func (w *Wallet) SendWaitTransaction(ctx context.Context, message *Message) (*tl } // BroadcastTransactionsAndWait broadcasts the transaction and waits for confirmation, returning the transaction, block, and any errors. -func (w *Wallet) BroadcastTransactionsAndWait(ctx context.Context, addr *address.Address, amount tlb.Coins, comment string) (*tlb.Transaction, *ton.BlockIDExt, error) { +func (w *Wallet) BroadcastTransactionsAndWait(ctx context.Context, addr *address.Address, amount tlb.Coins, bounce bool, comment string) (*tlb.Transaction, *ton.BlockIDExt, error) { var body *cell.Cell var err error @@ -481,7 +481,7 @@ func (w *Wallet) BroadcastTransactionsAndWait(ctx context.Context, addr *address Mode: PayGasSeparately + IgnoreErrors, InternalMessage: &tlb.InternalMessage{ IHRDisabled: true, - Bounce: false, + Bounce: bounce, DstAddr: addr, Amount: amount, Body: body, From f879c4d91eb5d38078f560deea1be7b1522d9f77 Mon Sep 17 00:00:00 2001 From: Nima Date: Thu, 1 Aug 2024 14:33:38 +0200 Subject: [PATCH 08/31] fix: use IsBounceable for bounce --- ton/wallet/wallet.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index 88eb4a8a..2e101635 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -466,7 +466,7 @@ func (w *Wallet) SendWaitTransaction(ctx context.Context, message *Message) (*tl } // BroadcastTransactionsAndWait broadcasts the transaction and waits for confirmation, returning the transaction, block, and any errors. -func (w *Wallet) BroadcastTransactionsAndWait(ctx context.Context, addr *address.Address, amount tlb.Coins, bounce bool, comment string) (*tlb.Transaction, *ton.BlockIDExt, error) { +func (w *Wallet) BroadcastTransactionsAndWait(ctx context.Context, addr *address.Address, amount tlb.Coins, comment string) (*tlb.Transaction, *ton.BlockIDExt, error) { var body *cell.Cell var err error @@ -481,7 +481,7 @@ func (w *Wallet) BroadcastTransactionsAndWait(ctx context.Context, addr *address Mode: PayGasSeparately + IgnoreErrors, InternalMessage: &tlb.InternalMessage{ IHRDisabled: true, - Bounce: bounce, + Bounce: addr.IsBounceable(), DstAddr: addr, Amount: amount, Body: body, From 8cad801fb8b2852dfa026dca9dd46b6c162fdccb Mon Sep 17 00:00:00 2001 From: Nima Date: Thu, 1 Aug 2024 14:34:38 +0200 Subject: [PATCH 09/31] fix: use IsBounceable for bounce integration test --- ton/wallet/integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ton/wallet/integration_test.go b/ton/wallet/integration_test.go index 33b05d29..1086d5bb 100644 --- a/ton/wallet/integration_test.go +++ b/ton/wallet/integration_test.go @@ -353,7 +353,7 @@ func TestWallet_BroadcastTransactionsAndWait(t *testing.T) { } t.Logf("wallet address: %s", w.Address().String()) - tx, block, err := w.BroadcastTransactionsAndWait(ctx, address.MustParseAddr("EQC9bWZd29foipyPOGWlVNVCQzpGAjvi1rGWF7EbNcSVClpA"), tlb.MustFromTON("0.005"), true, "Hello from tonutils-go!") + tx, block, err := w.BroadcastTransactionsAndWait(ctx, address.MustParseAddr("EQC9bWZd29foipyPOGWlVNVCQzpGAjvi1rGWF7EbNcSVClpA"), tlb.MustFromTON("0.005"), "Hello from tonutils-go!") if err != nil { t.Fatal("transfer err:", err) } From 62e06ada922ea8b532dddcb6850b3078b604eaca Mon Sep 17 00:00:00 2001 From: Nima Date: Thu, 1 Aug 2024 14:38:14 +0200 Subject: [PATCH 10/31] fix: change BroadcastTransactionsAndWait to TransferWaitTransaction --- ton/wallet/integration_test.go | 4 ++-- ton/wallet/wallet.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ton/wallet/integration_test.go b/ton/wallet/integration_test.go index 1086d5bb..c46790be 100644 --- a/ton/wallet/integration_test.go +++ b/ton/wallet/integration_test.go @@ -342,7 +342,7 @@ func TestWallet_TransferEncrypted(t *testing.T) { } } -func TestWallet_BroadcastTransactionsAndWait(t *testing.T) { +func TestWallet_TransferWaitTransaction(t *testing.T) { seed := strings.Split(_seed, " ") ctx := api.Client().StickyContext(context.Background()) @@ -353,7 +353,7 @@ func TestWallet_BroadcastTransactionsAndWait(t *testing.T) { } t.Logf("wallet address: %s", w.Address().String()) - tx, block, err := w.BroadcastTransactionsAndWait(ctx, address.MustParseAddr("EQC9bWZd29foipyPOGWlVNVCQzpGAjvi1rGWF7EbNcSVClpA"), tlb.MustFromTON("0.005"), "Hello from tonutils-go!") + tx, block, err := w.TransferWaitTransaction(ctx, address.MustParseAddr("EQC9bWZd29foipyPOGWlVNVCQzpGAjvi1rGWF7EbNcSVClpA"), tlb.MustFromTON("0.005"), "Hello from tonutils-go!") if err != nil { t.Fatal("transfer err:", err) } diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index 2e101635..19e97733 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -465,8 +465,8 @@ func (w *Wallet) SendWaitTransaction(ctx context.Context, message *Message) (*tl return w.SendManyWaitTransaction(ctx, []*Message{message}) } -// BroadcastTransactionsAndWait broadcasts the transaction and waits for confirmation, returning the transaction, block, and any errors. -func (w *Wallet) BroadcastTransactionsAndWait(ctx context.Context, addr *address.Address, amount tlb.Coins, comment string) (*tlb.Transaction, *ton.BlockIDExt, error) { +// TransferWaitTransaction always waits for tx block confirmation and returns found tx. +func (w *Wallet) TransferWaitTransaction(ctx context.Context, addr *address.Address, amount tlb.Coins, comment string) (*tlb.Transaction, *ton.BlockIDExt, error) { var body *cell.Cell var err error From 2ab7cb9fab9926400632c0e0a2e5e4f63ee5a826 Mon Sep 17 00:00:00 2001 From: Nima Date: Thu, 1 Aug 2024 14:51:42 +0200 Subject: [PATCH 11/31] fix: use BuildTransfer to handle comment and message creation logic --- ton/wallet/wallet.go | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index 19e97733..51bb3e1b 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -466,29 +466,13 @@ func (w *Wallet) SendWaitTransaction(ctx context.Context, message *Message) (*tl } // TransferWaitTransaction always waits for tx block confirmation and returns found tx. -func (w *Wallet) TransferWaitTransaction(ctx context.Context, addr *address.Address, amount tlb.Coins, comment string) (*tlb.Transaction, *ton.BlockIDExt, error) { - var body *cell.Cell - var err error - - if comment != "" { - body, err = CreateCommentCell(comment) - if err != nil { - return nil, nil, err - } - } - - msg := &Message{ - Mode: PayGasSeparately + IgnoreErrors, - InternalMessage: &tlb.InternalMessage{ - IHRDisabled: true, - Bounce: addr.IsBounceable(), - DstAddr: addr, - Amount: amount, - Body: body, - }, +func (w *Wallet) TransferWaitTransaction(ctx context.Context, to *address.Address, amount tlb.Coins, comment string) (*tlb.Transaction, *ton.BlockIDExt, error) { + transfer, err := w.BuildTransfer(to, amount, to.IsBounceable(), comment) + if err != nil { + return nil, nil, err } - return w.SendManyWaitTransaction(ctx, []*Message{msg}) + return w.SendManyWaitTransaction(ctx, []*Message{transfer}) } func (w *Wallet) sendMany(ctx context.Context, messages []*Message, waitConfirmation ...bool) (tx *tlb.Transaction, block *ton.BlockIDExt, inMsgHash []byte, err error) { From e56ecd412c1dd4d2200a00cd21a6eeb5107537a6 Mon Sep 17 00:00:00 2001 From: LivingRoot Date: Wed, 14 Aug 2024 21:49:31 +0300 Subject: [PATCH 12/31] Added Compare and Decimals methods to coins --- tlb/coins.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tlb/coins.go b/tlb/coins.go index f7b5b1b4..110e74c4 100644 --- a/tlb/coins.go +++ b/tlb/coins.go @@ -222,3 +222,15 @@ func (g *Coins) UnmarshalJSON(data []byte) error { return nil } + +func Compare(coins1 *Coins, coins2 *Coins) int { + if coins1.decimals != coins2.decimals { + panic("invalid comparsion") + } + + return coins1.Nano().Cmp(coins2.Nano()) +} + +func Decimals(coins *Coins) int { + return coins.decimals +} From f7d939d34943a90531d049b6f17e5835fb7adce9 Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Fri, 23 Aug 2024 19:47:28 +0800 Subject: [PATCH 13/31] custom wallet feature Signed-off-by: s.klimov --- ton/wallet/address.go | 3 +- ton/wallet/custom.go | 15 ++++++ ton/wallet/custom_test.go | 93 ++++++++++++++++++++++++++++++++++ ton/wallet/integration_test.go | 26 ++++++++++ ton/wallet/wallet.go | 7 +++ ton/wallet/wallet_test.go | 2 +- 6 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 ton/wallet/custom.go create mode 100644 ton/wallet/custom_test.go diff --git a/ton/wallet/address.go b/ton/wallet/address.go index a3700618..1a7592e0 100644 --- a/ton/wallet/address.go +++ b/ton/wallet/address.go @@ -5,7 +5,6 @@ import ( "context" "crypto/ed25519" "fmt" - "github.com/xssnick/tonutils-go/address" "github.com/xssnick/tonutils-go/tlb" "github.com/xssnick/tonutils-go/tvm/cell" @@ -66,6 +65,8 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin ver = V5R1Beta case ConfigV5R1Final: ver = V5R1Final + case ConfigCustom: + return v.GetStateInit(pubKey, subWallet) } code, ok := walletCode[ver] diff --git a/ton/wallet/custom.go b/ton/wallet/custom.go new file mode 100644 index 00000000..3dd13d4c --- /dev/null +++ b/ton/wallet/custom.go @@ -0,0 +1,15 @@ +package wallet + +import ( + "crypto/ed25519" + "github.com/xssnick/tonutils-go/tlb" +) + +type ConfigCustom interface { + StateIniter + getSpec(w *Wallet) RegularBuilder +} + +type StateIniter interface { + GetStateInit(pubKey ed25519.PublicKey, subWallet uint32) (*tlb.StateInit, error) +} diff --git a/ton/wallet/custom_test.go b/ton/wallet/custom_test.go new file mode 100644 index 00000000..fc223d9f --- /dev/null +++ b/ton/wallet/custom_test.go @@ -0,0 +1,93 @@ +package wallet + +import ( + "crypto/ed25519" + "fmt" + "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/tvm/cell" + "testing" +) + +type configCustom struct { + code *cell.Cell + ConfigV5R1Final +} + +func newConfigCustom(code *cell.Cell) ConfigCustom { + return &configCustom{code: code, ConfigV5R1Final: ConfigV5R1Final{ + NetworkGlobalID: MainnetGlobalID, + }} +} + +func (c *configCustom) GetStateInit(pubKey ed25519.PublicKey, subWallet uint32) (*tlb.StateInit, error) { + walletId := V5R1ID{ + NetworkGlobalID: c.NetworkGlobalID, + WorkChain: c.Workchain, + SubwalletNumber: uint16(subWallet), + WalletVersion: 0, + } + + data := cell.BeginCell(). + MustStoreBoolBit(true). + MustStoreUInt(0, 32). + MustStoreUInt(uint64(walletId.Serialized()), 32). + MustStoreSlice(pubKey, 256). + MustStoreDict(nil). + EndCell() + + return &tlb.StateInit{ + Data: data, + Code: c.code, + }, nil +} + +func (c *configCustom) getSpec(w *Wallet) RegularBuilder { + return &SpecV5R1Final{ + SpecRegular: SpecRegular{ + wallet: w, + messagesTTL: 60 * 3, + }, + SpecSeqno: SpecSeqno{}, + config: c.ConfigV5R1Final, + } +} + +func ExampleConfigCustom() { + pkey := ed25519.NewKeyFromSeed([]byte("12345678901234567890123456789012")) + cfg := newConfigCustom(walletCode[V5R1Final]) + w, _ := FromPrivateKey(nil, pkey, cfg) + w, _ = w.GetSubwallet(1) + fmt.Println(w.WalletAddress()) + // Output: + // UQAFbC0rPzK6i2pFz3aDuCav8WO73bqM1bwdcZfF6p8ykBuF +} + +func TestConfigCustom(t *testing.T) { + pkey := ed25519.NewKeyFromSeed([]byte("12345678901234567890123456789012")) + cfg := newConfigCustom(walletCode[V5R1Final]) + wCustom, err := FromPrivateKey(nil, pkey, cfg) + if err != nil { + t.Fatalf("failed to get custom v5r1 wallet from pk, err: %s", err) + } + + wCustomSub, err := wCustom.GetSubwallet(1) + if err != nil { + t.Fatalf("failed to get custom sub v5r1 wallet, err: %s", err) + } + + wOrig, err := FromPrivateKey(nil, pkey, ConfigV5R1Final{ + NetworkGlobalID: MainnetGlobalID, + }) + if err != nil { + t.Fatalf("failed to get orig v5r1 wallet from pk, err: %s", err) + } + + wOrigSub, err := wOrig.GetSubwallet(1) + if err != nil { + t.Fatalf("failed to get orig sub v5r1 wallet, err: %s", err) + } + + if !wCustomSub.WalletAddress().Equals(wOrigSub.WalletAddress()) { + t.Fatalf("orig and custom v5r1 wallet addresses mismatch") + } +} diff --git a/ton/wallet/integration_test.go b/ton/wallet/integration_test.go index 80ce3fab..3be561e9 100644 --- a/ton/wallet/integration_test.go +++ b/ton/wallet/integration_test.go @@ -1,3 +1,6 @@ +//go:build integration +// +build integration + package wallet import ( @@ -21,6 +24,8 @@ import ( "github.com/xssnick/tonutils-go/tvm/cell" ) +const emptyWalletSeedEnvFatalMsg = "WALLET_SEED not found in environment" + var api = func() ton.APIClientWrapped { client := liteclient.NewConnectionPool() @@ -52,6 +57,9 @@ var apiMain = func() ton.APIClientWrapped { var _seed = os.Getenv("WALLET_SEED") func Test_HighloadHeavyTransfer(t *testing.T) { + if _seed == "" { + t.Fatal(emptyWalletSeedEnvFatalMsg) + } seed := strings.Split(_seed, " ") w, err := FromSeed(api, seed, ConfigHighloadV3{ @@ -84,6 +92,9 @@ func Test_HighloadHeavyTransfer(t *testing.T) { } func Test_V5HeavyTransfer(t *testing.T) { + if _seed == "" { + t.Fatal(emptyWalletSeedEnvFatalMsg) + } seed := strings.Split(_seed, " ") w, err := FromSeed(api, seed, ConfigV5R1Final{ @@ -112,6 +123,9 @@ func Test_V5HeavyTransfer(t *testing.T) { } func Test_WalletTransfer(t *testing.T) { + if _seed == "" { + t.Fatalf(emptyWalletSeedEnvFatalMsg) + } seed := strings.Split(_seed, " ") for _, v := range []VersionConfig{ConfigV5R1Final{ @@ -192,6 +206,9 @@ func Test_WalletTransfer(t *testing.T) { } func Test_WalletFindTransactionByInMsgHash(t *testing.T) { + if _seed == "" { + t.Fatal(emptyWalletSeedEnvFatalMsg) + } seed := strings.Split(_seed, " ") ctx := api.Client().StickyContext(context.Background()) @@ -240,6 +257,9 @@ func Test_WalletFindTransactionByInMsgHash(t *testing.T) { } func TestWallet_DeployContract(t *testing.T) { + if _seed == "" { + t.Fatal(emptyWalletSeedEnvFatalMsg) + } seed := strings.Split(_seed, " ") ctx := api.Client().StickyContext(context.Background()) @@ -280,6 +300,9 @@ func TestWallet_DeployContract(t *testing.T) { } func TestWallet_DeployContractUsingHW3(t *testing.T) { + if _seed == "" { + t.Fatal(emptyWalletSeedEnvFatalMsg) + } seed := strings.Split(_seed, " ") ctx := api.Client().StickyContext(context.Background()) @@ -326,6 +349,9 @@ func TestWallet_DeployContractUsingHW3(t *testing.T) { } func TestWallet_TransferEncrypted(t *testing.T) { + if _seed == "" { + t.Skip() + } seed := strings.Split(_seed, " ") ctx := api.Client().StickyContext(context.Background()) diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index d01c4f66..86e3b2fa 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -248,6 +248,8 @@ func getSpec(w *Wallet) (any, error) { } case ConfigHighloadV3: return &SpecHighloadV3{wallet: w, config: v}, nil + case ConfigCustom: + return v.getSpec(w), nil } return nil, fmt.Errorf("cannot init spec: %w", ErrUnsupportedWalletVersion) @@ -374,6 +376,11 @@ func (w *Wallet) PrepareExternalMessageForMany(ctx context.Context, withStateIni if err != nil { return nil, fmt.Errorf("build message err: %w", err) } + case ConfigCustom: + msg, err = v.getSpec(w).BuildMessage(ctx, !withStateInit, nil, messages) + if err != nil { + return nil, fmt.Errorf("build message err: %w", err) + } default: return nil, fmt.Errorf("send is not yet supported: %w", ErrUnsupportedWalletVersion) } diff --git a/ton/wallet/wallet_test.go b/ton/wallet/wallet_test.go index 47aca381..d7272be4 100644 --- a/ton/wallet/wallet_test.go +++ b/ton/wallet/wallet_test.go @@ -602,7 +602,7 @@ func TestCreateEncryptedCommentCell(t *testing.T) { return } - msg := randString(150 + i) + msg := "Hello, world!!!" sender := address.MustParseAddr("EQC9bWZd29foipyPOGWlVNVCQzpGAjvi1rGWF7EbNcSVClpA") c, err := CreateEncryptedCommentCell(msg, sender, priv1, pub2) From 013e073646b7f759d8b0206447362f8e4a55c0e5 Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Fri, 23 Aug 2024 20:23:32 +0800 Subject: [PATCH 14/31] custom wallet feature Signed-off-by: s.klimov --- ton/wallet/custom_test.go | 127 ++++++++++++++++++++++++++++++++------ ton/wallet/wallet.go | 2 +- 2 files changed, 110 insertions(+), 19 deletions(-) diff --git a/ton/wallet/custom_test.go b/ton/wallet/custom_test.go index fc223d9f..d4447af6 100644 --- a/ton/wallet/custom_test.go +++ b/ton/wallet/custom_test.go @@ -1,25 +1,28 @@ package wallet import ( + "context" "crypto/ed25519" + "encoding/hex" "fmt" "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/ton" "github.com/xssnick/tonutils-go/tvm/cell" "testing" ) -type configCustom struct { +type configCustomV5R1 struct { code *cell.Cell ConfigV5R1Final } -func newConfigCustom(code *cell.Cell) ConfigCustom { - return &configCustom{code: code, ConfigV5R1Final: ConfigV5R1Final{ +func newConfigCustomV5R1(code *cell.Cell) ConfigCustom { + return &configCustomV5R1{code: code, ConfigV5R1Final: ConfigV5R1Final{ NetworkGlobalID: MainnetGlobalID, }} } -func (c *configCustom) GetStateInit(pubKey ed25519.PublicKey, subWallet uint32) (*tlb.StateInit, error) { +func (c *configCustomV5R1) GetStateInit(pubKey ed25519.PublicKey, subWallet uint32) (*tlb.StateInit, error) { walletId := V5R1ID{ NetworkGlobalID: c.NetworkGlobalID, WorkChain: c.Workchain, @@ -41,30 +44,20 @@ func (c *configCustom) GetStateInit(pubKey ed25519.PublicKey, subWallet uint32) }, nil } -func (c *configCustom) getSpec(w *Wallet) RegularBuilder { +func (c *configCustomV5R1) getSpec(w *Wallet) RegularBuilder { return &SpecV5R1Final{ SpecRegular: SpecRegular{ wallet: w, messagesTTL: 60 * 3, }, - SpecSeqno: SpecSeqno{}, + SpecSeqno: SpecSeqno{seqnoFetcher: nil}, config: c.ConfigV5R1Final, } } -func ExampleConfigCustom() { +func TestConfigCustom_CmpV5SubWalletAddress(t *testing.T) { pkey := ed25519.NewKeyFromSeed([]byte("12345678901234567890123456789012")) - cfg := newConfigCustom(walletCode[V5R1Final]) - w, _ := FromPrivateKey(nil, pkey, cfg) - w, _ = w.GetSubwallet(1) - fmt.Println(w.WalletAddress()) - // Output: - // UQAFbC0rPzK6i2pFz3aDuCav8WO73bqM1bwdcZfF6p8ykBuF -} - -func TestConfigCustom(t *testing.T) { - pkey := ed25519.NewKeyFromSeed([]byte("12345678901234567890123456789012")) - cfg := newConfigCustom(walletCode[V5R1Final]) + cfg := newConfigCustomV5R1(walletCode[V5R1Final]) wCustom, err := FromPrivateKey(nil, pkey, cfg) if err != nil { t.Fatalf("failed to get custom v5r1 wallet from pk, err: %s", err) @@ -91,3 +84,101 @@ func TestConfigCustom(t *testing.T) { t.Fatalf("orig and custom v5r1 wallet addresses mismatch") } } + +type configCustomHighloadV3 struct { + code *cell.Cell + ConfigHighloadV3 +} + +func newConfigCustomHighloadV3(code *cell.Cell) ConfigCustom { + return &configCustomHighloadV3{code: code, ConfigHighloadV3: ConfigHighloadV3{ + MessageTTL: 60, + MessageBuilder: func(ctx context.Context, subWalletId uint32) (id uint32, createdAt int64, err error) { + return 1, 1733333333, nil + }, + }} +} + +func (c *configCustomHighloadV3) GetStateInit(pubKey ed25519.PublicKey, subWallet uint32) (*tlb.StateInit, error) { + timeout := c.MessageTTL + if timeout >= 1<<22 { + return nil, fmt.Errorf("too big timeout") + } + + data := cell.BeginCell(). + MustStoreSlice(pubKey, 256). + MustStoreUInt(uint64(subWallet), 32). + MustStoreUInt(0, 66). + MustStoreUInt(uint64(timeout), 22). + EndCell() + + return &tlb.StateInit{ + Data: data, + Code: c.code, + }, nil +} + +type specCustomHighloadV3 struct { + SpecHighloadV3 +} + +func (s *specCustomHighloadV3) BuildMessage(ctx context.Context, isInitialized bool, _ *ton.BlockIDExt, messages []*Message) (*cell.Cell, error) { + return s.SpecHighloadV3.BuildMessage(ctx, messages) +} + +func (c *configCustomHighloadV3) getSpec(w *Wallet) RegularBuilder { + return &specCustomHighloadV3{SpecHighloadV3: SpecHighloadV3{ + wallet: w, + config: ConfigHighloadV3{ + MessageTTL: 60, + MessageBuilder: func(ctx context.Context, subWalletId uint32) (id uint32, createdAt int64, err error) { + return 1, 1733333333, nil + }, + }, + }} +} + +func TestConfigCustom_V3BocTx(t *testing.T) { + pkey := ed25519.NewKeyFromSeed([]byte("12345678901234567890123456789012")) + cfg := newConfigCustomHighloadV3(walletCode[HighloadV3]) + wCustom, err := FromPrivateKey(nil, pkey, cfg) + if err != nil { + t.Fatalf("failed to get custom HL3 wallet from pk, err: %s", err) + } + + wCustomSub, err := wCustom.GetSubwallet(1) + if err != nil { + t.Fatalf("failed to get custom sub HL3 wallet, err: %s", err) + } + + wCustomSubExtMgs, _ := wCustomSub.PrepareExternalMessageForMany(context.Background(), false, []*Message{SimpleMessage(wCustomSub.WalletAddress(), tlb.MustFromTON("0.5"), nil)}) + wCustomSubExtMgsCell, _ := tlb.ToCell(wCustomSubExtMgs) + wCustomSubExtMgsBocHex := hex.EncodeToString(wCustomSubExtMgsCell.ToBOCWithFlags(false)) + + wOrig, err := FromPrivateKey(nil, pkey, ConfigHighloadV3{ + MessageTTL: 60, + MessageBuilder: func(ctx context.Context, subWalletId uint32) (id uint32, createdAt int64, err error) { + return 1, 1733333333, nil + }, + }) + if err != nil { + t.Fatalf("failed to get orig HL3 wallet from pk, err: %s", err) + } + + wOrigSub, err := wOrig.GetSubwallet(1) + if err != nil { + t.Fatalf("failed to get orig sub HL3 wallet, err: %s", err) + } + + wOrigSubSubExtMgs, _ := wCustomSub.PrepareExternalMessageForMany(context.Background(), false, []*Message{SimpleMessage(wOrigSub.WalletAddress(), tlb.MustFromTON("0.5"), nil)}) + wOrigSubSubExtMgsCell, _ := tlb.ToCell(wOrigSubSubExtMgs) + wOrigSubSubExtMgsBocHex := hex.EncodeToString(wOrigSubSubExtMgsCell.ToBOCWithFlags(false)) + + if !wCustomSub.WalletAddress().Equals(wOrigSub.WalletAddress()) { + t.Fatalf("orig and custom v5r1 wallet addresses mismatch") + } + + if wCustomSubExtMgsBocHex != wOrigSubSubExtMgsBocHex { + t.Fatalf("orig and custom ext boc msg mismatch") + } +} diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index 86e3b2fa..61db79a1 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -377,7 +377,7 @@ func (w *Wallet) PrepareExternalMessageForMany(ctx context.Context, withStateIni return nil, fmt.Errorf("build message err: %w", err) } case ConfigCustom: - msg, err = v.getSpec(w).BuildMessage(ctx, !withStateInit, nil, messages) + msg, err = w.spec.(RegularBuilder).BuildMessage(ctx, !withStateInit, nil, messages) if err != nil { return nil, fmt.Errorf("build message err: %w", err) } From 8f80b88d9fae8b331bba36d74a28cff02da2f788 Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Fri, 23 Aug 2024 20:24:35 +0800 Subject: [PATCH 15/31] typo Signed-off-by: s.klimov --- ton/wallet/custom_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ton/wallet/custom_test.go b/ton/wallet/custom_test.go index d4447af6..1a5b1dd7 100644 --- a/ton/wallet/custom_test.go +++ b/ton/wallet/custom_test.go @@ -170,15 +170,15 @@ func TestConfigCustom_V3BocTx(t *testing.T) { t.Fatalf("failed to get orig sub HL3 wallet, err: %s", err) } - wOrigSubSubExtMgs, _ := wCustomSub.PrepareExternalMessageForMany(context.Background(), false, []*Message{SimpleMessage(wOrigSub.WalletAddress(), tlb.MustFromTON("0.5"), nil)}) - wOrigSubSubExtMgsCell, _ := tlb.ToCell(wOrigSubSubExtMgs) - wOrigSubSubExtMgsBocHex := hex.EncodeToString(wOrigSubSubExtMgsCell.ToBOCWithFlags(false)) + wOrigSubExtMgs, _ := wCustomSub.PrepareExternalMessageForMany(context.Background(), false, []*Message{SimpleMessage(wOrigSub.WalletAddress(), tlb.MustFromTON("0.5"), nil)}) + wOrigSubExtMgsCell, _ := tlb.ToCell(wOrigSubExtMgs) + wOrigSubExtMgsBocHex := hex.EncodeToString(wOrigSubExtMgsCell.ToBOCWithFlags(false)) if !wCustomSub.WalletAddress().Equals(wOrigSub.WalletAddress()) { t.Fatalf("orig and custom v5r1 wallet addresses mismatch") } - if wCustomSubExtMgsBocHex != wOrigSubSubExtMgsBocHex { + if wCustomSubExtMgsBocHex != wOrigSubExtMgsBocHex { t.Fatalf("orig and custom ext boc msg mismatch") } } From 446a5105a64a28063fed40695c43709de493d502 Mon Sep 17 00:00:00 2001 From: Klimov Sergey Date: Fri, 23 Aug 2024 20:27:17 +0800 Subject: [PATCH 16/31] Update ton/wallet/integration_test.go Co-authored-by: Sergey Klimov <118167768+klimov-ankr@users.noreply.github.com> --- ton/wallet/integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ton/wallet/integration_test.go b/ton/wallet/integration_test.go index 3be561e9..05427b91 100644 --- a/ton/wallet/integration_test.go +++ b/ton/wallet/integration_test.go @@ -350,7 +350,7 @@ func TestWallet_DeployContractUsingHW3(t *testing.T) { func TestWallet_TransferEncrypted(t *testing.T) { if _seed == "" { - t.Skip() + t.Fatal(emptyWalletSeedEnvFatalMsg) } seed := strings.Split(_seed, " ") ctx := api.Client().StickyContext(context.Background()) From 7b9e5e2537847cd30e4b711c984ffa4611479624 Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Fri, 23 Aug 2024 20:31:31 +0800 Subject: [PATCH 17/31] typo Signed-off-by: s.klimov --- ton/wallet/custom_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ton/wallet/custom_test.go b/ton/wallet/custom_test.go index 1a5b1dd7..e3a1ba3e 100644 --- a/ton/wallet/custom_test.go +++ b/ton/wallet/custom_test.go @@ -81,7 +81,7 @@ func TestConfigCustom_CmpV5SubWalletAddress(t *testing.T) { } if !wCustomSub.WalletAddress().Equals(wOrigSub.WalletAddress()) { - t.Fatalf("orig and custom v5r1 wallet addresses mismatch") + t.Error("orig and custom v5r1 wallet addresse mismatch") } } @@ -175,10 +175,10 @@ func TestConfigCustom_V3BocTx(t *testing.T) { wOrigSubExtMgsBocHex := hex.EncodeToString(wOrigSubExtMgsCell.ToBOCWithFlags(false)) if !wCustomSub.WalletAddress().Equals(wOrigSub.WalletAddress()) { - t.Fatalf("orig and custom v5r1 wallet addresses mismatch") + t.Error("orig and custom v5r1 wallet addresse mismatch") } if wCustomSubExtMgsBocHex != wOrigSubExtMgsBocHex { - t.Fatalf("orig and custom ext boc msg mismatch") + t.Error("orig and custom ext boc msg mismatch") } } From 8d8b65b90d0d28d6bcdea43fd9ae551f45a64d7e Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Fri, 23 Aug 2024 21:23:28 +0800 Subject: [PATCH 18/31] GetSpec as exported Signed-off-by: s.klimov --- ton/wallet/custom.go | 2 +- ton/wallet/custom_test.go | 4 ++-- ton/wallet/wallet.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ton/wallet/custom.go b/ton/wallet/custom.go index 3dd13d4c..8a43a295 100644 --- a/ton/wallet/custom.go +++ b/ton/wallet/custom.go @@ -7,7 +7,7 @@ import ( type ConfigCustom interface { StateIniter - getSpec(w *Wallet) RegularBuilder + GetSpec(w *Wallet) RegularBuilder } type StateIniter interface { diff --git a/ton/wallet/custom_test.go b/ton/wallet/custom_test.go index e3a1ba3e..f01669b8 100644 --- a/ton/wallet/custom_test.go +++ b/ton/wallet/custom_test.go @@ -44,7 +44,7 @@ func (c *configCustomV5R1) GetStateInit(pubKey ed25519.PublicKey, subWallet uint }, nil } -func (c *configCustomV5R1) getSpec(w *Wallet) RegularBuilder { +func (c *configCustomV5R1) GetSpec(w *Wallet) RegularBuilder { return &SpecV5R1Final{ SpecRegular: SpecRegular{ wallet: w, @@ -126,7 +126,7 @@ func (s *specCustomHighloadV3) BuildMessage(ctx context.Context, isInitialized b return s.SpecHighloadV3.BuildMessage(ctx, messages) } -func (c *configCustomHighloadV3) getSpec(w *Wallet) RegularBuilder { +func (c *configCustomHighloadV3) GetSpec(w *Wallet) RegularBuilder { return &specCustomHighloadV3{SpecHighloadV3: SpecHighloadV3{ wallet: w, config: ConfigHighloadV3{ diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index 61db79a1..57b246eb 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -249,7 +249,7 @@ func getSpec(w *Wallet) (any, error) { case ConfigHighloadV3: return &SpecHighloadV3{wallet: w, config: v}, nil case ConfigCustom: - return v.getSpec(w), nil + return v.GetSpec(w), nil } return nil, fmt.Errorf("cannot init spec: %w", ErrUnsupportedWalletVersion) From 74da662a17f3817ab4b993021396090b094a9592 Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Fri, 23 Aug 2024 21:30:58 +0800 Subject: [PATCH 19/31] add GetSubwalletID() Signed-off-by: s.klimov --- ton/wallet/wallet.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index 57b246eb..c89cc382 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -293,6 +293,10 @@ func (w *Wallet) GetSubwallet(subwallet uint32) (*Wallet, error) { return sub, nil } +func (w *Wallet) GetSubwalletID() uint32 { + return w.subwallet +} + func (w *Wallet) GetBalance(ctx context.Context, block *ton.BlockIDExt) (tlb.Coins, error) { acc, err := w.api.WaitForBlock(block.SeqNo).GetAccount(ctx, block, w.addr) if err != nil { From 6092b38e9013e5ed55c943b6a289bb6d0da857e7 Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Fri, 23 Aug 2024 21:51:34 +0800 Subject: [PATCH 20/31] add Custom version constant Signed-off-by: s.klimov --- ton/wallet/wallet.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index c89cc382..1ee513bc 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -48,6 +48,7 @@ const ( HighloadV3 Version = 300 Lockup Version = 200 Unknown Version = 0 + Custom Version = 1 ) const ( From 0c62ebbcd957f78d066efbddfa921aff01fd6e60 Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Fri, 23 Aug 2024 21:52:05 +0800 Subject: [PATCH 21/31] remove Custom version constant Signed-off-by: s.klimov --- ton/wallet/wallet.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index 1ee513bc..c89cc382 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -48,7 +48,6 @@ const ( HighloadV3 Version = 300 Lockup Version = 200 Unknown Version = 0 - Custom Version = 1 ) const ( From 645c325bf119c861f4ff10ec64b6b7bac9725e3e Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Sat, 24 Aug 2024 01:29:50 +0800 Subject: [PATCH 22/31] remove StateIniter Signed-off-by: s.klimov --- ton/wallet/custom.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ton/wallet/custom.go b/ton/wallet/custom.go index 8a43a295..a81cf179 100644 --- a/ton/wallet/custom.go +++ b/ton/wallet/custom.go @@ -6,10 +6,6 @@ import ( ) type ConfigCustom interface { - StateIniter - GetSpec(w *Wallet) RegularBuilder -} - -type StateIniter interface { GetStateInit(pubKey ed25519.PublicKey, subWallet uint32) (*tlb.StateInit, error) + GetSpec(w *Wallet) RegularBuilder } From 9f6988d6baf19a1647a9e96b673c2f3da8a2672b Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Sat, 24 Aug 2024 01:41:30 +0800 Subject: [PATCH 23/31] typo Signed-off-by: s.klimov --- ton/wallet/custom_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ton/wallet/custom_test.go b/ton/wallet/custom_test.go index f01669b8..d2b0324d 100644 --- a/ton/wallet/custom_test.go +++ b/ton/wallet/custom_test.go @@ -81,7 +81,7 @@ func TestConfigCustom_CmpV5SubWalletAddress(t *testing.T) { } if !wCustomSub.WalletAddress().Equals(wOrigSub.WalletAddress()) { - t.Error("orig and custom v5r1 wallet addresse mismatch") + t.Error("orig and custom v5r1 wallet address mismatch") } } @@ -175,7 +175,7 @@ func TestConfigCustom_V3BocTx(t *testing.T) { wOrigSubExtMgsBocHex := hex.EncodeToString(wOrigSubExtMgsCell.ToBOCWithFlags(false)) if !wCustomSub.WalletAddress().Equals(wOrigSub.WalletAddress()) { - t.Error("orig and custom v5r1 wallet addresse mismatch") + t.Error("orig and custom HL3 wallet address mismatch") } if wCustomSubExtMgsBocHex != wOrigSubExtMgsBocHex { From f1278479ec27ea61b985f5a1f18a30d98a5bc952 Mon Sep 17 00:00:00 2001 From: "s.klimov" Date: Wed, 28 Aug 2024 05:21:48 +0800 Subject: [PATCH 24/31] replace RegularBuilder to MessageBuilder Signed-off-by: s.klimov --- ton/wallet/custom.go | 8 +++++- ton/wallet/custom_test.go | 55 ++++++++++++++++----------------------- ton/wallet/wallet.go | 2 +- 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/ton/wallet/custom.go b/ton/wallet/custom.go index a81cf179..679bc285 100644 --- a/ton/wallet/custom.go +++ b/ton/wallet/custom.go @@ -1,11 +1,17 @@ package wallet import ( + "context" "crypto/ed25519" "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/tvm/cell" ) type ConfigCustom interface { GetStateInit(pubKey ed25519.PublicKey, subWallet uint32) (*tlb.StateInit, error) - GetSpec(w *Wallet) RegularBuilder + GetSpec(w *Wallet) MessageBuilder +} + +type MessageBuilder interface { + BuildMessage(ctx context.Context, messages []*Message) (*cell.Cell, error) } diff --git a/ton/wallet/custom_test.go b/ton/wallet/custom_test.go index d2b0324d..192dfe76 100644 --- a/ton/wallet/custom_test.go +++ b/ton/wallet/custom_test.go @@ -6,7 +6,6 @@ import ( "encoding/hex" "fmt" "github.com/xssnick/tonutils-go/tlb" - "github.com/xssnick/tonutils-go/ton" "github.com/xssnick/tonutils-go/tvm/cell" "testing" ) @@ -22,6 +21,25 @@ func newConfigCustomV5R1(code *cell.Cell) ConfigCustom { }} } +type customSpecV5R1 struct { + SpecV5R1Final +} + +func (c *customSpecV5R1) BuildMessage(ctx context.Context, messages []*Message) (*cell.Cell, error) { + return c.SpecV5R1Final.BuildMessage(ctx, false, nil, messages) +} + +func (c *configCustomV5R1) GetSpec(w *Wallet) MessageBuilder { + return &customSpecV5R1{SpecV5R1Final: SpecV5R1Final{ + SpecRegular: SpecRegular{ + wallet: w, + messagesTTL: 60 * 3, + }, + SpecSeqno: SpecSeqno{seqnoFetcher: nil}, + config: c.ConfigV5R1Final, + }} +} + func (c *configCustomV5R1) GetStateInit(pubKey ed25519.PublicKey, subWallet uint32) (*tlb.StateInit, error) { walletId := V5R1ID{ NetworkGlobalID: c.NetworkGlobalID, @@ -44,17 +62,6 @@ func (c *configCustomV5R1) GetStateInit(pubKey ed25519.PublicKey, subWallet uint }, nil } -func (c *configCustomV5R1) GetSpec(w *Wallet) RegularBuilder { - return &SpecV5R1Final{ - SpecRegular: SpecRegular{ - wallet: w, - messagesTTL: 60 * 3, - }, - SpecSeqno: SpecSeqno{seqnoFetcher: nil}, - config: c.ConfigV5R1Final, - } -} - func TestConfigCustom_CmpV5SubWalletAddress(t *testing.T) { pkey := ed25519.NewKeyFromSeed([]byte("12345678901234567890123456789012")) cfg := newConfigCustomV5R1(walletCode[V5R1Final]) @@ -99,6 +106,10 @@ func newConfigCustomHighloadV3(code *cell.Cell) ConfigCustom { }} } +func (c *configCustomHighloadV3) GetSpec(w *Wallet) MessageBuilder { + return &SpecHighloadV3{wallet: w, config: c.ConfigHighloadV3} +} + func (c *configCustomHighloadV3) GetStateInit(pubKey ed25519.PublicKey, subWallet uint32) (*tlb.StateInit, error) { timeout := c.MessageTTL if timeout >= 1<<22 { @@ -118,26 +129,6 @@ func (c *configCustomHighloadV3) GetStateInit(pubKey ed25519.PublicKey, subWalle }, nil } -type specCustomHighloadV3 struct { - SpecHighloadV3 -} - -func (s *specCustomHighloadV3) BuildMessage(ctx context.Context, isInitialized bool, _ *ton.BlockIDExt, messages []*Message) (*cell.Cell, error) { - return s.SpecHighloadV3.BuildMessage(ctx, messages) -} - -func (c *configCustomHighloadV3) GetSpec(w *Wallet) RegularBuilder { - return &specCustomHighloadV3{SpecHighloadV3: SpecHighloadV3{ - wallet: w, - config: ConfigHighloadV3{ - MessageTTL: 60, - MessageBuilder: func(ctx context.Context, subWalletId uint32) (id uint32, createdAt int64, err error) { - return 1, 1733333333, nil - }, - }, - }} -} - func TestConfigCustom_V3BocTx(t *testing.T) { pkey := ed25519.NewKeyFromSeed([]byte("12345678901234567890123456789012")) cfg := newConfigCustomHighloadV3(walletCode[HighloadV3]) diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index c89cc382..26b897d0 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -381,7 +381,7 @@ func (w *Wallet) PrepareExternalMessageForMany(ctx context.Context, withStateIni return nil, fmt.Errorf("build message err: %w", err) } case ConfigCustom: - msg, err = w.spec.(RegularBuilder).BuildMessage(ctx, !withStateInit, nil, messages) + msg, err = w.spec.(MessageBuilder).BuildMessage(ctx, messages) if err != nil { return nil, fmt.Errorf("build message err: %w", err) } From 74a5c9a40c2a772a8bee665b0319cb75aabaf8b9 Mon Sep 17 00:00:00 2001 From: LivingRoot Date: Fri, 30 Aug 2024 13:20:31 +0300 Subject: [PATCH 25/31] changed compare and decimals method for --- tlb/coins.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tlb/coins.go b/tlb/coins.go index 110e74c4..3eb0fa48 100644 --- a/tlb/coins.go +++ b/tlb/coins.go @@ -223,14 +223,14 @@ func (g *Coins) UnmarshalJSON(data []byte) error { return nil } -func Compare(coins1 *Coins, coins2 *Coins) int { - if coins1.decimals != coins2.decimals { +func (g *Coins) Compare(coins *Coins) int { + if g.decimals != coins.decimals { panic("invalid comparsion") } - return coins1.Nano().Cmp(coins2.Nano()) + return g.Nano().Cmp(coins.Nano()) } -func Decimals(coins *Coins) int { - return coins.decimals +func (g *Coins) Decimals() int { + return g.decimals } From 41c6626be86eb9fdb0969992d695d9f3c0e3c130 Mon Sep 17 00:00:00 2001 From: Oleg Baranov Date: Tue, 10 Sep 2024 12:51:27 +0400 Subject: [PATCH 26/31] Attached processing fees for HW3 + Fixed Retrier + Deprecated attr fields for onchain content --- example/nft-info/main.go | 4 ++-- ton/nft/content.go | 12 ++++++++---- ton/retrier.go | 4 ++-- ton/wallet/highloadv3.go | 4 ++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/example/nft-info/main.go b/example/nft-info/main.go index ecef810c..5ff3254c 100644 --- a/example/nft-info/main.go +++ b/example/nft-info/main.go @@ -44,7 +44,7 @@ func main() { case *nft.ContentOffchain: fmt.Println(" content offchain :", content.URI) case *nft.ContentOnchain: - fmt.Println(" content onchain :", content.Name) + fmt.Println(" content onchain :", content.GetAttribute("name")) } fmt.Println(" owner :", collectionData.OwnerAddress.String()) fmt.Println(" minted items num :", collectionData.NextItemIndex) @@ -69,7 +69,7 @@ func main() { fmt.Println(" full content :", nftContent.(*nft.ContentOffchain).URI) } case *nft.ContentOnchain: - fmt.Println(" content name :", content.Name) + fmt.Println(" content name :", content.GetAttribute("name")) } } else { fmt.Println(" empty content") diff --git a/ton/nft/content.go b/ton/nft/content.go index a325a09e..ca13e30b 100644 --- a/ton/nft/content.go +++ b/ton/nft/content.go @@ -16,11 +16,15 @@ type ContentOffchain struct { } type ContentOnchain struct { - Name string + // Deprecated: use GetAttribute("name") + Name string + // Deprecated: use GetAttribute("description") Description string - Image string - ImageData []byte - attributes *cell.Dictionary + // Deprecated: use GetAttribute("image") + Image string + // Deprecated: use GetAttributeBinary("image_data") + ImageData []byte + attributes *cell.Dictionary } type ContentSemichain struct { diff --git a/ton/retrier.go b/ton/retrier.go index 70e17be5..752f12fa 100644 --- a/ton/retrier.go +++ b/ton/retrier.go @@ -16,10 +16,10 @@ type retryClient struct { } func (w *retryClient) QueryLiteserver(ctx context.Context, payload tl.Serializable, result tl.Serializable) error { - tries := w.maxRetries + tries := 0 for { err := w.original.QueryLiteserver(ctx, payload, result) - if w.maxRetries > 0 && tries == w.maxRetries { + if w.maxRetries > 0 && tries >= w.maxRetries { return err } tries++ diff --git a/ton/wallet/highloadv3.go b/ton/wallet/highloadv3.go index e158de38..821bf034 100644 --- a/ton/wallet/highloadv3.go +++ b/ton/wallet/highloadv3.go @@ -123,6 +123,10 @@ func (s *SpecHighloadV3) packActions(queryId uint64, messages []*Message) (_ *Me list = cell.BeginCell().MustStoreRef(list).MustStoreBuilder(msg).EndCell() } + // attach some coins for internal message processing gas fees + fees := new(big.Int).Add(new(big.Int).Mul(tlb.MustFromTON("0.007").Nano(), big.NewInt(int64(len(messages)))), tlb.MustFromTON("0.01").Nano()) + amt = new(big.Int).Add(amt, fees) + return &Message{ Mode: PayGasSeparately + IgnoreErrors, InternalMessage: &tlb.InternalMessage{ From 4b5230185dedf167f57a9f382c5ba18dfa638875 Mon Sep 17 00:00:00 2001 From: Oleg Baranov Date: Wed, 11 Sep 2024 12:57:05 +0400 Subject: [PATCH 27/31] Fixed proof check for GetAccount when shard block passed --- ton/getstate.go | 3 ++- ton/proof.go | 2 +- ton/runmethod.go | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ton/getstate.go b/ton/getstate.go index 17fadc23..b4d5edc6 100644 --- a/ton/getstate.go +++ b/ton/getstate.go @@ -73,7 +73,8 @@ func (c *APIClient) GetAccount(ctx context.Context, block *BlockIDExt, addr *add } var shardHash []byte - if c.proofCheckPolicy != ProofCheckPolicyUnsafe && addr.Workchain() != address.MasterchainID { + if c.proofCheckPolicy != ProofCheckPolicyUnsafe && addr.Workchain() != address.MasterchainID && + block.Workchain == address.MasterchainID { if len(t.ShardProof) == 0 { return nil, ErrNoProof } diff --git a/ton/proof.go b/ton/proof.go index 9f287ed4..9de7d681 100644 --- a/ton/proof.go +++ b/ton/proof.go @@ -113,7 +113,7 @@ func CheckAccountStateProof(addr *address.Address, block *BlockIDExt, stateProof if !skipBlockCheck { blockHash := block.RootHash // we need shard proof only for not masterchain - if len(shardHash) > 0 { + if len(shardHash) > 0 && block.Workchain == address.MasterchainID { if err := CheckShardInMasterProof(block, shardProof, addr.Workchain(), shardHash); err != nil { return nil, nil, fmt.Errorf("shard proof is incorrect: %w", err) } diff --git a/ton/runmethod.go b/ton/runmethod.go index 063be2bb..30c68776 100644 --- a/ton/runmethod.go +++ b/ton/runmethod.go @@ -95,9 +95,10 @@ func (c *APIClient) RunGetMethod(ctx context.Context, blockInfo *BlockIDExt, add var shardProof []*cell.Cell var shardHash []byte - if c.proofCheckPolicy != ProofCheckPolicyUnsafe && addr.Workchain() != address.MasterchainID { + if c.proofCheckPolicy != ProofCheckPolicyUnsafe && addr.Workchain() != address.MasterchainID && + blockInfo.Workchain == address.MasterchainID { if len(t.ShardProof) == 0 { - return nil, fmt.Errorf("liteserver has no proof for this account in a given block, request newer block or disable proof checks") + return nil, ErrNoProof } shardProof = t.ShardProof From 93d1f3f2ed7d8bc103aed47aca571b69213e0dcd Mon Sep 17 00:00:00 2001 From: Oleg Baranov Date: Mon, 16 Sep 2024 12:19:55 +0400 Subject: [PATCH 28/31] CalcAddress method for state init + small refactor --- adnl/adnl_test.go | 1 + adnl/rldp/client_test.go | 18 ++++- tlb/state-init.go | 6 ++ tlb/state-init_test.go | 49 +++++++++++++ ton/integration_test.go | 19 ++--- ton/proof.go | 3 +- ton/sendmessagewait.go | 22 +++--- ton/wallet/integration_test.go | 2 +- ton/wallet/wallet.go | 126 ++------------------------------- ton/wallet/wallet_test.go | 33 ++++++--- 10 files changed, 124 insertions(+), 155 deletions(-) create mode 100644 tlb/state-init_test.go diff --git a/adnl/adnl_test.go b/adnl/adnl_test.go index bb952e23..3ea38a82 100644 --- a/adnl/adnl_test.go +++ b/adnl/adnl_test.go @@ -40,6 +40,7 @@ func TestADNL_ClientServer(t *testing.T) { case MessagePing: if m.Value == 9999 { client.Close() + println("DISCON") return fmt.Errorf("handle mock err") } diff --git a/adnl/rldp/client_test.go b/adnl/rldp/client_test.go index d5079cb0..86f49dcf 100644 --- a/adnl/rldp/client_test.go +++ b/adnl/rldp/client_test.go @@ -377,10 +377,14 @@ func TestRDLP_sendMessageParts(t *testing.T) { } decoded, receivData, err := tDecoder.Decode() - if err != nil || decoded != true { + if err != nil { t.Fatal("failed to decode received test data, err: ", err) } + if decoded != true { + return nil + } + if !bytes.Equal(data, receivData) { t.Fatal("bad data received in 'sendCustomMessage'") } @@ -479,10 +483,14 @@ func TestRLDP_DoQuery(t *testing.T) { } decoded, receivData, err := tDecoder.Decode() - if err != nil || decoded != true { + if err != nil { t.Fatal("failed to decode received test data, err: ", err) } + if decoded != true { + return nil + } + var checkReq Query _, err = tl.Parse(&checkReq, receivData, true) if err != nil { @@ -568,10 +576,14 @@ func TestRLDP_SendAnswer(t *testing.T) { } decoded, receivData, err := tDecoder.Decode() - if err != nil || decoded != true { + if err != nil { t.Fatal("failed to decode received test data, err: ", err) } + if decoded != true { + return nil + } + var checkAnswer Answer _, err = tl.Parse(&checkAnswer, receivData, true) if err != nil { diff --git a/tlb/state-init.go b/tlb/state-init.go index be662235..6202ae75 100644 --- a/tlb/state-init.go +++ b/tlb/state-init.go @@ -1,6 +1,7 @@ package tlb import ( + "github.com/xssnick/tonutils-go/address" "github.com/xssnick/tonutils-go/tvm/cell" ) @@ -16,3 +17,8 @@ type StateInit struct { Data *cell.Cell `tlb:"maybe ^"` Lib *cell.Dictionary `tlb:"dict 256"` } + +func (s StateInit) CalcAddress(workchain int) *address.Address { + c, _ := ToCell(s) + return address.NewAddress(0, byte(workchain), c.Hash()) +} diff --git a/tlb/state-init_test.go b/tlb/state-init_test.go new file mode 100644 index 00000000..e74abb78 --- /dev/null +++ b/tlb/state-init_test.go @@ -0,0 +1,49 @@ +package tlb + +import ( + "github.com/xssnick/tonutils-go/tvm/cell" + "testing" +) + +func TestStateInit_CalcAddress(t *testing.T) { + tests := []struct { + name string + stateInit StateInit + workchain int + want string + }{ + { + name: "Base", + stateInit: StateInit{ + Code: cell.BeginCell().MustStoreUInt(0, 8).EndCell(), + Data: cell.BeginCell().MustStoreUInt(0, 8).EndCell(), + }, + workchain: 0, + want: "EQBPQF6r6-pUObVWu6RO05YwoHQRnjM95tRLAL_s2A6n0pvq", + }, + { + name: "Empty", + stateInit: StateInit{}, + workchain: 0, + want: "EQA_B407fiLIlE5VYZCaI2rki0in6kLyjdhhwitvZNfpe7eY", + }, + { + name: "Master", + stateInit: StateInit{ + Code: cell.BeginCell().MustStoreUInt(123, 8).EndCell(), + Data: cell.BeginCell().MustStoreUInt(456, 16).EndCell(), + }, + workchain: -1, + want: "Ef_jHHi5wLtyTaS56iIEPUc9mJuoD2keQPxZX87rl2FcVDZ1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.stateInit.CalcAddress(tt.workchain) + if got.String() != tt.want { + t.Errorf("StateInit.CalcAddress() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/ton/integration_test.go b/ton/integration_test.go index a8db93b6..5e4fbb43 100644 --- a/ton/integration_test.go +++ b/ton/integration_test.go @@ -3,6 +3,7 @@ package ton import ( "bytes" "context" + "encoding/base64" "encoding/hex" "errors" "fmt" @@ -204,7 +205,7 @@ func Test_RunMethod(t *testing.T) { } func Test_ExternalMessage(t *testing.T) { // need to deploy contract on test-net - > than change config to test-net. - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() ctx = apiTestNet.Client().StickyContext(ctx) @@ -229,7 +230,7 @@ func Test_ExternalMessage(t *testing.T) { // need to deploy contract on test-net MustStoreUInt(1, 16). // add 1 to total EndCell() - err = apiTestNet.SendExternalMessage(ctx, &tlb.ExternalMessage{ + tx, block, _, err := apiTestNet.SendExternalMessageWaitTransaction(ctx, &tlb.ExternalMessage{ DstAddr: testContractAddrTestNet, Body: data, }) @@ -239,9 +240,7 @@ func Test_ExternalMessage(t *testing.T) { // need to deploy contract on test-net return } - // TODO: wait for update and check result - - log.Printf("Current seqno = %d and total = %d", seqno, total) + log.Printf("Current seqno = %d and total = %d | block: %d tx: %d hash: %s", seqno, total, block.SeqNo, tx.LT, base64.URLEncoding.EncodeToString(tx.Hash)) } func Test_Account(t *testing.T) { @@ -601,8 +600,8 @@ func TestAccountStorage_LoadFromCell_ExtraCurrencies(t *testing.T) { t.Run("with proof", func(t *testing.T) { _, err := mainnetAPI.GetAccount(ctx, b, address.MustParseAddr("EQCYv992KVNNCKZHSLLJgM2GGzsgL0UgWP24BCQBaAdqSE2I")) - if err != ErrNoProof { - t.Fatal(err) + if err != nil { + t.Fatal("no proof") } }) @@ -783,7 +782,7 @@ func TestAPIClient_FindLastTransactionByInMsgHash(t *testing.T) { } func TestAPIClient_FindLastTransactionByOutMsgHash(t *testing.T) { - addr := address.MustParseAddr("UQCZ-7akCw_dvl_Q5xyriWqCXdWubIPbuN7aDQlzX45pa01R") + addr := address.MustParseAddr("EQB3ncyBUTjZUA5EnFKR5_EnOMI9V1tTEAAPaiU71gc4TiUt") block, err := api.CurrentMasterchainInfo(context.Background()) if err != nil { @@ -802,6 +801,10 @@ func TestAPIClient_FindLastTransactionByOutMsgHash(t *testing.T) { var hash []byte for i := len(list) - 1; i >= 0; i-- { + if list[i].IO.Out == nil { + continue + } + ls, err := list[i].IO.Out.ToSlice() if err != nil { continue diff --git a/ton/proof.go b/ton/proof.go index 9de7d681..e9cd54c4 100644 --- a/ton/proof.go +++ b/ton/proof.go @@ -23,6 +23,7 @@ func init() { } var ErrNoProof = fmt.Errorf("liteserver has no proof for this account in a given block, request newer block or disable proof checks") +var ErrNoAddrInProof = errors.New("no addr info in proof hashmap") func CheckShardMcStateExtraProof(master *BlockIDExt, shardProof []*cell.Cell) (*tlb.McStateExtra, error) { shardState, err := CheckBlockShardStateProof(shardProof, master.RootHash) @@ -145,7 +146,7 @@ func CheckAccountStateProof(addr *address.Address, block *BlockIDExt, stateProof addrKey := cell.BeginCell().MustStoreSlice(addr.Data(), 256).EndCell() val := shardState.Accounts.ShardAccounts.Get(addrKey) if val == nil { - return nil, nil, errors.New("no addr info in proof hashmap") + return nil, nil, ErrNoAddrInProof } loadVal := val.BeginParse() diff --git a/ton/sendmessagewait.go b/ton/sendmessagewait.go index 5ba36fe4..ec08198a 100644 --- a/ton/sendmessagewait.go +++ b/ton/sendmessagewait.go @@ -12,24 +12,24 @@ import ( 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) +func (c *APIClient) SendExternalMessageWaitTransaction(ctx context.Context, ext *tlb.ExternalMessage) (*tlb.Transaction, *BlockIDExt, []byte, error) { + block, err := c.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) + acc, err := c.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 { + if err = c.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) + tx, block, err := c.waitConfirmation(ctx, block, acc, ext) if err != nil { return nil, nil, nil, err } @@ -37,7 +37,7 @@ func (api *APIClient) SendExternalMessageWaitTransaction(ctx context.Context, ex return tx, block, inMsgHash, nil } -func (api *APIClient) waitConfirmation(ctx context.Context, block *BlockIDExt, acc *tlb.Account, ext *tlb.ExternalMessage) (*tlb.Transaction, *BlockIDExt, error) { +func (c *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 @@ -46,15 +46,15 @@ func (api *APIClient) waitConfirmation(ctx context.Context, block *BlockIDExt, a } till, _ := ctx.Deadline() - ctx = api.Client().StickyContext(ctx) + ctx = c.Client().StickyContext(ctx) for time.Now().Before(till) { - blockNew, err := api.WaitForBlock(block.SeqNo + 1).GetMasterchainInfo(ctx) + blockNew, err := c.WaitForBlock(block.SeqNo + 1).GetMasterchainInfo(ctx) if err != nil { continue } - accNew, err := api.WaitForBlock(blockNew.SeqNo).GetAccount(ctx, blockNew, ext.DstAddr) + accNew, err := c.WaitForBlock(blockNew.SeqNo).GetAccount(ctx, blockNew, ext.DstAddr) if err != nil { continue } @@ -62,7 +62,7 @@ func (api *APIClient) waitConfirmation(ctx context.Context, block *BlockIDExt, a 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 { + if err = c.SendExternalMessage(ctx, ext); err != nil { continue } @@ -75,7 +75,7 @@ func (api *APIClient) waitConfirmation(ctx context.Context, block *BlockIDExt, a // 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) + txList, err := c.WaitForBlock(block.SeqNo).ListTransactions(ctx, ext.DstAddr, 5, lastLt, lastHash) if err != nil { continue } diff --git a/ton/wallet/integration_test.go b/ton/wallet/integration_test.go index be640f55..610e0ad8 100644 --- a/ton/wallet/integration_test.go +++ b/ton/wallet/integration_test.go @@ -249,7 +249,7 @@ func Test_WalletFindTransactionByInMsgHash(t *testing.T) { } // find tx hash - tx, err := w.FindTransactionByInMsgHash(ctx, inMsgHash, 30) + tx, err := w.api.FindLastTransactionByInMsgHash(ctx, inMsgHash, 30) if err != nil { t.Fatal("cannot find tx:", err.Error()) } diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index 0346cefb..84a272df 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -123,9 +123,6 @@ var timeNow = time.Now var ( ErrUnsupportedWalletVersion = errors.New("wallet version is not supported") - ErrTxWasNotConfirmed = errors.New("transaction was not confirmed in a given deadline, but it may still be confirmed later") - // Deprecated: use ton.ErrTxWasNotFound - ErrTxWasNotFound = errors.New("requested transaction is not found") ) type TonAPI interface { @@ -134,6 +131,7 @@ type TonAPI interface { CurrentMasterchainInfo(ctx context.Context) (*ton.BlockIDExt, error) GetAccount(ctx context.Context, block *ton.BlockIDExt, addr *address.Address) (*tlb.Account, error) SendExternalMessage(ctx context.Context, msg *tlb.ExternalMessage) error + SendExternalMessageWaitTransaction(ctx context.Context, ext *tlb.ExternalMessage) (*tlb.Transaction, *ton.BlockIDExt, []byte, error) RunGetMethod(ctx context.Context, blockInfo *ton.BlockIDExt, addr *address.Address, method string, params ...interface{}) (*ton.ExecutionResult, error) ListTransactions(ctx context.Context, addr *address.Address, num uint32, lt uint64, txHash []byte) ([]*tlb.Transaction, error) FindLastTransactionByInMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) @@ -489,123 +487,19 @@ func (w *Wallet) TransferWaitTransaction(ctx context.Context, to *address.Addres } func (w *Wallet) sendMany(ctx context.Context, messages []*Message, waitConfirmation ...bool) (tx *tlb.Transaction, block *ton.BlockIDExt, inMsgHash []byte, err error) { - block, err = w.api.CurrentMasterchainInfo(ctx) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get block: %w", err) - } - - acc, err := w.api.WaitForBlock(block.SeqNo).GetAccount(ctx, block, w.addr) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get account state: %w", err) - } - ext, err := w.BuildExternalMessageForMany(ctx, messages) if err != nil { return nil, nil, nil, err } - inMsgHash = ext.Body.Hash() - - if err = w.api.SendExternalMessage(ctx, ext); err != nil { - return nil, nil, nil, fmt.Errorf("failed to send message: %w", err) - } if len(waitConfirmation) > 0 && waitConfirmation[0] { - tx, block, err = w.waitConfirmation(ctx, block, acc, ext) - if err != nil { - return nil, nil, nil, err - } + return w.api.SendExternalMessageWaitTransaction(ctx, ext) } - return tx, block, inMsgHash, nil -} - -func (w *Wallet) waitConfirmation(ctx context.Context, block *ton.BlockIDExt, acc *tlb.Account, ext *tlb.ExternalMessage) (*tlb.Transaction, *ton.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 = w.api.Client().StickyContext(ctx) - - for time.Now().Before(till) { - blockNew, err := w.api.WaitForBlock(block.SeqNo + 1).GetMasterchainInfo(ctx) - if err != nil { - continue - } - - accNew, err := w.api.WaitForBlock(blockNew.SeqNo).GetAccount(ctx, blockNew, w.addr) - 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 = w.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 := w.api.WaitForBlock(block.SeqNo).ListTransactions(ctx, w.addr, 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 + if err = w.api.SendExternalMessage(ctx, ext); err != nil { + return nil, nil, nil, fmt.Errorf("failed to send message: %w", err) } - - return nil, nil, ErrTxWasNotConfirmed + return nil, nil, ext.Body.Hash(), nil } // TransferNoBounce - can be used to transfer TON to not yet initialized contract/wallet @@ -830,16 +724,6 @@ func (w *Wallet) DeployContract(ctx context.Context, amount tlb.Coins, msgBody, return addr, nil } -// Deprecated: use ton.FindLastTransactionByInMsgHash -// FindTransactionByInMsgHash returns transaction in wallet account with incoming message hash equal to msgHash. -func (w *Wallet) FindTransactionByInMsgHash(ctx context.Context, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) { - tx, err := w.api.FindLastTransactionByInMsgHash(ctx, w.addr, msgHash, maxTxNumToScan...) - if err != nil && errors.Is(err, ton.ErrTxWasNotFound) { - return nil, ErrTxWasNotFound - } - return tx, err -} - func SimpleMessage(to *address.Address, amount tlb.Coins, payload *cell.Cell) *Message { return &Message{ Mode: PayGasSeparately + IgnoreErrors, diff --git a/ton/wallet/wallet_test.go b/ton/wallet/wallet_test.go index 492b867a..b7d6b434 100644 --- a/ton/wallet/wallet_test.go +++ b/ton/wallet/wallet_test.go @@ -20,15 +20,20 @@ import ( ) type MockAPI struct { - getBlockInfo func(ctx context.Context) (*ton.BlockIDExt, error) - getAccount func(ctx context.Context, block *ton.BlockIDExt, addr *address.Address) (*tlb.Account, error) - sendExternalMessage func(ctx context.Context, msg *tlb.ExternalMessage) error - runGetMethod func(ctx context.Context, blockInfo *ton.BlockIDExt, addr *address.Address, method string, params ...interface{}) (*ton.ExecutionResult, error) - listTransactions func(ctx context.Context, addr *address.Address, limit uint32, lt uint64, txHash []byte) ([]*tlb.Transaction, error) + getBlockInfo func(ctx context.Context) (*ton.BlockIDExt, error) + getAccount func(ctx context.Context, block *ton.BlockIDExt, addr *address.Address) (*tlb.Account, error) + sendExternalMessage func(ctx context.Context, msg *tlb.ExternalMessage) error + sendExternalMessageWait func(ctx context.Context, ext *tlb.ExternalMessage) (*tlb.Transaction, *ton.BlockIDExt, []byte, error) + runGetMethod func(ctx context.Context, blockInfo *ton.BlockIDExt, addr *address.Address, method string, params ...interface{}) (*ton.ExecutionResult, error) + listTransactions func(ctx context.Context, addr *address.Address, limit uint32, lt uint64, txHash []byte) ([]*tlb.Transaction, error) extMsgSent *tlb.ExternalMessage } +func (m MockAPI) SendExternalMessageWaitTransaction(ctx context.Context, ext *tlb.ExternalMessage) (*tlb.Transaction, *ton.BlockIDExt, []byte, error) { + return m.sendExternalMessageWait(ctx, ext) +} + func (m MockAPI) FindLastTransactionByInMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) { //TODO implement me panic("implement me") @@ -41,11 +46,12 @@ func (m MockAPI) FindLastTransactionByOutMsgHash(ctx context.Context, addr *addr func (m MockAPI) WaitForBlock(seqno uint32) ton.APIClientWrapped { return &WaiterMock{ - MGetMasterchainInfo: m.getBlockInfo, - MGetAccount: m.getAccount, - MSendExternalMessage: m.sendExternalMessage, - MRunGetMethod: m.runGetMethod, - MListTransactions: m.listTransactions, + MGetMasterchainInfo: m.getBlockInfo, + MGetAccount: m.getAccount, + MSendExternalMessage: m.sendExternalMessage, + MRunGetMethod: m.runGetMethod, + MListTransactions: m.listTransactions, + MSendExternalMessageWaitTransaction: m.sendExternalMessageWait, } } @@ -257,6 +263,13 @@ func TestWallet_Send(t *testing.T) { return nil } + m.sendExternalMessageWait = func(ctx context.Context, ext *tlb.ExternalMessage) (*tlb.Transaction, *ton.BlockIDExt, []byte, error) { + if err := m.sendExternalMessage(ctx, ext); err != nil { + return nil, nil, nil, err + } + return &tlb.Transaction{}, &ton.BlockIDExt{}, make([]byte, 32), nil + } + msg := &Message{ Mode: CarryAllRemainingBalance, InternalMessage: intMsg, From e831a182f3b0a755275dc2e74a67acf029eea310 Mon Sep 17 00:00:00 2001 From: Coverage Date: Mon, 16 Sep 2024 08:26:14 +0000 Subject: [PATCH 29/31] Updated coverage badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c598ee9e..91be2326 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Based on TON][ton-svg]][ton] [![Telegram Channel][tgc-svg]][tg-channel] -![Coverage](https://img.shields.io/badge/Coverage-73.8%25-brightgreen) +![Coverage](https://img.shields.io/badge/Coverage-72.0%25-brightgreen) Golang library for interacting with TON blockchain. From 0774432a7c88d9a4e3f056f35212ae2339ee829d Mon Sep 17 00:00:00 2001 From: Oleg Baranov Date: Mon, 16 Sep 2024 15:54:30 +0400 Subject: [PATCH 30/31] Retrier improved --- adnl/adnl_test.go | 1 - ton/retrier.go | 21 ++++++++++++++++++++- ton/wallet/integration_test.go | 5 +---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/adnl/adnl_test.go b/adnl/adnl_test.go index 3ea38a82..bb952e23 100644 --- a/adnl/adnl_test.go +++ b/adnl/adnl_test.go @@ -40,7 +40,6 @@ func TestADNL_ClientServer(t *testing.T) { case MessagePing: if m.Value == 9999 { client.Close() - println("DISCON") return fmt.Errorf("handle mock err") } diff --git a/ton/retrier.go b/ton/retrier.go index 752f12fa..6f09a31d 100644 --- a/ton/retrier.go +++ b/ton/retrier.go @@ -16,7 +16,11 @@ type retryClient struct { } func (w *retryClient) QueryLiteserver(ctx context.Context, payload tl.Serializable, result tl.Serializable) error { - tries := 0 + const maxRounds = 2 + + tries, rounds := 0, 0 + ctxBackup := ctx + for { err := w.original.QueryLiteserver(ctx, payload, result) if w.maxRetries > 0 && tries >= w.maxRetries { @@ -37,6 +41,13 @@ func (w *retryClient) QueryLiteserver(ctx context.Context, payload tl.Serializab // try next node ctx, err = w.original.StickyContextNextNode(ctx) if err != nil { + rounds++ + if rounds < maxRounds { + // try same nodes one more time + ctx = ctxBackup + continue + } + return fmt.Errorf("timeout error received, but failed to try with next node, "+ "looks like all active nodes was already tried, original error: %w", err) } @@ -50,7 +61,15 @@ func (w *retryClient) QueryLiteserver(ctx context.Context, payload tl.Serializab lsErr.Code == -400 || lsErr.Code == -503 || (lsErr.Code == 0 && strings.Contains(lsErr.Text, "Failed to get account state"))) { + if ctx, err = w.original.StickyContextNextNode(ctx); err != nil { // try next node + rounds++ + if rounds < maxRounds { + // try same nodes one more time + ctx = ctxBackup + continue + } + // no more nodes left, return as it is return nil } diff --git a/ton/wallet/integration_test.go b/ton/wallet/integration_test.go index 610e0ad8..c83cfe37 100644 --- a/ton/wallet/integration_test.go +++ b/ton/wallet/integration_test.go @@ -1,6 +1,3 @@ -//go:build integration -// +build integration - package wallet import ( @@ -249,7 +246,7 @@ func Test_WalletFindTransactionByInMsgHash(t *testing.T) { } // find tx hash - tx, err := w.api.FindLastTransactionByInMsgHash(ctx, inMsgHash, 30) + tx, err := w.api.FindLastTransactionByInMsgHash(ctx, w.addr, inMsgHash, 30) if err != nil { t.Fatal("cannot find tx:", err.Error()) } From ed8b367705bb15d422e5d7aab71f83cb5e858109 Mon Sep 17 00:00:00 2001 From: Coverage Date: Mon, 16 Sep 2024 11:58:52 +0000 Subject: [PATCH 31/31] Updated coverage badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91be2326..60e34da9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Based on TON][ton-svg]][ton] [![Telegram Channel][tgc-svg]][tg-channel] -![Coverage](https://img.shields.io/badge/Coverage-72.0%25-brightgreen) +![Coverage](https://img.shields.io/badge/Coverage-73.5%25-brightgreen) Golang library for interacting with TON blockchain.