Skip to content

Commit

Permalink
tvm.Executor to support libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksej-paschenko committed Jul 20, 2023
1 parent 2887f95 commit 1ea5f58
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 79 deletions.
16 changes: 13 additions & 3 deletions liteapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ func (c *Client) GetValidatorStats(
return nil, fmt.Errorf("not implemented")
}

func (c *Client) GetLibraries(ctx context.Context, libraryList []tongo.Bits256) ([]liteclient.LiteServerLibraryEntryC, error) {
func (c *Client) GetLibraries(ctx context.Context, libraryList []tongo.Bits256) (map[tongo.Bits256]*boc.Cell, error) {
id, err := c.targetBlock(ctx)
if err != nil {
return nil, err
Expand All @@ -857,8 +857,18 @@ func (c *Client) GetLibraries(ctx context.Context, libraryList []tongo.Bits256)
if err != nil {
return nil, err
}
// TODO: replace with tongo type
return r.Result, nil
libs := make(map[tongo.Bits256]*boc.Cell, len(r.Result))
for _, lib := range r.Result {
data, err := boc.DeserializeBoc(lib.Data)
if err != nil {
return nil, err
}
if len(data) != 1 {
return nil, fmt.Errorf("multiroot lib is not supported")
}
libs[tongo.Bits256(lib.Hash)] = data[0]
}
return libs, nil
}

func (c *Client) GetShardBlockProof(ctx context.Context) (liteclient.LiteServerShardBlockProofC, error) {
Expand Down
85 changes: 29 additions & 56 deletions liteapi/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,10 @@ import (
"testing"
"time"

"github.com/tonkeeper/tongo/tlb"

"github.com/tonkeeper/tongo"
"github.com/tonkeeper/tongo/tlb"
)

//type mockLiteClient struct {
// requestCounter int
// OnRequest func(ctx context.Context, q []byte) ([]byte, error)
//}
//
//func (m *mockLiteClient) Request(ctx context.Context, q []byte) ([]byte, error) {
// m.requestCounter += 1
// return m.OnRequest(ctx, q)
//}
//
//var _ liteClient = &mockLiteClient{}

func TestNewClient_WithMaxConnectionsNumber(t *testing.T) {
cli, err := NewClient(Mainnet())
if err != nil {
Expand Down Expand Up @@ -245,6 +232,34 @@ func TestGetOneTransaction(t *testing.T) {
}
}

func TestGetLibraries(t *testing.T) {
tongoClient, err := NewClientWithDefaultMainnet()
if err != nil {
log.Fatalf("Unable to create tongo client: %v", err)
}

hash := tongo.MustParseHash("587CC789EFF1C84F46EC3797E45FC809A14FF5AE24F1E0C7A6A99CC9DC9061FF")
libs, err := tongoClient.GetLibraries(context.Background(), []tongo.Bits256{hash})
if err != nil {
log.Fatalf("GetLibraries() failed: %v", err)
}
if len(libs) != 1 {
t.Fatalf("expected libs lengths: 1, got: %v", len(libs))
}
cell, ok := libs[hash]
if !ok {
t.Fatalf("expected lib is not found")
}
base64, err := cell.ToBocBase64()
if err != nil {
t.Fatalf("ToBocBase64() failed: %v", err)
}
expected := "te6ccgEBAQEAXwAAuv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVA=="
if base64 != expected {
t.Fatalf("want: %v, got: %v", expected, base64)
}
}

func TestGetJettonWallet(t *testing.T) {
tongoClient, err := NewClientWithDefaultTestnet()
if err != nil {
Expand Down Expand Up @@ -310,48 +325,6 @@ func TestGetRootDNS(t *testing.T) {
fmt.Printf("Root DNS: %v\n", root.ToRaw())
}

// TODO: fix
//func TestGetLastConfigAll_mockConnection(t *testing.T) {
// cli := &mockLiteClient{}
// cli.OnRequest = func(ctx context.Context, q []byte) ([]byte, error) {
// switch cli.requestCounter {
// case 1:
// bytes, err := ioutil.ReadFile("testdata/get-last-config-all-1.bin")
// if err != nil {
// t.Errorf("failed to read response file: %v", err)
// }
// return bytes, err
// case 2:
// bytes, err := ioutil.ReadFile("testdata/get-last-config-all-2.bin")
// if err != nil {
// t.Errorf("failed to read response file: %v", err)
// }
// return bytes, err
// }
// t.Errorf("unexpected request")
// return nil, fmt.Errorf("unexpected request")
// }
// api := &Client{
// connectionPool: []connection{{client: cli}},
// }
// conf, err := api.GetConfigAll(context.TODO(), 0)
// if err != nil {
// t.Fatal(err)
// }
// cell := boc.NewCell()
// err = tlb.Marshal(cell, conf)
// if err != nil {
// t.Fatal(err)
// }
// hash, err := cell.HashString()
// if err != nil {
// t.Fatal(err)
// }
// if hash != "32bfc7a9c87ec2b0d4544740813fc02db0705f7056db6ffd2029d78897a01c6d" {
// t.Fatal(err)
// }
//}

func TestClient_GetTransactionsForUnknownAccount(t *testing.T) {
var a tongo.AccountID
rand.Read(a.Address[:])
Expand Down
36 changes: 22 additions & 14 deletions tlb/hashmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,22 @@ type Hashmap[keyT fixedSize, T any] struct {
values []T
}

// NewHashmap returns a new instance of Hashmap.
// Make sure that a key at index "i" corresponds to a value at the same index.
func NewHashmap[keyT fixedSize, T any](keys []keyT, values []T) Hashmap[keyT, T] {
return Hashmap[keyT, T]{
keys: keys,
values: values,
}
}

func (h Hashmap[keyT, T]) MarshalTLB(c *boc.Cell, encoder *Encoder) error {
// Marshal empty Hashmap
if len(h.values) == 0 || h.values == nil {
return nil
}
var s keyT
keys := make([]boc.BitString, 0)
keys := make([]boc.BitString, 0, len(h.keys))
for _, k := range h.keys {
cell := boc.NewCell()
err := Marshal(cell, k)
Expand All @@ -44,17 +53,15 @@ func (h Hashmap[keyT, T]) MarshalTLB(c *boc.Cell, encoder *Encoder) error {
return nil
}

func (h Hashmap[keyT, T]) encodeMap(c *boc.Cell, keys []boc.BitString, values []T, size int) error {
func (h Hashmap[keyT, T]) encodeMap(c *boc.Cell, keys []boc.BitString, values []T, keySize int) error {
if len(keys) == 0 || len(values) == 0 {
return fmt.Errorf("keys or values are empty")
}

label, err := encodeLabel(c, &keys[0], &keys[len(keys)-1], size)
label, err := encodeLabel(c, &keys[0], &keys[len(keys)-1], keySize)
if err != nil {
return err
}

size = size - label.BitsAvailableForRead() - 1 // l = n - m - 1 // see tlb
keySize = keySize - label.BitsAvailableForRead() - 1 // l = n - m - 1 // see tlb
var leftKeys, rightKeys []boc.BitString
var leftValues, rightValues []T
if len(keys) > 1 {
Expand All @@ -79,15 +86,15 @@ func (h Hashmap[keyT, T]) encodeMap(c *boc.Cell, keys []boc.BitString, values []
if err != nil {
return err
}
err = h.encodeMap(l, leftKeys, leftValues, size)
err = h.encodeMap(l, leftKeys, leftValues, keySize)
if err != nil {
return err
}
r, err := c.NewRef()
if err != nil {
return err
}
err = h.encodeMap(r, rightKeys, rightValues, size)
err = h.encodeMap(r, rightKeys, rightValues, keySize)
if err != nil {
return err
}
Expand Down Expand Up @@ -201,7 +208,7 @@ func (h HashmapE[keyT, T]) MarshalTLB(c *boc.Cell, encoder *Encoder) error {
var temp Maybe[Ref[Hashmap[keyT, T]]]
temp.Exists = len(h.m.keys) > 0
temp.Value.Value = h.m
return Marshal(c, temp)
return encoder.Marshal(c, temp)
}

func (h *HashmapE[keyT, T]) UnmarshalTLB(c *boc.Cell, decoder *Decoder) error {
Expand All @@ -218,10 +225,9 @@ func (h HashmapE[keyT, T]) Values() []T {
func (h HashmapE[keyT, T]) Keys() []keyT {
return h.m.keys
}
func encodeLabel(c *boc.Cell, keyFirst, keyLast *boc.BitString, size int) (boc.BitString, error) {
label := boc.NewBitString(size)
func encodeLabel(c *boc.Cell, keyFirst, keyLast *boc.BitString, keySize int) (boc.BitString, error) {
label := boc.NewBitString(keySize)
if keyFirst != keyLast {

bitLeft, err := keyFirst.ReadBit()
if err != nil {
return boc.BitString{}, err
Expand All @@ -234,7 +240,9 @@ func encodeLabel(c *boc.Cell, keyFirst, keyLast *boc.BitString, size int) (boc.B
if bitLeft != bitRight {
break
}
label.WriteBit(bitLeft)
if err := label.WriteBit(bitLeft); err != nil {
return boc.BitString{}, err
}
bitLeft, err = keyFirst.ReadBit()
if err != nil {
return boc.BitString{}, err
Expand Down Expand Up @@ -272,7 +280,7 @@ func encodeLabel(c *boc.Cell, keyFirst, keyLast *boc.BitString, size int) (boc.B
return boc.BitString{}, err
}
// todo pack label
err = c.WriteLimUint(label.BitsAvailableForRead(), size)
err = c.WriteLimUint(label.BitsAvailableForRead(), keySize)
if err != nil {
return boc.BitString{}, err
}
Expand Down
21 changes: 20 additions & 1 deletion tvm/tvmExecutor.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ type Emulator struct {
type Options struct {
verbosityLevel txemulator.VerbosityLevel
balance int64
lazyC7 bool
// libraries is a list of available libraries encoded as a base64 string.
libraries string
lazyC7 bool
}

type Option func(o *Options)
Expand All @@ -50,6 +52,14 @@ func WithBalance(balance int64) Option {
}
}

// WithLibrariesBase64 provides a list of available libraries as a base64 string.
// Take a look at LibrariesToBase64() to convert a map with libraries to such a string.
func WithLibrariesBase64(libraries string) Option {
return func(o *Options) {
o.libraries = libraries
}
}

// WithLazyC7Optimization allows to make two attempts to execute a get method.
// At the first attempt an emulator invokes a get method without C7.
// This works for most get methods and significantly decreases the execution time.
Expand Down Expand Up @@ -112,6 +122,11 @@ func NewEmulatorFromBOCsBase64(code, data, config string, opts ...Option) (*Emul
lazyC7: options.lazyC7,
balance: uint64(options.balance),
}
if len(options.libraries) > 0 {
if err := e.setLibs(options.libraries); err != nil {
return nil, err
}
}
runtime.SetFinalizer(&e, destroy)
return &e, nil
}
Expand Down Expand Up @@ -139,6 +154,10 @@ func (e *Emulator) SetLibs(libs *boc.Cell) error {
if err != nil {
return err
}
return e.setLibs(libsBoc)
}

func (e *Emulator) setLibs(libsBoc string) error {
cLibsStr := C.CString(libsBoc)
defer C.free(unsafe.Pointer(cLibsStr))
ok := C.tvm_emulator_set_libraries(e.emulator, cLibsStr)
Expand Down
53 changes: 51 additions & 2 deletions tvm/tvmExecutor_test.go

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions txemulator/libraries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package txemulator

import (
"github.com/tonkeeper/tongo"
"github.com/tonkeeper/tongo/boc"
"github.com/tonkeeper/tongo/tlb"
)

// FindLibraries looks for library cells inside the given cell tree and
// returns a list of hashes of found library cells.
func FindLibraries(cell *boc.Cell) ([]tongo.Bits256, error) {
libs, err := findLibraries(cell)
if err != nil {
return nil, err
}
if len(libs) == 0 {
return nil, nil
}
hashes := make([]tongo.Bits256, 0, len(libs))
for hash := range libs {
hashes = append(hashes, hash)
}
return hashes, nil
}

func findLibraries(cell *boc.Cell) (map[tongo.Bits256]struct{}, error) {
if cell.IsExotic() {
if cell.CellType() == boc.LibraryCell {
bytes, err := cell.ReadBytes(33)
if err != nil {
return nil, err
}
var hash tongo.Bits256
copy(hash[:], bytes[1:])
return map[tongo.Bits256]struct{}{
hash: {},
}, nil
}
return nil, nil
}
var libs map[tongo.Bits256]struct{}
for _, ref := range cell.Refs() {
hashes, err := findLibraries(ref)
if err != nil {
return nil, err
}
if libs == nil {
libs = hashes
continue
}
for hash := range hashes {
libs[hash] = struct{}{}
}
}
return libs, nil
}

// LibrariesToBase64 converts a map with libraries to a base64 string.
func LibrariesToBase64(libraries map[tongo.Bits256]*boc.Cell) (string, error) {
if len(libraries) == 0 {
return "", nil
}
hashes := make([]tlb.Bits256, 0, len(libraries))
descriptions := make([]tlb.LibDescr, 0, len(libraries))
for hash, cell := range libraries {
hashes = append(hashes, tlb.Bits256(hash))
descriptions = append(descriptions, tlb.LibDescr{Lib: *cell})
}
hashmap := tlb.NewHashmap[tlb.Bits256, tlb.LibDescr](hashes, descriptions)
libsCell := boc.NewCell()
if err := tlb.Marshal(libsCell, hashmap); err != nil {
return "", err
}
return libsCell.ToBocBase64()
}
Loading

0 comments on commit 1ea5f58

Please sign in to comment.