From 511b0ea8c5be3d107158a1dd988d2517ae863243 Mon Sep 17 00:00:00 2001 From: Oleg Baranov Date: Wed, 8 May 2024 21:04:09 +0500 Subject: [PATCH 1/3] FindTransaction & Offline LC & Dict Proof of no key & RLDP tunning --- adnl/rldp/client.go | 19 +++--- adnl/rldp/client_test.go | 7 +- example/vanity_fast/main.go | 2 +- example/wallet-cold-alike/main.go | 1 + liteclient/integration_test.go | 13 ++++ liteclient/offline.go | 31 +++++++++ tlb/transaction.go | 19 +++++- ton/api.go | 3 +- ton/integration_test.go | 71 ++++++++++++++++++++ ton/transactions.go | 83 +++++++++++++++++++++++ ton/wallet/wallet.go | 64 +++--------------- ton/wallet/wallet_test.go | 54 ++++++++++----- tvm/cell/cell.go | 31 +++++++++ tvm/cell/dict.go | 6 +- tvm/cell/dict_test.go | 31 ++++++++- tvm/cell/slice.go | 106 ++++++++++++++++++++++++++---- 16 files changed, 439 insertions(+), 102 deletions(-) create mode 100644 liteclient/offline.go diff --git a/adnl/rldp/client.go b/adnl/rldp/client.go index 45e6f822..9f3e0da1 100644 --- a/adnl/rldp/client.go +++ b/adnl/rldp/client.go @@ -4,7 +4,6 @@ import ( "context" "crypto/ed25519" "crypto/rand" - "encoding/hex" "fmt" "github.com/xssnick/tonutils-go/adnl" "github.com/xssnick/tonutils-go/adnl/rldp/raptorq" @@ -124,7 +123,7 @@ func (r *RLDP) handleMessage(msg *adnl.MessageCustom) error { return fmt.Errorf("not supported fec type") } - id := hex.EncodeToString(m.TransferID) + id := string(m.TransferID) r.mx.RLock() stream := r.recvStreams[id] r.mx.RUnlock() @@ -248,7 +247,7 @@ func (r *RLDP) handleMessage(msg *adnl.MessageCustom) error { }() } case Answer: - qid := hex.EncodeToString(rVal.ID) + qid := string(rVal.ID) r.mx.Lock() req := r.activeRequests[qid] @@ -298,7 +297,7 @@ func (r *RLDP) handleMessage(msg *adnl.MessageCustom) error { } } case Complete: // receiver has fully received transfer, close our stream - id := hex.EncodeToString(m.TransferID) + id := string(m.TransferID) r.mx.Lock() t := r.activeTransfers[id] @@ -312,7 +311,7 @@ func (r *RLDP) handleMessage(msg *adnl.MessageCustom) error { } case Confirm: // receiver has received some parts // TODO: use confirmed seqno to limit sends - // id := hex.EncodeToString(m.TransferID) + // id := string(m.TransferID) // r.mx.RLock() // t := r.activeTransfers[id] // r.mx.RUnlock() @@ -329,7 +328,7 @@ func (r *RLDP) sendMessageParts(ctx context.Context, transferId, data []byte) er return fmt.Errorf("failed to create raptorq object encoder: %w", err) } - id := hex.EncodeToString(transferId) + id := string(transferId) ch := make(chan bool, 1) r.mx.Lock() @@ -354,8 +353,10 @@ func (r *RLDP) sendMessageParts(ctx context.Context, transferId, data []byte) er default: } - if symbolsSent > enc.BaseSymbolsNum()+enc.BaseSymbolsNum()/2 { //+enc.BaseSymbolsNum()/2 - x := symbolsSent - (enc.BaseSymbolsNum() + enc.BaseSymbolsNum()/2) + fastSymbols := enc.BaseSymbolsNum() + enc.BaseSymbolsNum()/5 // send 120% packets fast + + if symbolsSent > fastSymbols { + x := (symbolsSent - fastSymbols) / 2 select { case <-ctx.Done(): @@ -415,7 +416,7 @@ func (r *RLDP) DoQuery(ctx context.Context, maxAnswerSize int64, query, result t Data: query, } - queryID := hex.EncodeToString(q.ID) + queryID := string(q.ID) res := make(chan any, 2) diff --git a/adnl/rldp/client_test.go b/adnl/rldp/client_test.go index 72592359..d5079cb0 100644 --- a/adnl/rldp/client_test.go +++ b/adnl/rldp/client_test.go @@ -5,7 +5,6 @@ import ( "context" "crypto/ed25519" "crypto/rand" - "encoding/hex" "errors" "github.com/xssnick/tonutils-go/adnl" "github.com/xssnick/tonutils-go/adnl/rldp/raptorq" @@ -227,7 +226,7 @@ func TestRLDP_handleMessage(t *testing.T) { return nil } } else if test.tstSubName == "answer case" { - queryId := hex.EncodeToString(tQuery.ID) + queryId := string(tQuery.ID) tChan := make(chan any, 2) cli.activeRequests[queryId] = tChan } @@ -279,7 +278,7 @@ func TestRLDP_handleMessage(t *testing.T) { if err != nil { t.Fatal("failed to execute handleMessage func, err: ", err) } - if cli.recvStreams[hex.EncodeToString(tId)].lastCompleteAt.IsZero() { + if cli.recvStreams[string(tId)].lastCompleteAt.IsZero() { t.Error("got lastCompleteAt == nil, want != nil") } }) @@ -296,7 +295,7 @@ func TestRLDP_handleMessage(t *testing.T) { cli := NewClient(tAdnl) tChan := make(chan bool, 1) - cli.activeTransfers[hex.EncodeToString(tId)] = tChan + cli.activeTransfers[string(tId)] = tChan err := cli.handleMessage(msgComplete) if err != nil { diff --git a/example/vanity_fast/main.go b/example/vanity_fast/main.go index 1fc29554..b2055d33 100644 --- a/example/vanity_fast/main.go +++ b/example/vanity_fast/main.go @@ -97,7 +97,7 @@ func generateWallets(caseSensitive bool, suffix string, counter *uint64) { binary.BigEndian.PutUint32(subwalletIDBytes, i) getHashV3HashFromKey(hash, subwalletIDBytes, v3DataCell, v3StateInit, hashDst) - addr := address.NewAddress(0, 0, hashDst) + addr := address.NewAddress(0, 0, hashDst).Bounce(false) addr.StringToBytes(addrTo, addrFrom) if equalityFunc(suffix, string(addrTo[strCmpOffset:])) { diff --git a/example/wallet-cold-alike/main.go b/example/wallet-cold-alike/main.go index b2959bae..caf21947 100644 --- a/example/wallet-cold-alike/main.go +++ b/example/wallet-cold-alike/main.go @@ -13,6 +13,7 @@ import ( ) func main() { + // for completely offline mode you could use liteclient.NewOfflineClient() instead client := liteclient.NewConnectionPool() // connect to mainnet lite server diff --git a/liteclient/integration_test.go b/liteclient/integration_test.go index c30297da..fb172f81 100644 --- a/liteclient/integration_test.go +++ b/liteclient/integration_test.go @@ -69,6 +69,19 @@ func Test_Conn(t *testing.T) { doReq(nil) } +func Test_Offline(t *testing.T) { + client := NewOfflineClient() + + doReq := func(expErr error) { + var resp tl.Serializable + err := client.QueryLiteserver(context.Background(), GetMasterchainInf{}, &resp) + if err != ErrOfflineMode { + t.Fatal("err", err) + } + } + doReq(nil) +} + func Test_ConnSticky(t *testing.T) { client := NewConnectionPool() diff --git a/liteclient/offline.go b/liteclient/offline.go new file mode 100644 index 00000000..8cbdf48b --- /dev/null +++ b/liteclient/offline.go @@ -0,0 +1,31 @@ +package liteclient + +import ( + "context" + "fmt" + "github.com/xssnick/tonutils-go/tl" +) + +var ErrOfflineMode = fmt.Errorf("offline mode is used") + +type OfflineClient struct{} + +func NewOfflineClient() OfflineClient { + return OfflineClient{} +} + +func (o OfflineClient) QueryLiteserver(ctx context.Context, payload tl.Serializable, result tl.Serializable) error { + return ErrOfflineMode +} + +func (o OfflineClient) StickyContext(ctx context.Context) context.Context { + return nil +} + +func (o OfflineClient) StickyContextNextNode(ctx context.Context) (context.Context, error) { + return ctx, nil +} + +func (o OfflineClient) StickyNodeID(ctx context.Context) uint32 { + return 0 +} diff --git a/tlb/transaction.go b/tlb/transaction.go index b2d4b151..974aa9a1 100644 --- a/tlb/transaction.go +++ b/tlb/transaction.go @@ -235,7 +235,15 @@ type Transaction struct { } func (t *Transaction) Dump() string { - res := fmt.Sprintf("LT: %d\n\nInput:\nType %s\nFrom %s\nPayload:\n%s\n\nOutputs:\n", t.LT, t.IO.In.MsgType, t.IO.In.Msg.SenderAddr(), t.IO.In.Msg.Payload().Dump()) + var in string + if t.IO.In != nil { + var pl = "EMPTY" + if p := t.IO.In.Msg.Payload(); p != nil { + pl = p.Dump() + } + in = fmt.Sprintf("\nInput:\nType %s\nFrom %s\nPayload:\n%s\n", t.IO.In.MsgType, t.IO.In.Msg.SenderAddr(), pl) + } + res := fmt.Sprintf("LT: %d\n%s\nOutputs:\n", t.LT, in) if t.IO.Out != nil { list, err := t.IO.Out.ToSlice() if err != nil { @@ -243,7 +251,14 @@ func (t *Transaction) Dump() string { } for _, m := range list { - res += m.AsInternal().Dump() + switch m.MsgType { + case MsgTypeInternal: + res += m.AsInternal().Dump() + case MsgTypeExternalOut: + res += "[EXT OUT] " + m.AsExternalOut().Body.Dump() + default: + res += "[UNKNOWN]" + } } } return res diff --git a/ton/api.go b/ton/api.go index 2f81eb88..e86c74af 100644 --- a/ton/api.go +++ b/ton/api.go @@ -16,7 +16,6 @@ import ( func init() { tl.Register(LSError{}, "liteServer.error code:int message:string = liteServer.Error") - } type ProofCheckPolicy int @@ -74,6 +73,8 @@ type APIClientWrapped interface { WithTimeout(timeout time.Duration) APIClientWrapped SetTrustedBlock(block *BlockIDExt) SetTrustedBlockFromConfig(cfg *liteclient.GlobalConfig) + FindLastTransactionByInMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) + FindLastTransactionByOutMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) } type APIClient struct { diff --git a/ton/integration_test.go b/ton/integration_test.go index 0ce8181d..a8db93b6 100644 --- a/ton/integration_test.go +++ b/ton/integration_test.go @@ -753,3 +753,74 @@ func TestAPIClient_WithRetry(t *testing.T) { t.Fatal("expected deadline exceeded error but", err) } } + +func TestAPIClient_FindLastTransactionByInMsgHash(t *testing.T) { + addr := address.MustParseAddr("Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF") + + block, err := api.CurrentMasterchainInfo(context.Background()) + if err != nil { + t.Fatal(err) + } + + acc, err := api.GetAccount(context.Background(), block, addr) + if err != nil { + t.Fatal(err) + } + + list, err := api.ListTransactions(context.Background(), addr, 20, acc.LastTxLT, acc.LastTxHash) + if err != nil { + t.Fatal(err) + } + + tx := list[len(list)-1] + + // find tx hash + tx, err = api.FindLastTransactionByInMsgHash(context.Background(), addr, tx.IO.In.Msg.Payload().Hash(), 30) + if err != nil { + t.Fatal("cannot find tx:", err.Error()) + } + t.Logf("tx hash: %s %s", hex.EncodeToString(tx.Hash), hex.EncodeToString(acc.LastTxHash)) +} + +func TestAPIClient_FindLastTransactionByOutMsgHash(t *testing.T) { + addr := address.MustParseAddr("UQCZ-7akCw_dvl_Q5xyriWqCXdWubIPbuN7aDQlzX45pa01R") + + block, err := api.CurrentMasterchainInfo(context.Background()) + if err != nil { + t.Fatal(err) + } + + acc, err := api.GetAccount(context.Background(), block, addr) + if err != nil { + t.Fatal(err) + } + + list, err := api.ListTransactions(context.Background(), addr, 20, acc.LastTxLT, acc.LastTxHash) + if err != nil { + t.Fatal(err) + } + + var hash []byte + for i := len(list) - 1; i >= 0; i-- { + ls, err := list[i].IO.Out.ToSlice() + if err != nil { + continue + } + + if len(ls) == 0 { + continue + } + hash = ls[0].Msg.Payload().Hash() + } + + if hash == nil { + t.Fatal("no outs") + } + + // find tx hash + tx, err := api.FindLastTransactionByOutMsgHash(context.Background(), addr, hash, 30) + if err != nil { + t.Fatal("cannot find tx:", err.Error()) + } + t.Logf("tx hash: %s %s", hex.EncodeToString(tx.Hash), hex.EncodeToString(acc.LastTxHash)) +} diff --git a/ton/transactions.go b/ton/transactions.go index a4919219..6deecd8f 100644 --- a/ton/transactions.go +++ b/ton/transactions.go @@ -9,6 +9,7 @@ import ( "github.com/xssnick/tonutils-go/tl" "github.com/xssnick/tonutils-go/tlb" "github.com/xssnick/tonutils-go/tvm/cell" + "strings" "time" ) @@ -19,6 +20,8 @@ func init() { tl.Register(TransactionInfo{}, "liteServer.transactionInfo id:tonNode.blockIdExt proof:bytes transaction:bytes = liteServer.TransactionInfo") } +var ErrTxWasNotFound = errors.New("requested transaction is not found") + type TransactionInfo struct { ID *BlockIDExt `tl:"struct"` Proof []byte `tl:"bytes"` @@ -270,3 +273,83 @@ func (c *APIClient) SubscribeOnTransactions(workerCtx context.Context, addr *add } } } + +// FindLastTransactionByInMsgHash returns last transaction in account where incoming message (payload) hash equal to msgHash. +func (c *APIClient) FindLastTransactionByInMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) { + return c.findLastTransactionByHash(ctx, addr, false, msgHash, maxTxNumToScan...) +} + +// FindLastTransactionByOutMsgHash returns last transaction in account where one of outgoing message (payload) hashes equal to msgHash. +func (c *APIClient) FindLastTransactionByOutMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) { + return c.findLastTransactionByHash(ctx, addr, true, msgHash, maxTxNumToScan...) +} + +func (c *APIClient) findLastTransactionByHash(ctx context.Context, addr *address.Address, isOut bool, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) { + limit := 60 + if len(maxTxNumToScan) > 0 { + limit = maxTxNumToScan[0] + } + + block, err := c.CurrentMasterchainInfo(ctx) + if err != nil { + return nil, fmt.Errorf("cannot get masterchain info: %w", err) + } + + acc, err := c.WaitForBlock(block.SeqNo).GetAccount(ctx, block, addr) + if err != nil { + return nil, fmt.Errorf("cannot get account: %w", err) + } + if !acc.IsActive { // no tx is made from this account + return nil, fmt.Errorf("account is inactive: %w", ErrTxWasNotFound) + } + + scanned := 0 + for lastLt, lastHash := acc.LastTxLT, acc.LastTxHash; ; { + if lastLt == 0 { // no older transactions + return nil, ErrTxWasNotFound + } + + txList, err := c.ListTransactions(ctx, addr, 15, lastLt, lastHash) + if err != nil { + if strings.Contains(err.Error(), "cannot compute block with specified transaction: lt not in db") { + return nil, fmt.Errorf("archive node is needed: %w", ErrTxWasNotFound) + } + return nil, fmt.Errorf("cannot list transactions: %w", err) + } + + 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 isOut { + list, err := transaction.IO.Out.ToSlice() + if err != nil { + return nil, fmt.Errorf("cannot list out messages: %w", err) + } + + for _, m := range list { + if bytes.Equal(m.Msg.Payload().Hash(), msgHash) { + return transaction, nil + } + } + } else { + if transaction.IO.In == nil { + continue + } + if !bytes.Equal(transaction.IO.In.Msg.Payload().Hash(), msgHash) { + continue + } + } + + return transaction, nil + } + + scanned += 15 + + if scanned >= limit { + return nil, fmt.Errorf("scan limit of %d transactions was reached, %d transactions was checked and hash was not found", limit, scanned) + } + } +} diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index 22caec11..62a6f384 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -14,7 +14,6 @@ import ( "errors" "fmt" "github.com/xssnick/tonutils-go/adnl" - "strings" "time" "github.com/xssnick/tonutils-go/ton" @@ -106,7 +105,8 @@ 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") - ErrTxWasNotFound = errors.New("requested transaction is not found") + // Deprecated: use ton.ErrTxWasNotFound + ErrTxWasNotFound = errors.New("requested transaction is not found") ) type TonAPI interface { @@ -117,6 +117,8 @@ type TonAPI interface { SendExternalMessage(ctx context.Context, msg *tlb.ExternalMessage) 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) + FindLastTransactionByOutMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) } type Message struct { @@ -755,62 +757,14 @@ 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) { - limit := 60 - if len(maxTxNumToScan) > 0 { - limit = maxTxNumToScan[0] - } - - block, err := w.api.CurrentMasterchainInfo(ctx) - if err != nil { - return nil, fmt.Errorf("cannot get masterchain info: %w", err) - } - - acc, err := w.api.WaitForBlock(block.SeqNo).GetAccount(ctx, block, w.addr) - if err != nil { - return nil, fmt.Errorf("cannot get account: %w", err) - } - if !acc.IsActive { // no tx is made from this account - return nil, fmt.Errorf("account is inactive: %w", ErrTxWasNotFound) - } - - scanned := 0 - for lastLt, lastHash := acc.LastTxLT, acc.LastTxHash; ; { - if lastLt == 0 { // no older transactions - return nil, ErrTxWasNotFound - } - - txList, err := w.api.ListTransactions(ctx, w.addr, 15, lastLt, lastHash) - if err != nil && strings.Contains(err.Error(), "cannot compute block with specified transaction: lt not in db") { - return nil, fmt.Errorf("archive node is needed: %w", ErrTxWasNotFound) - } - if err != nil { - return nil, fmt.Errorf("cannot list transactions: %w", err) - } - - 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 transaction.IO.In == nil { - continue - } - if !bytes.Equal(transaction.IO.In.Msg.Payload().Hash(), msgHash) { - continue - } - - return transaction, nil - } - - scanned += 15 - - if scanned >= limit { - return nil, fmt.Errorf("scan limit of %d transactions was reached, %d transactions was checked and hash was not found", limit, scanned) - } + 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 { diff --git a/ton/wallet/wallet_test.go b/ton/wallet/wallet_test.go index d1890214..0d8fae55 100644 --- a/ton/wallet/wallet_test.go +++ b/ton/wallet/wallet_test.go @@ -29,6 +29,16 @@ type MockAPI struct { extMsgSent *tlb.ExternalMessage } +func (m MockAPI) FindLastTransactionByInMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) { + //TODO implement me + panic("implement me") +} + +func (m MockAPI) FindLastTransactionByOutMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) { + //TODO implement me + panic("implement me") +} + func (m MockAPI) WaitForBlock(seqno uint32) ton.APIClientWrapped { return &WaiterMock{ MGetMasterchainInfo: m.getBlockInfo, @@ -452,23 +462,33 @@ 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) + 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) +} + +func (w WaiterMock) FindLastTransactionByInMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) { + return w.MFindLastTransactionByInMsgHash(ctx, addr, msgHash, maxTxNumToScan...) +} + +func (w WaiterMock) FindLastTransactionByOutMsgHash(ctx context.Context, addr *address.Address, msgHash []byte, maxTxNumToScan ...int) (*tlb.Transaction, error) { + return w.MFindLastTransactionByOutMsgHash(ctx, addr, msgHash, maxTxNumToScan...) } func (w WaiterMock) GetLibraries(ctx context.Context, list ...[]byte) ([]*cell.Cell, error) { diff --git a/tvm/cell/cell.go b/tvm/cell/cell.go index 3093715b..10e3dbe0 100644 --- a/tvm/cell/cell.go +++ b/tvm/cell/cell.go @@ -34,6 +34,15 @@ type Cell struct { refs []*Cell } +type RawUnsafeCell struct { + IsSpecial bool + LevelMask LevelMask + BitsSz uint + Data []byte + + Refs []*Cell +} + func (c *Cell) copy() *Cell { return &Cell{ special: c.special, @@ -70,6 +79,28 @@ func (c *Cell) ToBuilder() *Builder { } } +func (c *Cell) ToRawUnsafe() RawUnsafeCell { + return RawUnsafeCell{ + IsSpecial: c.special, + LevelMask: c.levelMask, + BitsSz: c.bitsSz, + Data: c.data, + Refs: c.refs, + } +} + +func FromRawUnsafe(u RawUnsafeCell) *Cell { + c := &Cell{ + special: u.IsSpecial, + levelMask: u.LevelMask, + bitsSz: u.BitsSz, + data: u.Data, + refs: u.Refs, + } + c.calculateHashes() + return c +} + func (c *Cell) BitsSize() uint { return c.bitsSz } diff --git a/tvm/cell/dict.go b/tvm/cell/dict.go index 19bb80d3..ccd82e91 100644 --- a/tvm/cell/dict.go +++ b/tvm/cell/dict.go @@ -264,7 +264,8 @@ func (d *Dictionary) LoadValue(key *Cell) (*Slice, error) { // LoadValueWithProof - searches key in the underline dict cell, constructs proof path and returns leaf // -// If key is not found ErrNoSuchKeyInDict will be returned +// If key is not found ErrNoSuchKeyInDict will be returned, +// and path with proof of non-existing key will be attached to skeleton (if passed) func (d *Dictionary) LoadValueWithProof(key *Cell, skeleton *ProofSkeleton) (*Slice, *ProofSkeleton, error) { if key.BitsSize() != d.keySz { return nil, nil, fmt.Errorf("incorrect key size") @@ -392,6 +393,9 @@ func (d *Dictionary) findKey(branch *Cell, lookupKey *Cell, at *ProofSkeleton) ( } if !bytes.Equal(loadedPfx, pfx) { + if sk != nil { + at.Merge(root) + } return nil, nil, ErrNoSuchKeyInDict } diff --git a/tvm/cell/dict_test.go b/tvm/cell/dict_test.go index fe4a0597..734d8048 100644 --- a/tvm/cell/dict_test.go +++ b/tvm/cell/dict_test.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "encoding/base64" "encoding/hex" + "errors" "math" "math/big" "testing" @@ -204,7 +205,35 @@ func TestDict_Delete(t *testing.T) { mm := NewDict(64) mm.SetIntKey(big.NewInt(255), BeginCell().EndCell()) mm.SetIntKey(big.NewInt(777), BeginCell().EndCell()) - hh, _ := mm.MustToCell().BeginParse().ToDict(64) + mm.SetIntKey(big.NewInt(333), BeginCell().EndCell()) + mm.SetIntKey(big.NewInt(334), BeginCell().EndCell()) + mm.SetIntKey(big.NewInt(331), BeginCell().EndCell()) + mm.SetIntKey(big.NewInt(1000), BeginCell().EndCell()) + mm.SetIntKey(big.NewInt(1001), BeginCell().EndCell()) + hh, _ := mm.AsCell().BeginParse().ToDict(64) + + kProof := BeginCell().MustStoreBigInt(big.NewInt(332), 64).EndCell() + sk := CreateProofSkeleton() + _, _, err := hh.LoadValueWithProof(kProof, sk) + if !errors.Is(err, ErrNoSuchKeyInDict) { + t.Fatal("no such key") + } + proof, err := hh.AsCell().CreateProof(sk) + if err != nil { + t.Fatal("failed to proof no key") + } + + if _, err = hh.LoadValue(kProof); !errors.Is(err, ErrNoSuchKeyInDict) { + t.Fatal("no key orig", err) + } + + proof, err = UnwrapProof(proof, hh.AsCell().Hash()) + if err != nil { + t.Fatal("bad proof hash", err) + } + if _, err = proof.AsDict(64).LoadValue(kProof); !errors.Is(err, ErrNoSuchKeyInDict) { + t.Fatal("no no key proof", err) + } hh.DeleteIntKey(big.NewInt(255)) hh2, _ := hh.MustToCell().BeginParse().ToDict(64) diff --git a/tvm/cell/slice.go b/tvm/cell/slice.go index a3bded69..2fd5e4c6 100644 --- a/tvm/cell/slice.go +++ b/tvm/cell/slice.go @@ -37,6 +37,14 @@ func (c *Slice) LoadRef() (*Slice, error) { return ref.BeginParse(), nil } +func (c *Slice) PreloadRef() (*Slice, error) { + ref, err := c.PreloadRefCell() + if err != nil { + return nil, err + } + return ref.BeginParse(), nil +} + func (c *Slice) LoadRefCell() (*Cell, error) { if len(c.refs) == 0 { return nil, ErrNoMoreRefs @@ -47,6 +55,13 @@ func (c *Slice) LoadRefCell() (*Cell, error) { return ref, nil } +func (c *Slice) PreloadRefCell() (*Cell, error) { + if len(c.refs) == 0 { + return nil, ErrNoMoreRefs + } + return c.refs[0], nil +} + func (c *Slice) MustLoadMaybeRef() *Slice { r, err := c.LoadMaybeRef() if err != nil { @@ -115,6 +130,14 @@ func (c *Slice) MustLoadUInt(sz uint) uint64 { return res } +func (c *Slice) MustPreloadUInt(sz uint) uint64 { + res, err := c.PreloadUInt(sz) + if err != nil { + panic(err) + } + return res +} + func (c *Slice) LoadUInt(sz uint) (uint64, error) { res, err := c.LoadBigUInt(sz) if err != nil { @@ -123,6 +146,14 @@ func (c *Slice) LoadUInt(sz uint) (uint64, error) { return res.Uint64(), nil } +func (c *Slice) PreloadUInt(sz uint) (uint64, error) { + res, err := c.PreloadBigUInt(sz) + if err != nil { + return 0, err + } + return res.Uint64(), nil +} + func (c *Slice) MustLoadInt(sz uint) int64 { res, err := c.LoadInt(sz) if err != nil { @@ -163,6 +194,14 @@ func (c *Slice) MustLoadBigUInt(sz uint) *big.Int { return r } +func (c *Slice) MustPreloadBigUInt(sz uint) *big.Int { + r, err := c.PreloadBigUInt(sz) + if err != nil { + panic(err) + } + return r +} + func (c *Slice) LoadBigUInt(sz uint) (*big.Int, error) { if sz > 256 { return nil, ErrTooBigSize @@ -171,6 +210,14 @@ func (c *Slice) LoadBigUInt(sz uint) (*big.Int, error) { return c.loadBigNumber(sz) } +func (c *Slice) PreloadBigUInt(sz uint) (*big.Int, error) { + if sz > 256 { + return nil, ErrTooBigSize + } + + return c.preloadBigNumber(sz) +} + func (c *Slice) loadBigNumber(sz uint) (*big.Int, error) { b, err := c.LoadSlice(sz) if err != nil { @@ -191,6 +238,26 @@ func (c *Slice) loadBigNumber(sz uint) (*big.Int, error) { return new(big.Int).SetBytes(b), nil } +func (c *Slice) preloadBigNumber(sz uint) (*big.Int, error) { + b, err := c.PreloadSlice(sz) + if err != nil { + return nil, err + } + + // check is value is uses full bytes + if offset := sz % 8; offset > 0 { + // move bits to right side of bytes + for i := len(b) - 1; i >= 0; i-- { + b[i] >>= 8 - offset // get last bits + if i > 0 { + b[i] += b[i-1] << offset + } + } + } + + return new(big.Int).SetBytes(b), nil +} + func (c *Slice) LoadBigInt(sz uint) (*big.Int, error) { if sz > 257 { return nil, ErrTooBigSize @@ -204,11 +271,11 @@ func (c *Slice) LoadBigInt(sz uint) (*big.Int, error) { one := big.NewInt(1) // check is last bit = 1 - isNegative := new(big.Int).And(u, new(big.Int).Lsh(one, uint(sz-1))).Cmp(big.NewInt(0)) != 0 + isNegative := new(big.Int).And(u, new(big.Int).Lsh(one, sz-1)).Cmp(big.NewInt(0)) != 0 if isNegative { // get max value of given sz - i := new(big.Int).Lsh(one, uint(sz)) + i := new(big.Int).Lsh(one, sz) i = i.Sub(i, one) val := u.Sub(u, i) @@ -251,7 +318,23 @@ func (c *Slice) MustLoadSlice(sz uint) []byte { return s } +func (c *Slice) MustPreloadSlice(sz uint) []byte { + s, err := c.PreloadSlice(sz) + if err != nil { + panic(err) + } + return s +} + func (c *Slice) LoadSlice(sz uint) ([]byte, error) { + return c.loadSlice(sz, false) +} + +func (c *Slice) PreloadSlice(sz uint) ([]byte, error) { + return c.loadSlice(sz, true) +} + +func (c *Slice) loadSlice(sz uint, preload bool) ([]byte, error) { if c.bitsSz-c.loadedSz < sz { return nil, ErrNotEnoughData(int(c.bitsSz-c.loadedSz), int(sz)) } @@ -308,17 +391,18 @@ func (c *Slice) LoadSlice(sz uint) ([]byte, error) { i++ } - if sz >= unusedBits { - usedBytes := (sz - unusedBits) / 8 - if unusedBits > 0 { - usedBytes++ - } + if !preload { + if sz >= unusedBits { + usedBytes := (sz - unusedBits) / 8 + if unusedBits > 0 { + usedBytes++ + } - c.data = c.data[usedBytes:] + c.data = c.data[usedBytes:] + } + c.loadedSz += sz } - c.loadedSz += sz - return loadedData, nil } @@ -518,7 +602,7 @@ func (c *Slice) ToBuilder() *Builder { left := c.bitsSz - c.loadedSz return &Builder{ bitsSz: left, - data: c.Copy().MustLoadSlice(left), + data: c.MustPreloadSlice(left), refs: c.refs, } } From 140ab4b7c16a7fd2b1a77a8ddb91a5c3144b497a Mon Sep 17 00:00:00 2001 From: Klimov Sergey Date: Thu, 9 May 2024 00:28:12 +0800 Subject: [PATCH 2/3] Fixed interruption of repetitions if the parent contact was not closed, unlike the one created next --- ton/retrier.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ton/retrier.go b/ton/retrier.go index 31a7716d..e65191d8 100644 --- a/ton/retrier.go +++ b/ton/retrier.go @@ -35,6 +35,14 @@ func (w *retryClient) QueryLiteserver(ctx context.Context, payload tl.Serializab continue } + if errors.Is(err, context.DeadlineExceeded) { + err := ctx.Err() + if err != nil { + return err + } + + continue + } return err } From 3b5ac726f8c8165df9412114862a5df64ba24bab Mon Sep 17 00:00:00 2001 From: Coverage Date: Wed, 8 May 2024 16:43:24 +0000 Subject: [PATCH 3/3] Updated coverage badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0d91b53..6a5c3451 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Based on TON][ton-svg]][ton] -![Coverage](https://img.shields.io/badge/Coverage-74.0%25-brightgreen) +![Coverage](https://img.shields.io/badge/Coverage-73.8%25-brightgreen) Golang library for interacting with TON blockchain.