Skip to content

Commit 5058964

Browse files
Slightly change mempool emulation
1 parent f0793f2 commit 5058964

File tree

4 files changed

+295
-288
lines changed

4 files changed

+295
-288
lines changed

cmd/api/main.go

-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ func main() {
7373
api.WithExecutor(storage),
7474
api.WithMessageSender(msgSender),
7575
api.WithSpamFilter(spamFilter),
76-
api.WithEmulationChannel(mempoolCh),
7776
api.WithTonConnectSecret(cfg.TonConnect.Secret),
7877
)
7978
if err != nil {

pkg/api/emulation.go

+289
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
package api
2+
3+
import (
4+
"context"
5+
"encoding/base64"
6+
"errors"
7+
"math/big"
8+
"time"
9+
10+
"github.com/prometheus/client_golang/prometheus"
11+
"github.com/prometheus/client_golang/prometheus/promauto"
12+
"github.com/shopspring/decimal"
13+
"github.com/tonkeeper/tongo"
14+
"github.com/tonkeeper/tongo/abi"
15+
"github.com/tonkeeper/tongo/boc"
16+
"github.com/tonkeeper/tongo/tlb"
17+
"github.com/tonkeeper/tongo/ton"
18+
"github.com/tonkeeper/tongo/txemulator"
19+
tongoWallet "github.com/tonkeeper/tongo/wallet"
20+
21+
"github.com/tonkeeper/opentonapi/pkg/blockchain"
22+
"github.com/tonkeeper/opentonapi/pkg/cache"
23+
"github.com/tonkeeper/opentonapi/pkg/core"
24+
"github.com/tonkeeper/opentonapi/pkg/sentry"
25+
)
26+
27+
var (
28+
emulatedMessagesCounter = promauto.NewCounter(prometheus.CounterOpts{
29+
Name: "tonapi_emulated_messages_counter",
30+
Help: "The total number of emulated messages",
31+
})
32+
)
33+
34+
func (h *Handler) RunEmulation(ctx context.Context, msgCh <-chan blockchain.ExtInMsgCopy, emulationCh chan<- blockchain.ExtInMsgCopy) {
35+
for {
36+
select {
37+
case <-ctx.Done():
38+
return
39+
case msgCopy := <-msgCh:
40+
emulatedMessagesCounter.Inc()
41+
go func() {
42+
ctx, cancel := context.WithTimeout(ctx, time.Second*5)
43+
defer cancel()
44+
45+
// TODO: find a way to emulate when tonapi receives a batch of messages in a single request to SendBlockchainMessage endpoint.
46+
_, err := h.addToMempool(ctx, msgCopy.Payload, nil, emulationCh)
47+
if err != nil {
48+
sentry.Send("addToMempool", sentry.SentryInfoData{"payload": msgCopy.Payload}, sentry.LevelError)
49+
}
50+
}()
51+
}
52+
}
53+
}
54+
55+
func (h *Handler) addToMempool(ctx context.Context, bytesBoc []byte, shardAccount map[tongo.AccountID]tlb.ShardAccount, emulationCh chan<- blockchain.ExtInMsgCopy) (map[tongo.AccountID]tlb.ShardAccount, error) {
56+
if shardAccount == nil {
57+
shardAccount = map[tongo.AccountID]tlb.ShardAccount{}
58+
}
59+
msgCell, err := boc.DeserializeBoc(bytesBoc)
60+
if err != nil {
61+
return shardAccount, err
62+
}
63+
ttl := int64(30)
64+
msgV4, err := tongoWallet.DecodeMessageV4(msgCell[0])
65+
if err == nil {
66+
diff := int64(msgV4.ValidUntil) - time.Now().Unix()
67+
if diff < 600 {
68+
ttl = diff
69+
}
70+
}
71+
var message tlb.Message
72+
err = tlb.Unmarshal(msgCell[0], &message)
73+
if err != nil {
74+
return shardAccount, err
75+
}
76+
config, err := h.storage.TrimmedConfigBase64()
77+
if err != nil {
78+
return shardAccount, err
79+
}
80+
emulator, err := txemulator.NewTraceBuilder(txemulator.WithAccountsSource(h.storage),
81+
txemulator.WithAccountsMap(shardAccount),
82+
txemulator.WithConfigBase64(config),
83+
txemulator.WithSignatureCheck(),
84+
)
85+
if err != nil {
86+
return shardAccount, err
87+
}
88+
tree, err := emulator.Run(ctx, message)
89+
if err != nil {
90+
return shardAccount, err
91+
}
92+
newShardAccount := emulator.FinalStates()
93+
trace, err := emulatedTreeToTrace(ctx, h.executor, h.storage, config, tree, newShardAccount)
94+
if err != nil {
95+
return shardAccount, err
96+
}
97+
accounts := make(map[tongo.AccountID]struct{})
98+
core.Visit(trace, func(node *core.Trace) {
99+
accounts[node.Account] = struct{}{}
100+
})
101+
hash, err := msgCell[0].Hash256()
102+
if err != nil {
103+
return shardAccount, err
104+
}
105+
h.mempoolEmulate.traces.Set(hash, trace, cache.WithExpiration(time.Second*time.Duration(ttl)))
106+
for account := range accounts {
107+
if _, ok := h.mempoolEmulateIgnoreAccounts[account]; ok { // the map is filled only once at the start
108+
continue
109+
}
110+
oldMemHashes, _ := h.mempoolEmulate.accountsTraces.Get(account)
111+
newMemHashes := make([]ton.Bits256, 0, len(oldMemHashes)+1)
112+
for _, mHash := range oldMemHashes { //we need to filter messages which already created transactions
113+
_, err = h.storage.SearchTransactionByMessageHash(ctx, mHash)
114+
_, prs := h.mempoolEmulate.traces.Get(mHash)
115+
if err != nil || prs { //because if err is not null it already happened and if !prs it is not in mempool
116+
newMemHashes = append(newMemHashes, mHash)
117+
}
118+
}
119+
newMemHashes = append(newMemHashes, hash) // it's important to make it las
120+
h.mempoolEmulate.accountsTraces.Set(account, newMemHashes, cache.WithExpiration(time.Second*time.Duration(ttl)))
121+
}
122+
emulationCh <- blockchain.ExtInMsgCopy{
123+
MsgBoc: base64.StdEncoding.EncodeToString(bytesBoc),
124+
Details: h.ctxToDetails(ctx),
125+
Payload: bytesBoc,
126+
Accounts: accounts,
127+
}
128+
return newShardAccount, nil
129+
}
130+
131+
func emulatedTreeToTrace(ctx context.Context, executor executor, resolver core.LibraryResolver, configBase64 string, tree *txemulator.TxTree, accounts map[tongo.AccountID]tlb.ShardAccount) (*core.Trace, error) {
132+
if !tree.TX.Msgs.InMsg.Exists {
133+
return nil, errors.New("there is no incoming message in emulation result")
134+
}
135+
m := tree.TX.Msgs.InMsg.Value.Value
136+
var a tlb.MsgAddress
137+
switch m.Info.SumType {
138+
case "IntMsgInfo":
139+
a = m.Info.IntMsgInfo.Dest
140+
case "ExtInMsgInfo":
141+
a = m.Info.ExtInMsgInfo.Dest
142+
default:
143+
return nil, errors.New("unknown message type in emulation result")
144+
}
145+
transaction, err := core.ConvertTransaction(int32(a.AddrStd.WorkchainId), tongo.Transaction{
146+
Transaction: tree.TX,
147+
BlockID: tongo.BlockIDExt{BlockID: tongo.BlockID{Workchain: int32(a.AddrStd.WorkchainId)}},
148+
})
149+
filteredMsgs := make([]core.Message, 0, len(transaction.OutMsgs))
150+
for _, msg := range transaction.OutMsgs {
151+
if msg.Destination == nil {
152+
filteredMsgs = append(filteredMsgs, msg)
153+
}
154+
}
155+
transaction.OutMsgs = filteredMsgs //all internal messages in emulation result are delivered to another account and created transaction
156+
if err != nil {
157+
return nil, err
158+
}
159+
t := &core.Trace{
160+
Transaction: *transaction,
161+
}
162+
additionalInfo := &core.TraceAdditionalInfo{}
163+
for i := range tree.Children {
164+
child, err := emulatedTreeToTrace(ctx, executor, resolver, configBase64, tree.Children[i], accounts)
165+
if err != nil {
166+
return nil, err
167+
}
168+
t.Children = append(t.Children, child)
169+
}
170+
accountID := t.Account
171+
code := accountCode(accounts[accountID])
172+
if code == nil {
173+
return t, nil
174+
}
175+
b, err := code.ToBoc()
176+
if err != nil {
177+
return nil, err
178+
}
179+
sharedExecutor := newSharedAccountExecutor(accounts, executor, resolver, configBase64)
180+
inspectionResult, err := abi.NewContractInspector().InspectContract(ctx, b, sharedExecutor, accountID)
181+
if err != nil {
182+
return nil, err
183+
}
184+
implemented := make(map[abi.ContractInterface]struct{}, len(inspectionResult.ContractInterfaces))
185+
for _, iface := range inspectionResult.ContractInterfaces {
186+
implemented[iface] = struct{}{}
187+
}
188+
// TODO: for all obtained Jetton Masters confirm that jetton wallets are valid
189+
t.AccountInterfaces = inspectionResult.ContractInterfaces
190+
for _, m := range inspectionResult.GetMethods {
191+
switch data := m.Result.(type) {
192+
case abi.GetNftDataResult:
193+
if _, ok := implemented[abi.Teleitem]; !ok {
194+
continue
195+
}
196+
value := big.Int(data.Index)
197+
index := decimal.NewFromBigInt(&value, 0)
198+
collectionAddr, err := tongo.AccountIDFromTlb(data.CollectionAddress)
199+
if err != nil || collectionAddr == nil {
200+
continue
201+
}
202+
_, nftByIndex, err := abi.GetNftAddressByIndex(ctx, sharedExecutor, *collectionAddr, data.Index)
203+
if err != nil {
204+
continue
205+
}
206+
indexResult, ok := nftByIndex.(abi.GetNftAddressByIndexResult)
207+
if !ok {
208+
continue
209+
}
210+
nftAddr, err := tongo.AccountIDFromTlb(indexResult.Address)
211+
if err != nil || nftAddr == nil {
212+
continue
213+
}
214+
additionalInfo.EmulatedTeleitemNFT = &core.EmulatedTeleitemNFT{
215+
Index: index,
216+
CollectionAddress: collectionAddr,
217+
Verified: *nftAddr == accountID,
218+
}
219+
case abi.GetWalletDataResult:
220+
master, _ := tongo.AccountIDFromTlb(data.Jetton)
221+
additionalInfo.SetJettonMaster(accountID, *master)
222+
case abi.GetSaleData_GetgemsResult:
223+
price := big.Int(data.FullPrice)
224+
owner, err := tongo.AccountIDFromTlb(data.Owner)
225+
if err != nil {
226+
continue
227+
}
228+
item, err := tongo.AccountIDFromTlb(data.Nft)
229+
if err != nil || item == nil {
230+
continue
231+
}
232+
additionalInfo.NftSaleContract = &core.NftSaleContract{
233+
NftPrice: price.Int64(),
234+
Owner: owner,
235+
Item: *item,
236+
}
237+
case abi.GetSaleData_BasicResult:
238+
price := big.Int(data.FullPrice)
239+
owner, err := tongo.AccountIDFromTlb(data.Owner)
240+
if err != nil {
241+
continue
242+
}
243+
item, err := tongo.AccountIDFromTlb(data.Nft)
244+
if err != nil || item == nil {
245+
continue
246+
}
247+
additionalInfo.NftSaleContract = &core.NftSaleContract{
248+
NftPrice: price.Int64(),
249+
Owner: owner,
250+
Item: *item,
251+
}
252+
case abi.GetSaleData_GetgemsAuctionResult:
253+
owner, err := tongo.AccountIDFromTlb(data.Owner)
254+
if err != nil {
255+
continue
256+
}
257+
item, err := tongo.AccountIDFromTlb(data.Nft)
258+
if err != nil || item == nil {
259+
continue
260+
}
261+
additionalInfo.NftSaleContract = &core.NftSaleContract{
262+
NftPrice: int64(data.MaxBid),
263+
Owner: owner,
264+
Item: *item,
265+
}
266+
case abi.GetPoolData_StonfiResult:
267+
t0, err0 := tongo.AccountIDFromTlb(data.Token0Address)
268+
t1, err1 := tongo.AccountIDFromTlb(data.Token1Address)
269+
if err1 != nil || err0 != nil {
270+
continue
271+
}
272+
additionalInfo.STONfiPool = &core.STONfiPool{
273+
Token0: *t0,
274+
Token1: *t1,
275+
}
276+
for _, accountID := range []ton.AccountID{*t0, *t1} {
277+
_, value, err := abi.GetWalletData(ctx, sharedExecutor, accountID)
278+
if err != nil {
279+
return nil, err
280+
}
281+
data := value.(abi.GetWalletDataResult)
282+
master, _ := tongo.AccountIDFromTlb(data.Jetton)
283+
additionalInfo.SetJettonMaster(accountID, *master)
284+
}
285+
}
286+
}
287+
t.SetAdditionalInfo(additionalInfo)
288+
return t, nil
289+
}

0 commit comments

Comments
 (0)