diff --git a/README.md b/README.md index adcfad17..b106b43d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Based on TON][ton-svg]][ton] -![Coverage](https://img.shields.io/badge/Coverage-71.9%25-brightgreen) +![Coverage](https://img.shields.io/badge/Coverage-72.4%25-brightgreen) Golang library for interacting with TON blockchain. diff --git a/adnl/adnl.go b/adnl/adnl.go index 15a616e1..3b13f876 100644 --- a/adnl/adnl.go +++ b/adnl/adnl.go @@ -13,6 +13,7 @@ import ( "github.com/xssnick/tonutils-go/tl" "log" "reflect" + "strings" "sync" "time" ) @@ -154,7 +155,7 @@ func (a *ADNL) processPacket(packet *PacketContent, ch *Channel) (err error) { } if seqno > a.confirmSeqno { - a.confirmSeqno = uint64(*packet.Seqno) + a.confirmSeqno = seqno } if packet.ReinitDate != nil && *packet.ReinitDate > a.dstReinit { @@ -536,8 +537,11 @@ func (a *ADNL) send(ctx context.Context, buf []byte) error { n, err := a.writer.Write(buf, dl) if err != nil { - // it should trigger disconnect handler in read routine - a.writer.Close() + // not close on io timeout because it can be triggered by network overload + if !strings.Contains(err.Error(), "i/o timeout") { + // it should trigger disconnect handler in read routine + a.writer.Close() + } return err } if n != len(buf) { diff --git a/adnl/client.go b/adnl/client.go index d89c7937..76348ac1 100644 --- a/adnl/client.go +++ b/adnl/client.go @@ -69,7 +69,7 @@ func Connect(ctx context.Context, addr string, peerKey ed25519.PublicKey, ourKey } return nil - }) + }, a.Close) go func() { if err = listenPacketsAsClient(a, conn); err != nil { diff --git a/adnl/conn.go b/adnl/conn.go index 179c22db..317ea77a 100644 --- a/adnl/conn.go +++ b/adnl/conn.go @@ -14,10 +14,11 @@ type clientConn struct { mx sync.Mutex } -func newWriter(writer func(p []byte, deadline time.Time) (err error)) *clientConn { +func newWriter(writer func(p []byte, deadline time.Time) (err error), close func()) *clientConn { return &clientConn{ - closer: make(chan bool, 1), - writer: writer, + onClose: close, + closer: make(chan bool, 1), + writer: writer, } } @@ -41,6 +42,9 @@ func (c *clientConn) Close() error { if !c.closed { c.closed = true close(c.closer) + if h := c.onClose; h != nil { + go h() // to not lock + } } return nil diff --git a/adnl/dht/client.go b/adnl/dht/client.go index 6d825d16..1e861e77 100644 --- a/adnl/dht/client.go +++ b/adnl/dht/client.go @@ -325,11 +325,33 @@ func (c *Client) StoreAddress(ctx context.Context, addresses address.List, ttl t if err != nil { return 0, nil, err } - return c.Store(ctx, []byte("address"), 0, data, ttl, ownerKey, copies) -} -func (c *Client) Store(ctx context.Context, name []byte, index int32, value []byte, ttl time.Duration, ownerKey ed25519.PrivateKey, atLeastCopies int) (copiesMade int, idKey []byte, err error) { id := adnl.PublicKeyED25519{Key: ownerKey.Public().(ed25519.PublicKey)} + return c.Store(ctx, id, []byte("address"), 0, data, UpdateRuleSignature{}, ttl, ownerKey, copies) +} + +func (c *Client) StoreOverlayNodes(ctx context.Context, overlayKey []byte, nodes *overlay.NodesList, ttl time.Duration, copies int) (int, []byte, error) { + if len(nodes.List) == 0 { + return 0, nil, fmt.Errorf("0 nodes in list") + } + + for _, node := range nodes.List { + err := node.CheckSignature() + if err != nil { + return 0, nil, fmt.Errorf("untrusted overlay node in list: %w", err) + } + } + + data, err := tl.Serialize(nodes, true) + if err != nil { + return 0, nil, err + } + + id := adnl.PublicKeyOverlay{Key: overlayKey} + return c.Store(ctx, id, []byte("nodes"), 0, data, UpdateRuleOverlayNodes{}, ttl, nil, copies) +} + +func (c *Client) Store(ctx context.Context, id any, name []byte, index int32, value []byte, rule any, ttl time.Duration, ownerKey ed25519.PrivateKey, atLeastCopies int) (copiesMade int, idKey []byte, err error) { idKey, err = adnl.ToKeyID(id) if err != nil { return 0, nil, err @@ -343,19 +365,22 @@ func (c *Client) Store(ctx context.Context, name []byte, index int32, value []by Index: index, }, ID: id, - UpdateRule: UpdateRuleSignature{}, + UpdateRule: rule, }, Data: value, TTL: int32(time.Now().Add(ttl).Unix()), } - val.KeyDescription.Signature, err = signTL(val.KeyDescription, ownerKey) - if err != nil { - return 0, nil, fmt.Errorf("failed to sign key description: %w", err) - } - val.Signature, err = signTL(val, ownerKey) - if err != nil { - return 0, nil, fmt.Errorf("failed to sign value: %w", err) + switch rule.(type) { + case UpdateRuleSignature: + val.KeyDescription.Signature, err = signTL(val.KeyDescription, ownerKey) + if err != nil { + return 0, nil, fmt.Errorf("failed to sign key description: %w", err) + } + val.Signature, err = signTL(val, ownerKey) + if err != nil { + return 0, nil, fmt.Errorf("failed to sign value: %w", err) + } } kid, err := adnl.ToKeyID(val.KeyDescription.Key) diff --git a/adnl/dht/client_test.go b/adnl/dht/client_test.go index 35e71f16..0f9525d7 100644 --- a/adnl/dht/client_test.go +++ b/adnl/dht/client_test.go @@ -11,8 +11,10 @@ import ( "fmt" "github.com/xssnick/tonutils-go/adnl" "github.com/xssnick/tonutils-go/adnl/address" + "github.com/xssnick/tonutils-go/adnl/overlay" "github.com/xssnick/tonutils-go/liteclient" "github.com/xssnick/tonutils-go/tl" + "math/rand" "net" "reflect" "strconv" @@ -47,7 +49,6 @@ func (m MockADNL) SetQueryHandler(handler func(msg *adnl.MessageQuery) error) { func (m MockADNL) SendCustomMessage(ctx context.Context, req tl.Serializable) error { return nil - } func (m MockADNL) Answer(ctx context.Context, queryID []byte, result tl.Serializable) error { @@ -585,7 +586,7 @@ func TestClient_Close(t *testing.T) { }) } -func TestClient_Store(t *testing.T) { +func TestClient_StoreAddress(t *testing.T) { for i := 0; i < 15; i++ { addrList := address.List{ @@ -608,13 +609,6 @@ func TestClient_Store(t *testing.T) { Priority: 0, ExpireAt: 0, } - tlAddrList, err := tl.Serialize(addrList, true) - if err != nil { - t.Fatal() - } - - nameAddr := []byte("address") - var index int32 = 0 cliePrivK, err := hex.DecodeString("83590f541d37b783aa504049bab792696d12bbec3d23a954353300f816ca8b9693037f2613f6063869544caacac3eabbd7456e4d6e731478fccc961c137d1284") if err != nil { @@ -707,7 +701,7 @@ func TestClient_Store(t *testing.T) { t.Fatal("failed to prepare test client, err: ", err) } - count, _, err := cli.Store(context.Background(), nameAddr, index, tlAddrList, time.Hour, cliePrivK, 1) + count, _, err := cli.StoreAddress(context.Background(), addrList, time.Hour, cliePrivK, 1) if err != nil { t.Errorf(err.Error()) } @@ -717,3 +711,126 @@ func TestClient_Store(t *testing.T) { }) } } + +func TestClient_StoreOverlayNodesIntegration(t *testing.T) { + pub, priv, err := ed25519.GenerateKey(nil) + if err != nil { + t.Fatal(err) + } + + gateway := adnl.NewGateway(priv) + err = gateway.StartClient() + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second) + defer cancel() + + dhtClient, err := NewClientFromConfigUrl(ctx, gateway, "https://ton-blockchain.github.io/global.config.json") + if err != nil { + t.Fatalf("failed to init DHT client: %s", err.Error()) + } + + time.Sleep(2 * time.Second) + + id := make([]byte, 32) + rand.Read(id) + + node, err := overlay.NewNode(id, priv) + if err != nil { + t.Fatal(err) + } + + _, _, err = dhtClient.StoreOverlayNodes(ctx, id, &overlay.NodesList{ + List: []overlay.Node{*node}, + }, 5*time.Minute, 1) + if err != nil { + t.Fatal(err) + } + + list, _, err := dhtClient.FindOverlayNodes(ctx, id) + if err != nil { + t.Fatal(err) + } + + println("NUM", len(list.List)) + + if len(list.List) > 1 { + t.Fatal("list len") + } + + if !bytes.Equal(list.List[0].ID.(adnl.PublicKeyED25519).Key, pub) { + t.Fatal("key not eq") + } +} + +func TestClient_StoreAddressIntegration(t *testing.T) { + _, priv, err := ed25519.GenerateKey(nil) + if err != nil { + t.Fatal(err) + } + + gateway := adnl.NewGateway(priv) + err = gateway.StartClient() + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second) + defer cancel() + + dhtClient, err := NewClientFromConfigUrl(ctx, gateway, "https://ton-blockchain.github.io/global.config.json") + if err != nil { + t.Fatalf("failed to init DHT client: %s", err.Error()) + } + + time.Sleep(2 * time.Second) + + id := make([]byte, 32) + rand.Read(id) + + pub, key, _ := ed25519.GenerateKey(nil) + + addrList := address.List{ + Addresses: []*address.UDP{ + { + net.IPv4(1, 1, 1, 1).To4(), + 11111, + }, + { + net.IPv4(2, 2, 2, 2).To4(), + 22222, + }, + { + net.IPv4(3, 3, 3, 3).To4(), + 333333, + }, + }, + Version: 0, + ReinitDate: 0, + Priority: 0, + ExpireAt: 0, + } + + _, _, err = dhtClient.StoreAddress(ctx, addrList, 5*time.Minute, key, 2) + if err != nil { + t.Fatal(err) + } + + kid, err := adnl.ToKeyID(adnl.PublicKeyED25519{ + Key: pub, + }) + if err != nil { + t.Fatal(err) + } + + res, _, err := dhtClient.FindAddresses(ctx, kid) + if err != nil { + t.Fatal(err) + } + + if len(res.Addresses) != 3 { + t.Fatal("addr len not 3") + } +} diff --git a/adnl/gateway.go b/adnl/gateway.go index 086e1385..c6b8bea2 100644 --- a/adnl/gateway.go +++ b/adnl/gateway.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "crypto/ed25519" - "encoding/hex" "fmt" "github.com/xssnick/tonutils-go/adnl/address" "github.com/xssnick/tonutils-go/tl" @@ -268,7 +267,6 @@ func (g *Gateway) listen(rootId []byte) { g.mx.RUnlock() if proc == nil { - Logger("unknown destination:", hex.EncodeToString(id)) continue } @@ -349,7 +347,7 @@ func (g *Gateway) registerClient(addr net.Addr, key ed25519.PublicKey, id string a.writer = newWriter(func(p []byte, deadline time.Time) (err error) { currentAddr := *(*net.Addr)(atomic.LoadPointer(&peer.addr)) return g.write(deadline, currentAddr, p) - }) + }, a.Close) peer.client = a g.peers[id] = peer diff --git a/adnl/overlay/manager-adnl.go b/adnl/overlay/manager-adnl.go index 229001ac..67523d57 100644 --- a/adnl/overlay/manager-adnl.go +++ b/adnl/overlay/manager-adnl.go @@ -37,6 +37,7 @@ type ADNLWrapper struct { rootQueryHandler func(msg *adnl.MessageQuery) error rootDisconnectHandler func(addr string, key ed25519.PublicKey) rootCustomHandler func(msg *adnl.MessageCustom) error + unknownOverlayHandler func(msg *adnl.MessageQuery) error ADNL } @@ -65,14 +66,21 @@ func (a *ADNLWrapper) SetCustomMessageHandler(handler func(msg *adnl.MessageCust a.rootCustomHandler = handler } +func (a *ADNLWrapper) SetOnUnknownOverlayQuery(handler func(query *adnl.MessageQuery) error) { + a.unknownOverlayHandler = handler +} + func (a *ADNLWrapper) queryHandler(msg *adnl.MessageQuery) error { - obj, over := unwrapQuery(msg.Data) + obj, over := UnwrapQuery(msg.Data) if over != nil { id := hex.EncodeToString(over) a.mx.RLock() o := a.overlays[id] a.mx.RUnlock() if o == nil { + if h := a.unknownOverlayHandler; h != nil { + return h(msg) + } return fmt.Errorf("got query for unregistered overlay with id: %s", id) } @@ -113,7 +121,7 @@ func (a *ADNLWrapper) disconnectHandler(addr string, key ed25519.PublicKey) { } func (a *ADNLWrapper) customHandler(msg *adnl.MessageCustom) error { - obj, over := unwrapMessage(msg.Data) + obj, over := UnwrapMessage(msg.Data) if over != nil { id := hex.EncodeToString(over) a.mx.RLock() diff --git a/adnl/overlay/manager-rldp.go b/adnl/overlay/manager-rldp.go index 129384c9..b951ab04 100644 --- a/adnl/overlay/manager-rldp.go +++ b/adnl/overlay/manager-rldp.go @@ -24,6 +24,7 @@ type RLDPWrapper struct { rootQueryHandler func(transferId []byte, query *rldp.Query) error rootDisconnectHandler func() + unknownOverlayHandler func(transferId []byte, query *rldp.Query) error RLDP } @@ -43,18 +44,25 @@ func (r *RLDPWrapper) SetOnQuery(handler func(transferId []byte, query *rldp.Que r.rootQueryHandler = handler } +func (r *RLDPWrapper) SetOnUnknownOverlayQuery(handler func(transferId []byte, query *rldp.Query) error) { + r.unknownOverlayHandler = handler +} + func (r *RLDPWrapper) SetOnDisconnect(handler func()) { r.rootDisconnectHandler = handler } func (r *RLDPWrapper) queryHandler(transferId []byte, query *rldp.Query) error { - obj, over := unwrapQuery(query.Data) + obj, over := UnwrapQuery(query.Data) if over != nil { id := hex.EncodeToString(over) r.mx.RLock() o := r.overlays[id] r.mx.RUnlock() if o == nil { + if h := r.unknownOverlayHandler; h != nil { + return h(transferId, query) + } return fmt.Errorf("got query for unregistered overlay with id: %s", id) } diff --git a/adnl/overlay/types.go b/adnl/overlay/types.go index 75f048a1..9087ad10 100644 --- a/adnl/overlay/types.go +++ b/adnl/overlay/types.go @@ -299,3 +299,22 @@ func (n *Node) Sign(key ed25519.PrivateKey) error { return nil } + +func NewNode(overlay []byte, key ed25519.PrivateKey) (*Node, error) { + keyHash, err := adnl.ToKeyID(adnl.PublicKeyOverlay{ + Key: overlay, + }) + if err != nil { + return nil, err + } + + oNode := Node{ + ID: adnl.PublicKeyED25519{Key: key.Public().(ed25519.PublicKey)}, + Overlay: keyHash, + Version: int32(time.Now().Unix()), + } + if err := oNode.Sign(key); err != nil { + return nil, err + } + return &oNode, nil +} diff --git a/adnl/overlay/wrap.go b/adnl/overlay/wrap.go index e25a9098..42f8a3c6 100644 --- a/adnl/overlay/wrap.go +++ b/adnl/overlay/wrap.go @@ -4,7 +4,7 @@ import ( "github.com/xssnick/tonutils-go/tl" ) -func unwrapMessage(data tl.Serializable) (tl.Serializable, []byte) { +func UnwrapMessage(data tl.Serializable) (tl.Serializable, []byte) { if arr, ok := data.([]tl.Serializable); ok && len(arr) > 1 { if q, isQuery := arr[0].(Message); isQuery { return arr[1], q.Overlay @@ -13,7 +13,7 @@ func unwrapMessage(data tl.Serializable) (tl.Serializable, []byte) { return nil, nil } -func unwrapQuery(data tl.Serializable) (tl.Serializable, []byte) { +func UnwrapQuery(data tl.Serializable) (tl.Serializable, []byte) { if arr, ok := data.([]tl.Serializable); ok && len(arr) > 1 { if q, isQuery := arr[0].(Query); isQuery { return arr[1], q.Overlay @@ -21,3 +21,11 @@ func unwrapQuery(data tl.Serializable) (tl.Serializable, []byte) { } return nil, nil } + +func WrapQuery(id []byte, data tl.Serializable) tl.Serializable { + return []tl.Serializable{Query{Overlay: id}, data} +} + +func WrapMessage(id []byte, data tl.Serializable) tl.Serializable { + return []tl.Serializable{Message{Overlay: id}, data} +} diff --git a/adnl/rldp/client.go b/adnl/rldp/client.go index 3951da79..3f7b1280 100644 --- a/adnl/rldp/client.go +++ b/adnl/rldp/client.go @@ -55,7 +55,7 @@ type decoderStream struct { const _MTU = 1 << 37 const _SymbolSize = 768 -const _PacketWaitTime = 15 * time.Millisecond +const _PacketWaitTime = 5 * time.Millisecond func NewClient(a ADNL) *RLDP { r := &RLDP{ @@ -154,7 +154,7 @@ func (r *RLDP) handleMessage(msg *adnl.MessageCustom) error { defer stream.mx.Unlock() if stream.finishedAt != nil { - if stream.lastCompleteAt.Add(_PacketWaitTime).Before(time.Now()) { // we not send completions too often, to not get socket buffer overflow + if stream.lastCompleteAt.Add(2 * time.Millisecond).Before(time.Now()) { // we not send completions too often, to not get socket buffer overflow var complete tl.Serializable = Complete{ TransferID: m.TransferID, @@ -268,7 +268,7 @@ func (r *RLDP) handleMessage(msg *adnl.MessageCustom) error { stream.maxSeqno = m.Seqno // send confirm for each 10 packets or after 30 ms - if stream.lastConfirmAt.Add(10*time.Millisecond).Before(tm) || + if stream.lastConfirmAt.Add(20*time.Millisecond).Before(tm) || stream.receivedNumConfirmed+0 < stream.receivedNum { var confirm tl.Serializable if isV2 { @@ -351,8 +351,8 @@ 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) + if symbolsSent > enc.BaseSymbolsNum()+enc.BaseSymbolsNum()*2 { //+enc.BaseSymbolsNum()/2 + x := symbolsSent - (enc.BaseSymbolsNum() + enc.BaseSymbolsNum()*2) select { case <-ctx.Done(): diff --git a/adnl/storage/client.go b/adnl/storage/client.go index 67df2fd8..f8300b1e 100644 --- a/adnl/storage/client.go +++ b/adnl/storage/client.go @@ -40,6 +40,8 @@ type FileInfo struct { } type TorrentDownloader interface { + GetDescription() string + GetDirName() string ListFiles() []string GetFileOffsets(name string) *FileInfo DownloadPiece(ctx context.Context, pieceIndex uint32) (_ []byte, err error) @@ -127,14 +129,16 @@ type storageNode struct { // description:Text = TorrentInfo; type TorrentInfo struct { - PieceSize uint32 `tlb:"## 32"` - FileSize uint64 `tlb:"## 64"` - RootHash []byte `tlb:"bits 256"` - HeaderSize uint64 `tlb:"## 64"` - HeaderHash []byte `tlb:"bits 256"` + PieceSize uint32 `tlb:"## 32"` + FileSize uint64 `tlb:"## 64"` + RootHash []byte `tlb:"bits 256"` + HeaderSize uint64 `tlb:"## 64"` + HeaderHash []byte `tlb:"bits 256"` + Description tlb.Text `tlb:"."` } -func (c *Client) CreateDownloader(ctx context.Context, bagId []byte, desiredMinPeersNum, threadsPerPeer int) (_ TorrentDownloader, err error) { +func (c *Client) CreateDownloader(ctx context.Context, bagId []byte, desiredMinPeersNum, threadsPerPeer int, attempts ...int) (_ TorrentDownloader, err error) { + globalCtx, downloadCancel := context.WithCancel(context.Background()) var dow = &torrentDownloader{ client: c, @@ -154,7 +158,7 @@ func (c *Client) CreateDownloader(ctx context.Context, bagId []byte, desiredMinP }() // connect to first node - err = dow.scale(ctx, 1) + err = dow.scale(ctx, 1, 2) if err != nil { err = fmt.Errorf("failed to find storage nodes for this bag, err: %w", err) return nil, err @@ -273,6 +277,7 @@ func (s *storageNode) loop() { err = s.torrent.checkProofBranch(proof, piece.Data, uint32(req.index)) if err != nil { + time.Sleep(50 * time.Millisecond) return fmt.Errorf("proof branch check of piece %d failed: %w", req.index, err) } return nil @@ -539,7 +544,7 @@ func (t *torrentDownloader) checkProofBranch(proof *cell.Cell, data []byte, piec } // scale - add more nodes to pool, to increase load speed and capacity -func (t *torrentDownloader) scale(ctx context.Context, num int) error { +func (t *torrentDownloader) scale(ctx context.Context, num, attempts int) error { if num == 0 { return nil } @@ -549,7 +554,6 @@ func (t *torrentDownloader) scale(ctx context.Context, num int) error { connections := make(chan bool, num) checkedNodes := map[string]bool{} - attempts := 2 for { toCheck := make([]*overlay.Node, 0, len(t.knownNodes)) t.mx.RLock() @@ -713,7 +717,7 @@ func (t *torrentDownloader) scaleController() { t.mx.RUnlock() if peersNum < t.desiredMinPeersNum { - _ = t.scale(t.globalCtx, t.desiredMinPeersNum-peersNum) + _ = t.scale(t.globalCtx, t.desiredMinPeersNum-peersNum, 3) } } } @@ -733,3 +737,11 @@ func (t *torrentDownloader) ListFiles() []string { func (t *torrentDownloader) Close() { t.downloadCancel() } + +func (t *torrentDownloader) GetDirName() string { + return string(t.header.DirName) +} + +func (t *torrentDownloader) GetDescription() string { + return t.info.Description.Value +} diff --git a/adnl/storage/storage.go b/adnl/storage/storage.go index a9ea24cc..937ee539 100644 --- a/adnl/storage/storage.go +++ b/adnl/storage/storage.go @@ -131,6 +131,39 @@ func (t *TorrentHeader) Parse(data []byte) (_ []byte, err error) { } func (t *TorrentHeader) Serialize() ([]byte, error) { - //TODO implement me - return nil, fmt.Errorf("not implemented") + data := make([]byte, 20) + binary.LittleEndian.PutUint32(data[0:], t.FilesCount) + binary.LittleEndian.PutUint64(data[4:], t.TotalNameSize) + binary.LittleEndian.PutUint64(data[12:], t.TotalDataSize) + + fecData, err := tl.Serialize(t.FEC, true) + if err != nil { + return nil, err + } + data = append(data, fecData...) + + if t.DirNameSize != uint32(len(t.DirName)) { + return nil, fmt.Errorf("incorrect dir name size") + } + + dataDirNameSz := make([]byte, 4) + binary.LittleEndian.PutUint32(dataDirNameSz, t.DirNameSize) + data = append(data, dataDirNameSz...) + data = append(data, t.DirName...) + + for _, ni := range t.NameIndex { + iData := make([]byte, 8) + binary.LittleEndian.PutUint64(iData, ni) + data = append(data, iData...) + } + + for _, ni := range t.DataIndex { + iData := make([]byte, 8) + binary.LittleEndian.PutUint64(iData, ni) + data = append(data, iData...) + } + data = append(data, t.Names...) + data = append(data, t.Data...) + + return data, nil } diff --git a/tlb/loader.go b/tlb/loader.go index dd05ed7b..d9c34586 100644 --- a/tlb/loader.go +++ b/tlb/loader.go @@ -43,6 +43,14 @@ func LoadFromCell(v any, loader *cell.Slice, skipMagic ...bool) error { } rv = rv.Elem() + if ld, ok := v.(manualLoader); ok { + err := ld.LoadFromCell(loader) + if err != nil { + return fmt.Errorf("failed to load from cell for %s, using manual loader, err: %w", rv.Type().Name(), err) + } + return nil + } + for i := 0; i < rv.NumField(); i++ { structField := rv.Type().Field(i) parseType := structField.Type @@ -356,6 +364,14 @@ func ToCell(v any) (*cell.Cell, error) { rv = rv.Elem() } + if ld, ok := v.(manualStore); ok { + c, err := ld.ToCell() + if err != nil { + return nil, fmt.Errorf("failed to store to cell for %s, using manual storer, err: %w", reflect.TypeOf(v).PkgPath(), err) + } + return c, nil + } + builder := cell.BeginCell() for i := 0; i < rv.NumField(); i++ { @@ -483,7 +499,11 @@ func ToCell(v any) (*cell.Cell, error) { switch parseType { case reflect.TypeOf(&cell.Cell{}): - c = fieldVal.Interface().(*cell.Cell) + if fieldVal.IsNil() { + c = cell.BeginCell().EndCell() + } else { + c = fieldVal.Interface().(*cell.Cell) + } default: c, err = structStore(fieldVal, structField.Type.Name()) if err != nil { @@ -566,18 +586,10 @@ func structLoad(field reflect.Type, loader *cell.Slice) (reflect.Value, error) { } nVal := reflect.New(newTyp) - inf := nVal.Interface() - if ld, ok := inf.(manualLoader); ok { - err := ld.LoadFromCell(loader) - if err != nil { - return reflect.Value{}, fmt.Errorf("failed to load from cell for %s, using manual loader, err: %w", field.Name(), err) - } - } else { - err := LoadFromCell(nVal.Interface(), loader) - if err != nil { - return reflect.Value{}, fmt.Errorf("failed to load from cell for %s, err: %w", field.Name(), err) - } + err := LoadFromCell(nVal.Interface(), loader) + if err != nil { + return reflect.Value{}, fmt.Errorf("failed to load from cell for %s, err: %w", field.Name(), err) } if field.Kind() != reflect.Ptr { @@ -590,14 +602,6 @@ func structLoad(field reflect.Type, loader *cell.Slice) (reflect.Value, error) { func structStore(field reflect.Value, name string) (*cell.Cell, error) { inf := field.Interface() - if ld, ok := inf.(manualStore); ok { - c, err := ld.ToCell() - if err != nil { - return nil, fmt.Errorf("failed to store to cell for %s, using manual storer, err: %w", name, err) - } - return c, nil - } - c, err := ToCell(inf) if err != nil { return nil, fmt.Errorf("failed to store to cell for %s, err: %w", name, err) diff --git a/tlb/text.go b/tlb/text.go new file mode 100644 index 00000000..524b0add --- /dev/null +++ b/tlb/text.go @@ -0,0 +1,103 @@ +package tlb + +import ( + "fmt" + "github.com/xssnick/tonutils-go/tvm/cell" +) + +const MaxTextChunkSize = 127 - 2 + +type Text struct { + MaxFirstChunkSize uint8 + Value string +} + +func (t *Text) LoadFromCell(loader *cell.Slice) error { + num, err := loader.LoadUInt(8) + if err != nil { + return fmt.Errorf("failed to load chunks num: %w", err) + } + + firstSz := uint8(0) + var res string + for i := 0; i < int(num); i++ { + ln, err := loader.LoadUInt(8) + if err != nil { + return fmt.Errorf("failed to load len of chunk %d: %w", i, err) + } + + if i == 0 { + firstSz = uint8(ln) + } + + data, err := loader.LoadSlice(uint(ln * 8)) + if err != nil { + return fmt.Errorf("failed to load data of chunk %d: %w", i, err) + } + res += string(data) + + if i < int(num)-1 { + loader, err = loader.LoadRef() + if err != nil { + return fmt.Errorf("failed to load next chunk of chunk %d: %w", i, err) + } + } + } + + t.Value = res + t.MaxFirstChunkSize = firstSz + return nil +} + +func (t Text) ToCell() (*cell.Cell, error) { + if len(t.Value) == 0 { + return cell.BeginCell().MustStoreUInt(0, 8).EndCell(), nil + } + + if t.MaxFirstChunkSize > MaxTextChunkSize { + return nil, fmt.Errorf("too big first chunk size") + } + if t.MaxFirstChunkSize == 0 { + return nil, fmt.Errorf("first chunk size should be > 0") + } + + val := []byte(t.Value) + leftSz := len(val) - int(t.MaxFirstChunkSize) + chunksNum := 1 + if leftSz > 0 { + chunksNum += leftSz / MaxTextChunkSize + if leftSz%MaxTextChunkSize > 0 { + chunksNum++ + } + } + + if chunksNum > 255 { + return nil, fmt.Errorf("too big data") + } + + var f func(depth int) *cell.Builder + f = func(depth int) *cell.Builder { + c := cell.BeginCell() + sz := uint8(MaxTextChunkSize) + if depth == 0 { + sz = t.MaxFirstChunkSize + } + if int(sz) > len(val) { + sz = uint8(len(val)) + } + + c.MustStoreUInt(uint64(sz), 8) + c.MustStoreSlice(val[:sz], uint(sz)*8) + val = val[sz:] + + if depth != chunksNum-1 { + c.MustStoreRef(f(depth + 1).EndCell()) + } + return c + } + + return cell.BeginCell(). + MustStoreUInt(uint64(chunksNum), 8). + MustStoreBuilder(f(0)). + EndCell(), nil +} diff --git a/tlb/text_test.go b/tlb/text_test.go new file mode 100644 index 00000000..747bba56 --- /dev/null +++ b/tlb/text_test.go @@ -0,0 +1,37 @@ +package tlb + +import ( + "testing" +) + +func TestText(t *testing.T) { + for _, s := range []string{ + "Hello garage,Hello garage,Hello garage,Hello garage,Hello " + + "garage,Hello garage,Hello garage,Hello garage,Hello garage,Hello garage", + "1", "", string(make([]byte, 1024*30)), + } { + txt := &Text{ + MaxFirstChunkSize: 30, + Value: s, + } + + cl, err := ToCell(txt) + if err != nil { + t.Fatal(err) + } + + txt2 := Text{} + err = LoadFromCell(&txt2, cl.BeginParse()) + if err != nil { + t.Fatal(err) + } + + if txt.Value != txt2.Value { + t.Fatal("incorrect value, want", txt.Value, "got", txt2.Value) + } + + if int(txt2.MaxFirstChunkSize) > len(txt2.Value) { + t.Fatal("incorrect sz, want", len(txt2.Value), "got", txt2.MaxFirstChunkSize) + } + } +} diff --git a/ton/jetton/integration_test.go b/ton/jetton/integration_test.go index e7103a40..4e37b278 100644 --- a/ton/jetton/integration_test.go +++ b/ton/jetton/integration_test.go @@ -73,6 +73,15 @@ func TestJettonMasterClient_GetWalletAddress(t *testing.T) { } } +func TestJettonMasterClient_Mint(t *testing.T) { + tt, err := tlb.ToCell(MintPayload{}) + if err != nil { + t.Fatal(err) + } + + println(tt.Dump()) +} + func TestJettonMasterClient_Transfer(t *testing.T) { cli := NewJettonMasterClient(api, address.MustParseAddr("EQAbMQzuuGiCne0R7QEj9nrXsjM7gNjeVmrlBZouyC-SCLlO")) diff --git a/ton/jetton/jetton.go b/ton/jetton/jetton.go index 851cc032..dce56613 100644 --- a/ton/jetton/jetton.go +++ b/ton/jetton/jetton.go @@ -18,12 +18,19 @@ type TonApi interface { RunGetMethod(ctx context.Context, blockInfo *ton.BlockIDExt, addr *address.Address, method string, params ...any) (*ton.ExecutionResult, error) } +type MintPayloadMasterMsg struct { + Opcode uint32 `tlb:"## 32"` + QueryID uint64 `tlb:"## 64"` + JettonAmount tlb.Coins `tlb:"."` + RestData *cell.Cell `tlb:"."` +} + type MintPayload struct { - _ tlb.Magic `tlb:"#00000001"` - QueryID uint64 `tlb:"## 64"` - Index uint64 `tlb:"## 64"` - TonAmount tlb.Coins `tlb:"."` - Content *cell.Cell `tlb:"^"` + _ tlb.Magic `tlb:"#00000015"` + QueryID uint64 `tlb:"## 64"` + ToAddress *address.Address `tlb:"addr"` + Amount tlb.Coins `tlb:"."` + MasterMsg MintPayloadMasterMsg `tlb:"^"` } type Data struct { diff --git a/ton/jetton/wallet.go b/ton/jetton/wallet.go index f8c5b1f5..a1ca6a86 100644 --- a/ton/jetton/wallet.go +++ b/ton/jetton/wallet.go @@ -48,7 +48,7 @@ func (c *WalletClient) GetBalance(ctx context.Context) (tlb.Coins, error) { } func (c *WalletClient) GetBalanceAtBlock(ctx context.Context, b *ton.BlockIDExt) (tlb.Coins, error) { - res, err := c.master.api.RunGetMethod(ctx, b, c.addr, "get_wallet_data") + res, err := c.master.api.WaitForBlock(b.SeqNo).RunGetMethod(ctx, b, c.addr, "get_wallet_data") if err != nil { if cErr, ok := err.(ton.ContractExecError); ok && cErr.Code == ton.ErrCodeContractNotInitialized { return tlb.Coins{}, nil diff --git a/ton/nft/item-editable.go b/ton/nft/item-editable.go index b8098373..80eafdea 100644 --- a/ton/nft/item-editable.go +++ b/ton/nft/item-editable.go @@ -36,7 +36,7 @@ func (c *ItemEditableClient) GetEditor(ctx context.Context) (*address.Address, e } func (c *ItemEditableClient) GetEditorAtBlock(ctx context.Context, b *ton.BlockIDExt) (*address.Address, error) { - res, err := c.api.RunGetMethod(ctx, b, c.addr, "get_editor") + res, err := c.api.WaitForBlock(b.SeqNo).RunGetMethod(ctx, b, c.addr, "get_editor") if err != nil { return nil, fmt.Errorf("failed to run get_editor method: %w", err) } @@ -55,9 +55,17 @@ func (c *ItemEditableClient) GetEditorAtBlock(ctx context.Context, b *ton.BlockI } func (c *ItemEditableClient) BuildEditPayload(content ContentAny) (*cell.Cell, error) { - con, err := content.ContentCell() - if err != nil { - return nil, err + var con *cell.Cell + switch cnt := content.(type) { + case *ContentOffchain: + // we have exception for offchain, it is without prefix + con = cell.BeginCell().MustStoreStringSnake(cnt.URI).EndCell() + default: + var err error + con, err = content.ContentCell() + if err != nil { + return nil, err + } } body, err := tlb.ToCell(ItemEditPayload{ diff --git a/ton/nft/item.go b/ton/nft/item.go index 1035f799..6c5a1306 100644 --- a/ton/nft/item.go +++ b/ton/nft/item.go @@ -55,7 +55,7 @@ func (c *ItemClient) GetNFTData(ctx context.Context) (*ItemData, error) { } func (c *ItemClient) GetNFTDataAtBlock(ctx context.Context, b *ton.BlockIDExt) (*ItemData, error) { - res, err := c.api.RunGetMethod(ctx, b, c.addr, "get_nft_data") + res, err := c.api.WaitForBlock(b.SeqNo).RunGetMethod(ctx, b, c.addr, "get_nft_data") if err != nil { return nil, fmt.Errorf("failed to run get_nft_data method: %w", err) } diff --git a/tvm/cell/builder.go b/tvm/cell/builder.go index bcc1c228..2fb9eceb 100644 --- a/tvm/cell/builder.go +++ b/tvm/cell/builder.go @@ -565,12 +565,9 @@ func BeginCell() *Builder { } func (b *Builder) EndCell() *Cell { - // copy data - data := append([]byte{}, b.data...) - return &Cell{ bitsSz: b.bitsSz, - data: data, + data: append([]byte{}, b.data...), // copy data refs: b.refs, } } diff --git a/tvm/cell/cell.go b/tvm/cell/cell.go index 1c971cc3..670fc123 100644 --- a/tvm/cell/cell.go +++ b/tvm/cell/cell.go @@ -2,28 +2,33 @@ package cell import ( "crypto/ed25519" - "crypto/sha256" + "encoding/base64" "encoding/hex" "fmt" + "strconv" "strings" ) const ( - _OrdinaryType = 0x00 - _PrunedType = 0x01 - _LibraryType = 0x02 - _MerkleProofType = 0x03 - _UnknownType = 0xFF + _OrdinaryType = 0x00 + _PrunedType = 0x01 + _LibraryType = 0x02 + _MerkleProofType = 0x03 + _MerkleUpdateType = 0x04 + _UnknownType = 0xFF ) +const maxDepth = 1024 + type Cell struct { - special bool - level byte - bitsSz uint - index int - data []byte + special bool + levelMask LevelMask + bitsSz uint + index int + data []byte - cachedHash []byte + hashes []byte + depthLevels []uint16 refs []*Cell } @@ -38,12 +43,13 @@ func (c *Cell) copy() *Cell { } return &Cell{ - special: c.special, - level: c.level, - bitsSz: c.bitsSz, - data: data, - cachedHash: c.cachedHash, - refs: refs, + special: c.special, + levelMask: c.levelMask, + bitsSz: c.bitsSz, + data: data, + hashes: c.hashes, + depthLevels: c.depthLevels, + refs: refs, } } @@ -52,11 +58,11 @@ func (c *Cell) BeginParse() *Slice { data := append([]byte{}, c.data...) return &Slice{ - special: c.special, - level: c.level, - bitsSz: c.bitsSz, - data: data, - refs: c.refs, + special: c.special, + levelMask: c.levelMask, + bitsSz: c.bitsSz, + data: data, + refs: c.refs, } } @@ -79,15 +85,25 @@ func (c *Cell) RefsNum() uint { return uint(len(c.refs)) } -func (c *Cell) Dump() string { - return c.dump(0, false) +func (c *Cell) Dump(limitLength ...int) string { + var lim = (1024 << 20) * 16 + if len(limitLength) > 0 { + // 16 MB default lim + lim = limitLength[0] + } + return c.dump(0, false, lim) } -func (c *Cell) DumpBits() string { - return c.dump(0, true) +func (c *Cell) DumpBits(limitLength ...int) string { + var lim = (1024 << 20) * 16 + if len(limitLength) > 0 { + // 16 MB default lim + lim = limitLength[0] + } + return c.dump(0, true, lim) } -func (c *Cell) dump(deep int, bin bool) string { +func (c *Cell) dump(deep int, bin bool, limitLength int) string { sz, data, _ := c.BeginParse().RestBits() var val string @@ -107,8 +123,8 @@ func (c *Cell) dump(deep int, bin bool) string { } str := strings.Repeat(" ", deep) + fmt.Sprint(sz) + "[" + val + "]" - if c.level > 0 { - str += fmt.Sprintf("{%d}", c.level) + if c.levelMask.getLevel() > 0 { + str += fmt.Sprintf("{%d}", c.levelMask.getLevel()) } if c.special { str += "*" @@ -116,26 +132,31 @@ func (c *Cell) dump(deep int, bin bool) string { if len(c.refs) > 0 { str += " -> {" for i, ref := range c.refs { - str += "\n" + ref.dump(deep+1, bin) + str += "\n" + ref.dump(deep+1, bin, limitLength) if i == len(c.refs)-1 { str += "\n" } else { str += "," } + + if len(str) > limitLength { + break + } } - str += strings.Repeat(" ", deep) - return str + "}" + str += strings.Repeat(" ", deep) + "}" + } + + if len(str) > limitLength { + str = str[:limitLength] } + return str } +const _DataCellMaxLevel = 3 + func (c *Cell) Hash() []byte { - if c.cachedHash == nil { - hash := sha256.New() - hash.Write(c.serializeHash()) - c.cachedHash = hash.Sum(nil) - } - return c.cachedHash + return c.getHash(_DataCellMaxLevel) } func (c *Cell) Sign(key ed25519.PrivateKey) []byte { @@ -151,10 +172,6 @@ func (c *Cell) getType() int { } switch c.data[0] { - case _MerkleProofType: - if c.RefsNum() == 1 && c.BitsSize() == 280 { - return _MerkleProofType - } case _PrunedType: if c.BitsSize() >= 288 { lvl := uint(c.data[1]) @@ -162,6 +179,14 @@ func (c *Cell) getType() int { return _PrunedType } } + case _MerkleProofType: + if c.RefsNum() == 1 && c.BitsSize() == 280 { + return _MerkleProofType + } + case _MerkleUpdateType: + if c.RefsNum() == 2 && c.BitsSize() == 552 { + return _MerkleUpdateType + } case _LibraryType: if c.BitsSize() == 8+256 { return _LibraryType @@ -169,3 +194,26 @@ func (c *Cell) getType() int { } return _UnknownType } + +func (c *Cell) UnmarshalJSON(bytes []byte) error { + if len(bytes) < 2 || bytes[0] != '"' || bytes[len(bytes)-1] != '"' { + return fmt.Errorf("invalid data") + } + bytes = bytes[1 : len(bytes)-1] + + data, err := base64.StdEncoding.DecodeString(string(bytes)) + if err != nil { + return err + } + + cl, err := FromBOC(data) + if err != nil { + return err + } + *c = *cl + return nil +} + +func (c *Cell) MarshalJSON() ([]byte, error) { + return []byte(strconv.Quote(base64.StdEncoding.EncodeToString(c.ToBOC()))), nil +} diff --git a/tvm/cell/cell_test.go b/tvm/cell/cell_test.go index d472bd6d..0ada4afa 100644 --- a/tvm/cell/cell_test.go +++ b/tvm/cell/cell_test.go @@ -3,7 +3,9 @@ package cell import ( "bytes" "crypto/ed25519" + "encoding/base64" "encoding/hex" + "encoding/json" "github.com/xssnick/tonutils-go/address" "math/big" "testing" @@ -119,6 +121,20 @@ func TestCell_Dump(t *testing.T) { } } +func TestCell_DumpBomb(t *testing.T) { + boc, _ := base64.StdEncoding.DecodeString("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") + c, err := FromBOC(boc) + if err != nil { + t.Fatal(err) + } + + str := c.Dump(8 << 20) + if len(str) != 8<<20 { + println(str) + t.Fatal("not eq lim len", len(str), 8<<20) + } +} + func TestVarAddr(t *testing.T) { for addrType, str := range map[address.AddrType]string{ address.NoneAddress: "b5ee9c724101010100030000012094418655", @@ -153,8 +169,57 @@ func BenchmarkHash(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { - c.cachedHash = nil + c.hashes = nil _ = c.Hash() } b.StopTimer() } + +func TestCell_UnmarshalJSON(t *testing.T) { + boc, _ := hex.DecodeString("b5ee9c72e20201380001000028250000002400cc00ea01c402a603420374039603a503be03d8044804b8050405ac05ec065606a2076e078e0824084208600880089e08bc08da08f80916093009d80a180b0a0b7a0bc70bea0c0e0cba0cda0cfa0d1a0d380d560d740d900dac0dc80e6e0ef20f160f360f820fce0fee100e102e104c106c108c10ac10cc10ec1196121e12841306132413421360137c142014a014ae14bc14ca14d814e614f415021510151e152c153a1548155615a215b015be15cc15da15e815f6160416ba16c816d616e416f21700170e171c172a1738178417a817cc181918c418e419041951199d19bc19da1a271a731a901aae1afb1b471b621b7e1bcb1c171c321cd81d251da81df51e471e921eb21eff1f4b1f6a1f8a1fd71ff620142061208020cd20ec210c2159217821c52211223022da232723ae23fb246024ad24f9257a25c725e42631264e269b26b827052751276c2810285d28dc2929297529c12a0d2ad82b252b442b522b9f2bbc2c092c262c732c922cdf2cfc2d492d662db32dd02e1d2e3a2e872ea42ec22f702fbd3009309e30ac30ba31073114316131ad31ba31c832153222326f327c32c9331533223330337d33c933d633e4343134e634f435aa35b8366c3720372e377b378837d537e237f037fe384b385838a538b238ff390c3959396639b339c03a0d3a1a3a673a743ac13b383bec3c393c463c543ca13cae3cfb3d473d543da13dae3dbc3e093e163ecd3f823fcf3fda3fe0402e405640aa40b740fa410441e84200420e421d422c423c42e043884430443c444844ce458e4614462646ca478b4794481a483648e74988499449a04a264ae64b6c4b7e4c3e4cc44cd64d7b4de94ea84eaf4f354f464fea504b041011ef55aaffffff11000100020003000401a09bc7a98700000000040101485c0d0000000100ffffffff000000000000000062b2ca7f00001a5752b3ec0000001a5752b3ec042d722c2c0004ee1f01485c09014820d8c400000003000000000000002e00050211b8e48dfb4a0eebb004000600071a8a03482793f3b50aaf5c1948a7daea6509532374f902fe6abeff45f620cb8c99cb00130443feeb6ff454dd7b749d28f509060c2cc668963d39733cf0e844a2880964938114c1372a89491186e2103aabb88851625b18674a78929f3c357ca78e98824227016e016e000b000c1489b85d301a5e194c97f1c275d3ead4bd74c6e4f42bc74ff8ef930e594b8dd9887900084a33f6fda87b6952196d2ab3a76db9754f0792826c8eb9650220c062ca9afc456d18d616915f7809f0e2a9af47f9b741c0d1567a8cc87eb371ae985206197a819b2896f2c00109010a010b010c009800001a5752a4a9c401485c0c493505d505412a231566c8853321bb16a8d291b5c091b6b89f7fb0f4470293ca35028007ab3f46b45071071ce8368191ef3b54ec0db1ad04e1ad4b8e254c89e4022581f2cf4db6b621cfac0f967a6e06286bfd400800080008001d43b9aca00250775d8011954fc400080201200009000a0015be000003bcb355ab466ad00015bfffffffbcbd0efda563d0245b9023afe2ffffff1100ffffffff000000000000000001485c0c0000000162b2ca7c00001a5752a4a9c401485c0960000d000e000f0010245b9023afe2ffffff1100ffffffff000000000000000001485c0d0000000162b2ca7f00001a5752b3ec0401485c0960001d001e001f0020284801010190c062d880448c7c066d5e7f424d3c899b5b7618f97ecf7c54e38dd4c8b25a00013213eaca33f4cbadbd5448172264067fbb377adbe16d7c4681fb55312783f206756a318a5c723a464c78c863a17c0802276f4114c4c8edf27dd9eb88622de8445b07016d00118207cb3d36dad8873eb00023008422330000000000000000ffffffffffffffff81f2cf4db6b621cfa828008400222455cc26aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac23224a27d088a237e001100ac001200ae28480101553f5abf307236fc3eae3b9829943a1b4f895241d07cd2f7fb7106f492a37a4f000222bf000193a937010004ee1f6000034aea52acf0880000d29bdd9fc8200a4106c7d59f0086cde0d05cc99baaddf7a9c5b890aa805e8c96680523524fb4a5a99ff81257e3afd8721409a6a81f22735c0e33400aeb6e7f0a1a5185bb3a3c9deba278be001300142213c3c0000695d4a559e12000b100153201c47dca7c0afc1021a7f8ac40f39fac4e3a34441c180afd0667ef533f08c042258d60f2282e7cde376f0df9fd5ebcc5d8bb19faf6cc57bbbb662485cd76bdce510010000c20004800492211480000d2ba94ab3c2400b30016221162000034aea52acf0900b500172213c480000d2ba94ab3c24000b700182211400000d2ba94ab3c2400b9001922110000034aea52acf09000bb001a22110000034aea52acf09000bd001b2212cc00001a575295678400bf001c2211400000d2ba94ab3c2400c300c4011100000000000000005000213213b82c6f6878b537db3c16dcd5f01397865949762fc38f51cb6aff56594f1062f1edf7f2f7d1549f1d22aab06d9a81c9d49510c3fa6d09dba89fcddb0b8576e4aa016d00118207cb3d37031435feb00068008422330000000000000000ffffffffffffffff81f2cf4dc0c50d7fa828008400223455ee67dd6327a64269ce346e9b1568ef56241f1ba5179cb6d2ac85c5c18646ae12717905e49ef1945ed5b3e03bab93b347378306bc631945e0355ad7507cbada29001b0010cc26aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac23224a2820ffffb7e010e00ac00ad00ae006bb0400000000000000000a42e0680000d2ba95254e1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc028480101173ea53338a2f89f176ad2d6129a90977e8c56e325d959bdf92b7171d9070911000423130103e59e9b6d6c439f580024006a00842313010217dee532a6595718002500260084331392afc8f7cde2b08b147ca948f16cc575bbbd4d383188441e172a5cbc617e05249b737c913fe069b3ce1ce7d550cb95f27f4d428faee54cd295c65c5b9ad7e3ae0027000e01014dad03493b489b5800310032008422130100ca31e1e96b10bbc80027006e2213010058b525488168d908006f0028221301003e470bb61928028800290072221100e1caf7c460aa04680073002a221100e1c6b40db0b41708002b0076221100e11e73378043a2080077002c220f00c021469bdc5408002d007a2210680c021412935422007b002e220f00c021383dbc6da8002f007e219dbceaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa81804265fa43649cc3b15c891620f7db60a685fb7054bcc7b8817f05a7e9b73acdede2200b57bfa000034aea549538700302277cff5555555555555555555555555555555555555555555555555555555555555555407aec10e79c0000000000000695d4a92a711804265fa436495d0008000812313010069341ebbd1eac97800330034008422130100e478e48d695dd1e8008500352848010199c07995a55880a2a97e2d1c235a7dfb6b24666e40158c97f7fafb18707d9e01002228480101a40b78c2e39cf3658d455ad29ca1f2bf10cdb87248a8d293e625f4ff821d3ffe001922130100cae245aca13c29a8003600882213010094b93dc1fd640ba800370038221301007e0f065474a37188008b0039221100f6aa376d88c09a280042009f221301007e00c5c8df3ca988008d003a221301007e00c226bc22c348003b0090221301007dfeec4e6f29f348003c0092221301007dfeec4db9634c680093003d221350401f7fbb08ed8733f2003e009621a1bcd9999999999999999999999999999999999999999999999999999999999998200fbfdd8431e351e0e7f7559f2be451b78ede8267e7a8cb24cabd8236aa272459ca146db75357e502000034aea5495385003f227bcff333333333333333333333333333333333333333333333333333333333333333340756c14c3f00000000000000695d4a92a70e00fbfdd8431e351e16d0009800402355ec05b6c5a0ba9cc563f4263b03448b2038580c84db168a6ea499c5ee5d19ddadc8351566240da1dbc38a3b009a009b00412179a062b1fa1362b37a13000080001d81a245901c2c06426d8b4537524ce2f72e8ceed6e41a8ab31206d0ede1c51dc00ebe70af3ef33d8313797763b95f20009d221100f6a49bd9e38f3928004300a1221100ea59d8cb334c9c08004400a3221100ea59cff28ceadfc800a40045220f00c108ba716ce988004600a7219bbd62f8f7bea30f8ab5e9f16c3fb8642b118f56ed1bdc49600dbe5220c8b1af9e040c474f8074f1d14ce894ceb17ddbe1eb5416175db816dcb3d9b86ecacfe305fc3df0baaa80000d2ba95254e1c00047236fcff34517c7bdf5187c55af4f8b61fdc321588c7ab768dee24b006df29106458d7cf21881f480000000000000695d4a92a7110311d3e017f000a900aa00ab220120004a00e2220120005e00c8220120004b00e4220120004c004d220120004e00e822012000f90056220120004f00ea220120005000ec220120005100ee220120005200f0220120005300f2220120005400f4220120005500f628480101a54022f5edbf4beba0648deeb1ffc566c63567b51e34ccd918ab339bdedfec330001220120005700fc220120005800fe22012000ff00592201200101005a220120005b0104220120005c0106220120005d010800b1bd24ee866df51003f6b534d22c17f58175a943d247f628a0d355a187e86e73bd18acb16cc00000000000002a80000001dd3de5878000001dde15bcd058acb28f0000000000000021800000014bff7ab78000001718cb2138a0220120005f00ca22012000cb0060220120006100ce220120006200d0220120006300d222012000d30064220120006500d622012000d70066220120006700da284801015dc67661a1c1ef1294e875961eea55bea7054c7101bc975ed0aa009be971719a000323130103e59e9b818a1aff580069006a00842313010217dee546c430b718006b006c0084284801013c24d22fb5e19c4c445ed87fd2f21aab05a39914a685834b6a1d0cf004891430016b33134d60714c9ba1d20d9c30bf39735d0ad7bfca9eb3ced50edfdae92a4c3339ef4d25e774489adf29b089b2e31b7a68d28886f450600fefbb6f581b3a94423ffc070027000e01014dad035d591ffb5800820083008422130100ca31e1e96b10bbc8006d006e2213010058b525488168d908006f007028480101256e458a2d80eecd799b387aa5e91b156bc583f9f095f0fcea7b2d73cb3d726e00262848010107983f5e2ef514d990c22499abec1ccb4fd222f7489b7febb2044fe24e39318d001a221301003e470bb61928028800710072221100e1caf7c460aa04680073007428480101d405a7172eafa75f0e67e1b0f52d000222f4399c5d7dda059af11c73faf135a300182848010189afd21725efab9748b8b6ef6dcfd892c9f647a939c73831d4f1dbc786b9ec690016221100e1c6b40db0b4170800750076221100e11e73378043a20800770078284801012b525231768abd4f2fc0464b8e5be011db5ebdb3a1ed6d61eebf186e8dc197ba001528480101b81fe7658c95d0f97eabe671d2b147f2da908bd4813119d475886872068353200014220f00c021469bdc54080079007a2210680c021412935422007b007c284801018916fffc4697fa71347a36028c9c0ab1b54bf2c278f9171f7837101f990665f2001128480101a248b81f22333cc28f6b6744e4298aefcd9b6f2dc5d7c99e1da1b28c37f3aa0c0007220f00c021383dbc6da8007d007e219dbceaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa81804265fa43649c9d338c13843e706c68e2455ed70bbf93820f28a394eec0b616e855fce8e6cac000034aea567d807007f284801010143b3d2dd671b2559543155e003f847022e510b3a57afabbca05d4069c327ef000d2277cff5555555555555555555555555555555555555555555555555555555555555555407aec10e79c0000000000000695d4acfb011804265fa436495d0008000812848010164a43970f2007a1da6d6fc81773cc095d1cc270e81359e471f3b03469abeb7b5000c214900000027cbb9d1062954439a83a91f27835fb9d2e3e798910356650c3c493c94623464684000ac28480101afccd0b5d74a6fad14fcb8652b14eea3d3d7ad0226c0961aa9992a1f0c2997c1002322130100e478e4a1873531e80085008628480101a5a7d24057d8643b2527709d986cda3846adcb3eddc32d28ec21f69e17dbaaef0001284801016161938bf6cbbbea618b3c71572f00d9392090c9f15e70d1ef9080503b42229c002322130100cae245c0bf1389a8008700882213010094b93dd61b3b6ba80089008a28480101b4d986be6da4a385ebc4d75e7b6864ddae73e6e6431f711cbb3208dfb35945750024221301007e0f0668927ad188008b008c221100f6aa376d88c09a28009e009f284801012234afc4f9aa54d36f371ab851ada0446bbab533a15161ca6f4afd77d69d89520012221301007e00c5dcfd140988008d008e2848010103f584b35917a807e8a2ec65376c8ed2c6ccecf273ba8cb55dbacfe29a84a6140013221301007e00c23ad9fa2348008f0090221301007dfeec628d0153480091009228480101735d9d54218cf8454bd30b70a53ae0de4219178b78f457b844f50dc11b5719f00013221301007dfeec61d73aac6800930094284801017cc50a2d61f5f6979db4641a9451edade1fb234111081a83333a2b03ce9dc373000a2848010110cac8bd77fa246c2bc95d0269759b612312bbc0438ed5eb7dd53116e55e9020000b221350401f7fbb0df4fd0bf20095009621a1bcd9999999999999999999999999999999999999999999999999999999999998200fbfdd86b59e3de15e7c728be36b6cb9aa8d1663f3d4c1e962ab20459e6e56ae211a233bc1e24f52000034aea567d80500972848010150725eee52e86432f846698a08ac153a67bc9ad9c160130af907c3bef05f29480007227bcff333333333333333333333333333333333333333333333333333333333333333340756c14c3f00000000000000695d4acfb00e00fbfdd86b59e3de16d000980099284801016217f872c99fafcb870f2c11a362f59339be95095f70d00b9cff2f6dcd69d3dd000e2355ec05b6c5a0ba9cc563f4263b03448b2038580c84db168a6ea499c5ee5d19ddadc8351566240da1dbc38a3b009a009b009c28480101fb16d1ca45ecb8d4d1f6b1ac903c630cc06f78334bc9b84bf30585e9422cb887000b284801018f1bd34960aa509ff15ef8c648fdcb942bb7a6c14bda5d4988792ce1c7800bee00062179a062b1fa1362b37a13000080001d81a245901c2c06426d8b4537524ce2f72e8ceed6e41a8ab31206d0ede1c51dc00ebe70af3ef33d831379c7db16df20009d28480101e9988188b13457c31092fcc241e6b801ab7dc39b30a0923557a193630fef257f000a221100f6a49bd9e38f392800a000a128480101c6100af2020b8ed627f48ec736f2cfa52e095b513479105a028f40e152c9586f0016221100ea59d8cb334c9c0800a200a3284801011e20584a9cf50091fb4dddfe4d2e98f8c879438cc843e24314604b44cb6f78580012221100ea59cff28ceadfc800a400a52848010141d3f8101f423d32b2cdb23d98d0f34f83db175e427605500093f4a31cd8df03000b28480101e2bc337ece7f3af5171f3265f44c612fc2fcba87f4b4563dc7fdc3285dd6a44d0008220f00c108ba716ce98800a600a7219bbd62f8f7bea30f8ab5e9f16c3fb8642b118f56ed1bdc49600dbe5220c8b1af9e040c474f800b0df7369fde2be70ee4d2e84a552cab4b035685314de3d2e8e12b8d25aa27ae80000d2ba959f601c000a8284801018e634c5cb159b3914109244a95171c97fe56c7ad67ec709cce94bb067893af680007236fcff34517c7bdf5187c55af4f8b61fdc321588c7ab768dee24b006df29106458d7cf21881f480000000000000695d4acfb0110311d3e017f000a900aa00ab284801017269fb9feb45d719ebdbc3b0816b987bab06f43378dc84dc84d55727905482140002004811fd096c000000000000000000000000000000000000000000000000000000000000000028480101986c49971b96062e1fba4410e27249c8d73b0a9380f7ffd44640167e68b215e8000328480101b4ff459f14a389ff7d6ea967ec8d5329f3cff84a787a7c1fcb6e3d447b6175e5001022bf000193a937010004ee1f6000034aea549538880000d29bdd9fc8200a4106c7d59f0086cde0d05cc99baaddf7a9c5b890aa805e8c96680523524fb4a5a99ff81257e3afd8721409a6a81f22735c0e33400aeb6e7f0a1a5185bb3a3c9deba278be00af00b028480101b20e36a3b36a4cdee601106c642e90718b0a58daf200753dbb3189f956b494b600012213c3c0000695d4a92a712000b100b222012000c500c628480101258d602eaa21d621634dcf86692aeae308ff3cf888f3edafc6a5b21848d732f900182211480000d2ba95254e2400b300b4284801014b01ebcf5425735461aa8b83bae89e70fa21e95d2ee85e57b05dad26c1d6d5300016221162000034aea549538900b500b6284801019523e298bdc5f691343d880493b8a6451f3f941c985a7c4a167ba0e1cdb4599600132213c480000d2ba95254e24000b700b82848010137844b3a6262ee12ef028d6b8968b779c5be75d93a7dc1838aba9408a360ea42000e2211400000d2ba95254e2400b900ba28480101ce05363b2c4d123e6af0a2c3edbe06e05e4b55117180062e69624e00f64ae2a7000c22110000034aea5495389000bb00bc28480101aad2366c8dcad53c429dfbea0cd1c479cc6b989ef981314b1382fd58de7c8afd000b22110000034aea5495389000bd00be284801015af875e56b2c21860165b66c58883a203401027a269c372667df9eb27b476259000a2212cc00001a5752a4a9c400bf00c0284801013eb4f392dec5652b5e530a0922b5533c816263d761c0775349d4265cd85bd761000322110000034aea5495389000c100c222110000034aea52acf09000c300c400a9d00000695d4a92a710000034aea54953880290b818926a0baa0a8254462acd910a6643762d51a5236b81236d713eff61e88e0527946a05000f567e8d68a0e20e39d06d0323de76a9d81b635a09c35a971c4a9913c9284801016e527b5263548e810db4a6e4a1f87c5c20d1c7859c8a5e2113127c954400e7b900012848010192f515d5126f2a2fe83d0204780eb95ac49143dae652a6a618f650997621e09a00013201d970752cf49fb39f7ea882f429ef6a8a5ce3eaf9ff4d35bfba6d93ffc9e2a7368180f9da67bd40c08ccea6759238e3e79b631d22aa1dc395bbcec337be6d4e84000f000c2000e100e222012000c700c822012000c900ca284801012a31c24fa32c257e4912fc641c19d5679a5359fb89020b7fe030e3b104be570f000e22012000cb00cc28480101f8b1119a3146337e09b6a8bbd36f93c78f448ea18e0591af0a3726033c7b5345000c2848010136a19e11f370f7b9cf36d83e0cdb68ea6f02ebe02487e3a5ca264a2baa6cd0d8000c22012000cd00ce22012000cf00d0284801015fb934835d63076694fbd0ca2191f6219a2017bfee8370008d48729374463b93000b22012000d100d228480101d98bd6122c9ac0cd3bb56dd7127ca09ede8c606fcb15de38baa4b47f7fe51f04000922012000d300d4284801011bd2caf8f41ad27d29c9c0d34e01bc0457050b3e5cce1754ec06a5fef2e688af000828480101d3073a3cbcbb4a650ca94a719590f8460e5f51d6081fcea01e7e51cfbc60a444000622012000d500d622012000d700d828480101ab9eb8899afa5c8bb9368d94781cf3ebb2eac7fb017a2b6133f9bb7b0e5d7b85000528480101664d4a2f536f0763f76857ceb94de20b4fad7e80b85675b3d02cf79a319b467e000122012000d900da02012000db00dc284801010f0ee7d20301abe555b4a76a6ada745283de3664ecb09ba794fec4d44db79b68000300b1bd1148769c03366e79fc451b06179e791641db92df408fd46e6ffe7ad24d90a90000000000000000000000000000000000000000000000000000000018a8fe848000000000000014000000006740cdc84000000b659904b9a002012000dd00de00b1bce1814fe876c4b4657ef4585f2f9b9158201e2fc77698c43e68104b88fe4ab2314e7cf70000000000000066800000039c368aef00000040a8c4d343b14e7b67800000000000003b000000040b2e294300000028134825e0c002015800df00e000afbc6d1a11a99e030e7005cac69de1a3cdeea9807743355fbf293f897e485ac868c54bf1b80000000000000124000000091627b10a000000bd5670281ec54bf2a8000000000000011400000006eaf842ee000000a8472576d100afbc6f013e1c5535e8ff36e8381a2acf51990fd66e35d30a40c32f50336512de48c56594fe00000000000001440000000ce66b410a000000d9c852602cc565938800000000000000d40000000de9bcb146000000935ab614cd22012000e300e4284801014356834a429921990e0c978c594d3d7734b7cd96dd66c057aa786ccf4f5a258c000e22012000e500e62848010134a3bc4b6c672bb70328222747df9434117f6ee5953dc9447b945d42862c585f000c22012000e700e822012000f900fa22012000e900ea284801016b54a1df1176e4608f655e63cf51ff3deec889dcf9229cceb100c32a12d3288e000922012000eb00ec284801015d23a19db6638756e655885d66767ab5ca9c7044b8d6263da018629630bda2a5000822012000ed00ee284801014e72d9d9406765832f323a591918a7b8cdb1acdbcbdeab3f2bcbc4c93fbc1f99000922012000ef00f02848010138b4953e5411a45c37899ce1936780cec049051f53d3a16bad52de7c7609476e000622012000f100f228480101a9cf62c6624684d83857a4381d9d525efe2e2d5bafd839d9893f80beeaa43f70000522012000f300f428480101d1a07de28968f21c0c3cfe0e69605731ae68b71969d27dd270f99b4079019904000222012000f500f6284801018d455e04c00fba1289866da39c0793758c8d6a4e41ca2dd5ec2c03c76df76c42000302014800f700f82848010122863015f2ff93c8dfe54f980440d8b3b2bd946770d126f32325fc9c8e37cb2e00020073de48c56594fe000000000290b818000004b380fb4ef8000090e500f4af24c56594fe00000000153ae540000004efd6476f22000098db0ac6ce1f00b0bc914560076a5a3e2f68770608073c7920cd095fd22f6624cdc5631150352c9400000000000000000000000000000000000000000000000000000000629df4ce000000000000004000000004a9d9195a0000002b5c8e819328480101d2fc779057e6ebb9d8413f029999ad6426bdc42b841578ba4f1302565b3bb3b8000a22012000fb00fc22012000fd00fe2848010166827a7a38f195b34597175fc019e4dc80b6f714f0b51a2369270417305747e1000922012000ff010028480101cef4f4863fe525820f12b7c79bbc2276d0fdb7e300c55d70d809bfdef1e801f5000728480101c76ad89ed6929dfcdcbaaa4f5720981c659c02b305514971803760369f7017650006220120010101022848010138e8cca37bb83b168adc87a7037092d90c734af074cace4ae828e0cf82b67b30000422012001030104220120010501062848010150d30f1a3fd6f709f0627934b2b31be081dc25bc52b51a353f850efa09ad224d00022201200107010800b1bd635fefc47229e53988915685933558fd7303875055c5949d871dc2688df796800000000000000000000000000000000000000000000000000000000c54be8f6000000000000007e0000000e610f41dc000000593a932aa9000b1bd24ee866df51003f6b534d22c17f58175a943d247f628a0d355a187e86e73bd18acb16cc00000000000002a80000001dd3de5878000001dde15bcd058acb29fc000000000000021c0000001814ffc698000001752c06e7e202848010160ce52c8bd8ed7f87a7643812f7690467a604fcff9ba1811d065a34a0202d78f000201038020010d00010211011366ea62c6b62574b18990480a15bd04daf2d4d5c8e3413a8f62b0ff533b259b00078201150317cca5687735940043b9aca004010e010f01100247a00d9b55c39995181e04934d61a2baf0f5aa35a4e059bb4c55f309227aee336c95200610011401210103d0400111003fb000000000400000000000000021dcd650010ee6b280087735940043b9aca004010150011301db500cc320a00a42e0680000d2ba95254e000000d2ba95254e0850d8a46888759f13cd17310cd46d6e9e821d494944c72f183259690def72a1a5a15278d7e6ad8df707446b0df93af44f8aca68c8a6c286121567c529e8dd7088800027779c00000000000000000a42e04b159653da0112001343b9aca0021dcd6500200201610114012101064606000125020340400116011702037604011801190297bf955555555555555555555555555555555555555555555555555555555555555502aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad000000695d4acfb00c1013201340397beb33333333333333333333333333333333333333333333333333333333333333029999999999999999999999999999999999999999999999999999999999999999cf80000695d4acfb00040011a011b011c0397be8517c7bdf5187c55af4f8b61fdc321588c7ab768dee24b006df29106458d7cf029a28be3defa8c3e2ad7a7c5b0fee190ac463d5bb46f71258036f9488322c6be7cf80000695d4acfb0004001270128012901035040011d0103404001210082724a765bd03de7e557d03d203239641e4570953914b82a806fdbc2fa10f8c5e3b7e1cb96b8a440f43d5cc8d5334f00711345b647d43e5837cf367741c70934d23703af7333333333333333333333333333333333333333333333333333333333333333300001a5752b3ec0173fbaacf95f228dbc76f4133f3d46592655ec11b5513922ce50a36dba9abf28100001a5752a4a9c262b2ca7f00014080133011e011f0082724a765bd03de7e557d03d203239641e4570953914b82a806fdbc2fa10f8c5e3b78d942e903175e0ffe9857660057e1e7e904397d1bd95e61a0dc318aaabb1b31f02052030240120013700a0431b9004c4b4000000000000000000960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003af7333333333333333333333333333333333333333333333333333333333333333300001a5752b3ec0246660377de4bc319a5038b872892ad701fa6a7f2783ecefd07a68cb4fe32f8b700001a5752b3ec0162b2ca7f00014080122012301240101a001250082728d942e903175e0ffe9857660057e1e7e904397d1bd95e61a0dc318aaabb1b31fe1cb96b8a440f43d5cc8d5334f00711345b647d43e5837cf367741c70934d237020f0409283baec018110126013700ab69fe00000000000000000000000000000000000000000000000000000000000000013fccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccd283baec0000000034aea567d800c56594fe40009e42614c107ac00000000000000000640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001035040012a01035040012d008272f46adf2640fb8c30d7d053c9c4084f8daeda7399e261dd404d1578691fcb5bd75f4f3be7c22b3f846e0fc1aa8c5ccb4ed9a7e5acc35369f076448359df9b18eb03af734517c7bdf5187c55af4f8b61fdc321588c7ab768dee24b006df29106458d7cf00001a5752b3ec01e9e3a299d1299d62fbb7c3d6a82c2ebb702db967b370dd959fc60bf87be1755500001a5752a4a9c362b2ca7f00014080133012b012c008272f46adf2640fb8c30d7d053c9c4084f8daeda7399e261dd404d1578691fcb5bd7b43acc176be3014d5645fa082b16fe872be1c3b8428621bc03e49a5bc0cf7db202052030340130013103af734517c7bdf5187c55af4f8b61fdc321588c7ab768dee24b006df29106458d7cf00001a5752b3ec0372f7b0548efa5c07fc58c3051d80c9f650039701e834559eabe556240894c83400001a5752b3ec0162b2ca7f00014080133012e012f008272b43acc176be3014d5645fa082b16fe872be1c3b8428621bc03e49a5bc0cf7db25f4f3be7c22b3f846e0fc1aa8c5ccb4ed9a7e5acc35369f076448359df9b18eb02053030340130013100a042665004c4b400000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000069600000009600000004000600000000000519ae84f17b8f8b22026a975ff55f1ab19fde4a768744d2178dfa63bb533e107a409026bc03af7555555555555555555555555555555555555555555555555555555555555555500001a5752b3ec03e61d8ae448b107bedb05342fdb82a5e63dc40bf82d3f4db9d66f6f11005abdfd00001a5752a4a9c362b2ca7f0001408013301340135000120008272010f24a4cdf5d7c0f8497739ff731e5175cd3f10069c838b4445caf821c4cf4e6ca0ffac88c5927e1becceb3949e8aa3388a1cd3e5405413900cce63ed93f19902053030240136013700a041297004c4b40000000000000000002e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005bc00000000000000000000000012d452da449e50b8cf7dd27861f146122afe1b546bb8b70fc8216f0c614139f8e04a5134191") + c, err := FromBOC(boc) + if err != nil { + t.Fatal(err) + } + + jj, err := json.Marshal(c) + if err != nil { + t.Fatal(err) + } + + var c2 *Cell + err = json.Unmarshal(jj, &c2) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(c2.Hash(), c.Hash()) { + t.Fatal("incorrect hash", hex.EncodeToString(c2.Hash()), hex.EncodeToString(c.Hash())) + } +} + +func TestBOCBomb(t *testing.T) { + boc, _ := base64.StdEncoding.DecodeString("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") + c, err := FromBOC(boc) + if err != nil { + t.Fatal(err) + } + + hash, _ := hex.DecodeString("EC89185485A679715A615BCA264BF01B4CB619BD28D9E17FF73D05CF5E01C0E0") + if !bytes.Equal(c.Hash(), hash) { + t.Fatal("incorrect hash", hex.EncodeToString(c.Hash()), hex.EncodeToString(hash)) + } +} + +func TestCell_TxWithMerkleBody(t *testing.T) { + boc, _ := hex.DecodeString("b5ee9c7201021c010004260003b5792fb2fb7884d2a79f8e5b1279264597682fd7e56cf3ccfebea767db7173526f100000a2261348e01ab0389959f7f3c33161c3e4bf3a5901c38958667d64b5603ea04397c1d44279400000a1f24f06c056453860b0003476245d680102030201e00405008272d96846fe22c11b2cbc067eea6a82f1b332efa12da7070d4e90ee6c9bd56388009f339073f094314d4a2b696c2face70a1a07882e875bd28aa243d0a0538e291002110cae650619760604401a1b01df880125f65f6f109a54f3f1cb624f24c8b2ed05fafcad9e799fd7d4ecfb6e2e6a4de2044942e0fdde60708999830ca7800441f5cc83bdacc4b308ea56a28d39cd0d82e2c8ecfd45ccaf81a95d04b896c13c3583a8dcabf41812ba9d50018e917836c81000000003229c3178000000d01c060101df07018032000f1e41cb30becd660a374c510bcd742b99682d17958ca64e1f9d598b6ae48f65202faf080000000000000000000000000000419d5d4d00000000000000000801cf280125f65f6f109a54f3f1cb624f24c8b2ed05fafcad9e799fd7d4ecfb6e2e6a4de300078f20e5985f66b3051ba62885e6ba15ccb4168bcac653270fceacc5b57247b29017d7840070eb8b0678525200001444c2691c04c8a70c1620ceaea68000000000000000400809460329fe4b78e00eea1a217eb3fe13cddfedab08022cf926f82a08343cff3be3342e0008092201200a0b284801018eeca88229bd7b563d72ba57749cd8c63f8efa7d47f3e4f74bc7c51847ec45be00072201200c0d2201200e0f28480101fe18b21f54a2802d6fef56513063a78c4af471bf0e24c31e00793949c91aac39000622012010112848010155cdeed7850ef4313f673c311f5c39bec3c161940c0a71cadde5a031f7db13a7000528480101b988fbf55f0ef7e992d36862abf33933601f50e1eb72c71762d5225bb843c87e0004220120121328480101960d9b2f2590c46bca66ac776d6048598c153f8cf05c980a1042e8003f24928300032201201415284801016604e5bef768c9ed879cdb892c0cf2076208d4d4a41b3aecec00b045eefb6c530002220120161722012018192848010155fae57e9a2b8351802cb998e175e2b93b3dc247592edb1901d07d0097902140000128480101f142b2da4d0e106b131f3640bd8f3cad72b53a3d6153c668fe75db1de12ec1ff0000008118f1e3ac53631fcb4844506477b86e3fffbef1c88e09633956a96a6112ff7d612513ceba691b3a6a0a29131524aa081cdc20820a40cb3ae22b01445499b7618c20009d417f03138800000000000000000e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020006fc9f0ccf44c78519c000000000002000000000002c3a7bf1a1987b4997fd6e16077ca4c5a9c62dd0bde5c7cd809ef35f2cbfaf24444d07b1c") + c, err := FromBOC(boc) + if err != nil { + t.Fatal(err) + } + + hash, _ := hex.DecodeString("CA676F0F30D21C8828D1094424797085B603E991D26943EE17EE5D77AC4B0896") + if !bytes.Equal(c.Hash(), hash) { + t.Fatal("incorrect hash", hex.EncodeToString(c.Hash()), hex.EncodeToString(hash)) + } +} diff --git a/tvm/cell/level.go b/tvm/cell/level.go new file mode 100644 index 00000000..a148f651 --- /dev/null +++ b/tvm/cell/level.go @@ -0,0 +1,23 @@ +package cell + +import "math/bits" + +type LevelMask struct { + mask byte +} + +func (m LevelMask) getLevel() int { + return bits.Len8(m.mask) +} + +func (m LevelMask) getHashIndex() int { + return bits.OnesCount8(m.mask) +} + +func (m LevelMask) apply(level int) LevelMask { + return LevelMask{m.mask & ((1 << level) - 1)} +} + +func (m LevelMask) isSignificant(level int) bool { + return level == 0 || ((m.mask>>(level-1))%2 != 0) +} diff --git a/tvm/cell/parse.go b/tvm/cell/parse.go index 84db0f99..7702f11d 100644 --- a/tvm/cell/parse.go +++ b/tvm/cell/parse.go @@ -126,7 +126,7 @@ func parseCells(rootsNum, cellsNum, refSzBytes int, data []byte, index []int) ([ refsNum := int(flags & 0b111) special := (flags & 0b1000) != 0 withHashes := (flags & 0b10000) != 0 - levelMask := flags >> 5 + levelMask := LevelMask{flags >> 5} if refsNum > 4 { return nil, errors.New("too many refs in cell") @@ -143,7 +143,7 @@ func parseCells(rootsNum, cellsNum, refSzBytes int, data []byte, index []int) ([ } if withHashes { - maskBits := int(math.Ceil(math.Log2(float64(levelMask) + 1))) + maskBits := int(math.Ceil(math.Log2(float64(levelMask.mask) + 1))) hashesNum := maskBits + 1 offset += hashesNum*hashSize + hashesNum*depthSize @@ -197,7 +197,7 @@ func parseCells(rootsNum, cellsNum, refSzBytes int, data []byte, index []int) ([ cells[i].special = special cells[i].bitsSz = bitsSz - cells[i].level = levelMask + cells[i].levelMask = levelMask cells[i].data = payload cells[i].refs = refs } diff --git a/tvm/cell/proof.go b/tvm/cell/proof.go index 2cddc415..0b463c18 100644 --- a/tvm/cell/proof.go +++ b/tvm/cell/proof.go @@ -9,39 +9,29 @@ import ( type cellHash = []byte -func (c *Cell) CreateProof(parts [][]byte) (*Cell, error) { +func (c *Cell) CreateProof(forHashes [][]byte) (*Cell, error) { proofBody := c.copy() // TODO: optimize - hasParts, err := proofBody.toProof(parts) + hasParts, err := proofBody.toProof(forHashes) if err != nil { return nil, fmt.Errorf("failed to build proof for cell: %w", err) } - if len(hasParts) != len(parts) { + if len(hasParts) != len(forHashes) { return nil, fmt.Errorf("given cell not contains all parts to proof") } - // we unwrap level by 1 to correctly calc proofs on pruned cells - calcHash, err := proofBody.proofHash(1) - if err != nil { - return nil, err - } - depth, err := proofBody.proofMaxDepth(0, 1) - if err != nil { - return nil, err - } - // build root Merkle Proof cell data := make([]byte, 1+32+2) data[0] = _MerkleProofType - copy(data[1:], calcHash) - binary.BigEndian.PutUint16(data[1+32:], depth) + copy(data[1:], c.getHash(c.levelMask.getLevel())) + binary.BigEndian.PutUint16(data[1+32:], c.getDepth(c.levelMask.getLevel())) proof := &Cell{ - special: true, - level: 0, - bitsSz: 8 + 256 + 16, - data: data, - refs: []*Cell{proofBody}, + special: true, + levelMask: c.levelMask, + bitsSz: 8 + 256 + 16, + data: data, + refs: []*Cell{proofBody}, } return proof, nil @@ -87,46 +77,38 @@ func (c *Cell) toProof(parts []cellHash) ([]cellHash, error) { if len(hasPartsRefs) > 0 && len(toPruneRefs) > 0 { // contains some useful and unuseful refs, pune unuseful for i, ref := range toPruneRefs { - if ref.level >= 3 { + if ref.levelMask.getLevel() >= 3 { return nil, fmt.Errorf("child level is to big to prune") } - prunedData := make([]byte, 2+(ref.level+1)*(32+2)) - prunedData[0] = _PrunedType - prunedData[1] = ref.level + 1 + ourLvl := ref.levelMask.getLevel() - for lvl := byte(0); lvl <= ref.level; lvl++ { - hash, err := ref.proofHash(lvl) - if err != nil { - return nil, fmt.Errorf("failed to hash child proof: %w", err) - } - - depth, err := ref.proofMaxDepth(0, lvl) - if err != nil { - return nil, fmt.Errorf("failed to hash child proof: %w", err) - } + prunedData := make([]byte, 2+(ourLvl+1)*(32+2)) + prunedData[0] = _PrunedType + prunedData[1] = byte(ref.levelMask.getLevel()) + 1 - copy(prunedData[2+(lvl*32):], hash) - binary.BigEndian.PutUint16(prunedData[2+((lvl+1)*32)+2*lvl:], depth) + for lvl := 0; lvl <= ourLvl; lvl++ { + copy(prunedData[2+(lvl*32):], ref.getHash(lvl)) + binary.BigEndian.PutUint16(prunedData[2+((lvl+1)*32)+2*lvl:], ref.getDepth(lvl)) } c.refs[toPruneIdx[i]] = &Cell{ - special: true, - level: ref.level + 1, - bitsSz: uint(len(prunedData) * 8), - data: prunedData, + special: true, + levelMask: LevelMask{ref.levelMask.mask + 1}, + bitsSz: uint(len(prunedData) * 8), + data: prunedData, } } } typ := c.getType() for _, ref := range c.refs { - if ref.level > c.level { + if ref.levelMask.getLevel() > c.levelMask.getLevel() { if typ == _MerkleProofType { // proof should be 1 level less than child - c.level = ref.level - 1 + c.levelMask = LevelMask{ref.levelMask.mask - 1} } else { - c.level = ref.level + c.levelMask = ref.levelMask } } } @@ -140,12 +122,12 @@ func CheckProof(proof *Cell, hash []byte) error { return fmt.Errorf("not a merkle proof cell") } - needLvl := proof.refs[0].level + needLvl := proof.refs[0].levelMask.getLevel() if needLvl > 0 { needLvl -= 1 } - if needLvl != proof.level { + if needLvl != proof.levelMask.getLevel() { return fmt.Errorf("incorrect level of child") } if !bytes.Equal(hash, proof.data[1:33]) { @@ -153,93 +135,147 @@ func CheckProof(proof *Cell, hash []byte) error { } // we unwrap level by 1 to correctly check proofs on pruned cells - calcHash, err := proof.refs[0].proofHash(1) - if err != nil { - return err - } - + calcHash := proof.refs[0].getHash(needLvl) if !bytes.Equal(hash, calcHash) { return fmt.Errorf("incorrect proof") } return nil } -func (c *Cell) proofHash(unwrapLevel byte) ([]byte, error) { - if unwrapLevel > 3 { - return nil, fmt.Errorf("too big unwrap level") - } +func (c *Cell) getLevelMask() LevelMask { + return c.levelMask +} - switch c.getType() { - case _PrunedType: - if unwrapLevel > 0 { - if unwrapLevel > c.level { - return nil, fmt.Errorf("too big unwrap level for pruned cell") - } - return c.data[2 : 2+(32*unwrapLevel)], nil - } - // in case of 0 unwrap we calc hash in the same way as ordinary - fallthrough - default: - body := c.BeginParse().MustLoadSlice(c.bitsSz) - - data := make([]byte, 2+len(body)+len(c.refs)*(2+32)) - data[0], data[1] = c.descriptors(unwrapLevel) - copy(data[2:], body) - offset := 2 + len(body) - - unusedBits := 8 - (c.bitsSz % 8) - if unusedBits != 8 { - // we need to set bit at the end if not whole byte was used - data[offset-1] += 1 << (unusedBits - 1) - } +func (c *Cell) getHash(level int) []byte { + hashIndex := c.getLevelMask().apply(level).getHashIndex() - for i, ref := range c.refs { - depth, err := ref.proofMaxDepth(0, unwrapLevel) - if err != nil { - return nil, err - } - hashProof, err := ref.proofHash(unwrapLevel) - if err != nil { - return nil, err - } - binary.BigEndian.PutUint16(data[offset+(2*i):], depth) - copy(data[offset+(2*len(c.refs))+(32*i):], hashProof) + if c.getType() == _PrunedType { + prunedHashIndex := c.getLevelMask().getHashIndex() + if hashIndex != prunedHashIndex { + // return hash from data + return c.data[2+(hashIndex*32) : 2+((hashIndex+1)*32)] } + hashIndex = 0 + } - hash := sha256.New() - hash.Write(data) - return hash.Sum(nil), nil + // lazy hash calc + if c.hashes == nil { + c.calculateHashes() } + + return c.hashes[hashIndex*32 : (hashIndex+1)*32] } -func (c *Cell) proofMaxDepth(start uint16, unwrapLevel byte) (uint16, error) { - d := start - switch c.getType() { - case _PrunedType: - if unwrapLevel > 0 { - if unwrapLevel > c.level { - return 0, fmt.Errorf("too big unwrap level for pruned cell") +func (c *Cell) calculateHashes() { + totalHashCount := c.levelMask.getHashIndex() + 1 + c.hashes = make([]byte, 32*totalHashCount) + c.depthLevels = make([]uint16, totalHashCount) + + hashCount := totalHashCount + typ := c.getType() + if typ == _PrunedType { + hashCount = 1 + } + + hashIndexOffset := totalHashCount - hashCount + hashIndex := 0 + level := c.levelMask.getLevel() + for levelIndex := 0; levelIndex <= level; levelIndex++ { + if !c.levelMask.isSignificant(levelIndex) { + continue + } + + func() { + defer func() { + hashIndex++ + }() + + if levelIndex < hashIndexOffset { + return } - depthOffset := 2 + (32 * (unwrapLevel)) + 2*(unwrapLevel-1) - x := start + binary.BigEndian.Uint16(c.data[depthOffset:]) - if x > d { - d = x + dsc := make([]byte, 2) + dsc[0], dsc[1] = c.descriptors(c.levelMask.apply(levelIndex)) + + hash := sha256.New() + hash.Write(dsc) + + if hashIndex == hashIndexOffset { + if levelIndex != 0 && typ != _PrunedType { + // should never happen + panic("not pruned or 0") + } + + data := c.BeginParse().MustLoadSlice(c.bitsSz) + unusedBits := 8 - (c.bitsSz % 8) + if unusedBits != 8 { + // we need to set bit at the end if not whole byte was used + data[len(data)-1] += 1 << (unusedBits - 1) + } + hash.Write(data) + } else { + if levelIndex == 0 || typ == _PrunedType { + // should never happen + panic("pruned or 0") + } + off := hashIndex - hashIndexOffset - 1 + hash.Write(c.hashes[off*32 : (off+1)*32]) } - return d, nil - } - fallthrough - default: - for _, cc := range c.refs { - x, err := cc.proofMaxDepth(start+1, unwrapLevel) - if err != nil { - return 0, err + + var depth uint16 + for i := 0; i < len(c.refs); i++ { + var childDepth uint16 + if typ == _MerkleProofType || typ == _MerkleUpdateType { + childDepth = c.refs[i].getDepth(levelIndex + 1) + } else { + childDepth = c.refs[i].getDepth(levelIndex) + } + + depthBytes := make([]byte, 2) + binary.BigEndian.PutUint16(depthBytes, childDepth) + hash.Write(depthBytes) + + if childDepth > depth { + depth = childDepth + } + } + if len(c.refs) > 0 { + depth++ + if depth >= maxDepth { + panic("depth is more than max depth") + } } - if x > d { - d = x + for i := 0; i < len(c.refs); i++ { + if typ == _MerkleProofType || typ == _MerkleUpdateType { + hash.Write(c.refs[i].getHash(levelIndex + 1)) + } else { + hash.Write(c.refs[i].getHash(levelIndex)) + } } + off := hashIndex - hashIndexOffset + c.depthLevels[off] = depth + copy(c.hashes[off*32:(off+1)*32], hash.Sum(nil)) + }() + } +} + +func (c *Cell) getDepth(level int) uint16 { + hashIndex := c.getLevelMask().apply(level).getHashIndex() + if c.getType() == _PrunedType { + prunedHashIndex := c.getLevelMask().getHashIndex() + if hashIndex != prunedHashIndex { + // return depth from data + off := 2 + 32*prunedHashIndex + hashIndex*2 + return binary.BigEndian.Uint16(c.data[off : off+2]) } + hashIndex = 0 } - return d, nil + + // lazy hash calc + if c.hashes == nil { + c.calculateHashes() + } + + return c.depthLevels[hashIndex] } diff --git a/tvm/cell/proof_test.go b/tvm/cell/proof_test.go index b0ba3a98..7e5ade29 100644 --- a/tvm/cell/proof_test.go +++ b/tvm/cell/proof_test.go @@ -41,9 +41,8 @@ func TestProofOfProofCreate(t *testing.T) { if err != nil { t.Fatal(err) } - println(prf.Dump()) - hash2, _ := hex.DecodeString("D3B8077755C605D944CC4C57E1D38B723885EAA1699319A3DCADF018B31B558F") + hash2, _ := hex.DecodeString("27FCB2CCEEF7159510BB08F96E037F910A38C1723D08B1FEFBBA43D73E660E3D") err = CheckProof(prf, hash2) if err != nil { t.Fatal(err) diff --git a/tvm/cell/serialize.go b/tvm/cell/serialize.go index 6a885b3c..6806039f 100644 --- a/tvm/cell/serialize.go +++ b/tvm/cell/serialize.go @@ -85,7 +85,7 @@ func (c *Cell) serialize(refIndexSzBytes uint) []byte { body := c.BeginParse().MustLoadSlice(c.bitsSz) data := make([]byte, 2+len(body)) - data[0], data[1] = c.descriptors(0) + data[0], data[1] = c.descriptors(c.levelMask) copy(data[2:], body) unusedBits := 8 - (c.bitsSz % 8) @@ -101,61 +101,19 @@ func (c *Cell) serialize(refIndexSzBytes uint) []byte { return data } -func (c *Cell) serializeHash() []byte { - body := c.BeginParse().MustLoadSlice(c.bitsSz) - - data := make([]byte, 2+len(body)+len(c.refs)*(2+32)) - data[0], data[1] = c.descriptors(0) - copy(data[2:], body) - offset := 2 + len(body) - - unusedBits := 8 - (c.bitsSz % 8) - if unusedBits != 8 { - // we need to set bit at the end if not whole byte was used - data[offset-1] += 1 << (unusedBits - 1) - } - - for i, ref := range c.refs { - binary.BigEndian.PutUint16(data[offset+(2*i):], uint16(ref.maxDepth(0))) - copy(data[offset+(2*len(c.refs))+(32*i):], ref.Hash()) - } - - return data -} - -// calc how deep is the cell (how long children tree) -func (c *Cell) maxDepth(start int) int { - d := start - for _, cc := range c.refs { - if x := cc.maxDepth(start + 1); x > d { - d = x - } - } - return d -} - -func (c *Cell) descriptors(unwrapLevel byte) (byte, byte) { - ceilBytes := c.bitsSz / 8 +func (c *Cell) descriptors(lvl LevelMask) (byte, byte) { + // calc size + ln := (c.bitsSz / 8) * 2 if c.bitsSz%8 != 0 { - ceilBytes++ + ln++ } - // calc size - ln := ceilBytes + c.bitsSz/8 - specBit := byte(0) if c.special { specBit = 8 } - lvl := c.level - if lvl >= unwrapLevel { - lvl -= unwrapLevel - } else { - lvl = 0 - } - - return byte(len(c.refs)) + specBit + lvl*32, byte(ln) + return byte(len(c.refs)) + specBit + lvl.mask*32, byte(ln) } func dynamicIntBytes(val uint64, sz uint) []byte { diff --git a/tvm/cell/slice.go b/tvm/cell/slice.go index 68f84447..a5f71225 100644 --- a/tvm/cell/slice.go +++ b/tvm/cell/slice.go @@ -10,11 +10,11 @@ import ( ) type Slice struct { - special bool - level byte - bitsSz uint - loadedSz uint - data []byte + special bool + levelMask LevelMask + bitsSz uint + loadedSz uint + data []byte // store it as slice of pointers to make indexing logic cleaner on parse, // from outside it should always come as object to not have problems @@ -493,12 +493,12 @@ func (c *Slice) Copy() *Slice { data := append([]byte{}, c.data...) return &Slice{ - special: c.special, - level: c.level, - bitsSz: c.bitsSz, - loadedSz: c.loadedSz, - data: data, - refs: c.refs, + special: c.special, + levelMask: c.levelMask, + bitsSz: c.bitsSz, + loadedSz: c.loadedSz, + data: data, + refs: c.refs, } } @@ -512,10 +512,10 @@ func (c *Slice) ToCell() (*Cell, error) { } return &Cell{ - special: c.special, - level: c.level, - bitsSz: left, - data: data, - refs: c.refs, + special: c.special, + levelMask: c.levelMask, + bitsSz: left, + data: data, + refs: c.refs, }, nil }