From 651185d558a67410a46447f55c454efdf7386ef2 Mon Sep 17 00:00:00 2001 From: "aleksej.paschenko" Date: Fri, 2 Feb 2024 11:50:56 +0300 Subject: [PATCH] Implement tlb.Transaction.ToBoc() --- boc/boc.go | 51 ++++++++++++++++++++++++++++++++----------- boc/boc_test.go | 10 ++++----- boc/cell.go | 8 +++---- tlb/benchmark_test.go | 7 ++++++ tlb/decoder.go | 5 +++++ tlb/transactions.go | 20 +++++++++++++++++ 6 files changed, 79 insertions(+), 22 deletions(-) diff --git a/boc/boc.go b/boc/boc.go index 175833f4..fac3e781 100644 --- a/boc/boc.go +++ b/boc/boc.go @@ -297,25 +297,50 @@ func DeserializeBocHex(boc string) ([]*Cell, error) { } func SerializeBoc(cell *Cell, idx bool, hasCrc32 bool, cacheBits bool, flags uint) ([]byte, error) { - bag := newBagOfCells() - return bag.serializeBoc([]*Cell{cell}, idx, hasCrc32, cacheBits, flags) + bag := NewBagOfCells() + return bag.SerializeBoc([]*Cell{cell}, idx, hasCrc32, cacheBits, flags) } -// bagOfCells serializes cells to a boc. +// BagOfCells serializes cells to a boc. +// +// Use of BagOfCells is an advanced technique, +// prefer SerializeBoc() function instead. // // the serialization algorithms is a golang version of // https://github.com/ton-blockchain/ton/blob/master/crypto/vm/boc.cpp# -type bagOfCells struct { +type BagOfCells struct { hasher *Hasher } -func newBagOfCells() *bagOfCells { - return &bagOfCells{ - hasher: NewHasher(), +type BagOfCellsOptions struct { + hasher *Hasher +} + +type BagOfCellsOption func(*BagOfCellsOptions) + +// BagWithHasher sets the hasher to be used by the BagOfCells. +// The idea is that one can use the same hasher for both deserialization and then serialization +// to speed up the process of hashing cells. +func BagWithHasher(hasher *Hasher) BagOfCellsOption { + return func(options *BagOfCellsOptions) { + options.hasher = hasher + } +} + +func NewBagOfCells(opts ...BagOfCellsOption) *BagOfCells { + options := &BagOfCellsOptions{} + for _, opt := range opts { + opt(options) + } + if options.hasher == nil { + options.hasher = NewHasher() + } + return &BagOfCells{ + hasher: options.hasher, } } -// serializeBoc converts the given list of root cells to a byte representation. +// SerializeBoc converts the given list of root cells to a byte representation. // // serialized_boc#672fb0ac has_idx:(## 1) has_crc32c:(## 1) // has_cache_bits:(## 1) flags:(## 2) { flags = 0 } @@ -328,7 +353,7 @@ func newBagOfCells() *bagOfCells { // index:(cells * ##(off_bytes * 8)) // cell_data:(tot_cells_size * [ uint8 ]) // = BagOfCells; -func (boc *bagOfCells) serializeBoc(rootCells []*Cell, idx bool, hasCrc32 bool, cacheBits bool, flags uint) ([]byte, error) { +func (boc *BagOfCells) SerializeBoc(rootCells []*Cell, idx bool, hasCrc32 bool, cacheBits bool, flags uint) ([]byte, error) { roots, cellInfos, err := boc.importRoots(rootCells) if err != nil { return nil, err @@ -437,7 +462,7 @@ func (boc *bagOfCells) serializeBoc(rootCells []*Cell, idx bool, hasCrc32 bool, return resBytes, nil } -func (boc *bagOfCells) importRoots(rootCells []*Cell) ([]*rootInfo, []*cellInfo, error) { +func (boc *BagOfCells) importRoots(rootCells []*Cell) ([]*rootInfo, []*cellInfo, error) { roots := make([]*rootInfo, 0, len(rootCells)) state := &orderState{ cells: map[string]int{}, @@ -457,7 +482,7 @@ func (boc *bagOfCells) importRoots(rootCells []*Cell) ([]*rootInfo, []*cellInfo, return roots, cellInfos, nil } -func (boc *bagOfCells) importCell(state *orderState, cell *Cell, depth int) (int, error) { +func (boc *BagOfCells) importCell(state *orderState, cell *Cell, depth int) (int, error) { if depth > maxDepth { return 0, ErrDepthIsTooBig } @@ -545,7 +570,7 @@ const ( allocate ) -func (boc *bagOfCells) revisit(state, newState *orderState, cellIndex int, force force) int { +func (boc *BagOfCells) revisit(state, newState *orderState, cellIndex int, force force) int { dci := state.cellList[cellIndex] if dci.newIndex >= 0 { return dci.newIndex @@ -589,7 +614,7 @@ func (boc *bagOfCells) revisit(state, newState *orderState, cellIndex int, force return dci.newIndex } -func (boc *bagOfCells) reorderCells(roots []*rootInfo, state *orderState) []*cellInfo { +func (boc *BagOfCells) reorderCells(roots []*rootInfo, state *orderState) []*cellInfo { for i := len(state.cellList) - 1; i >= 0; i-- { dci := state.cellList[i] c := dci.refsNumber diff --git a/boc/boc_test.go b/boc/boc_test.go index 6cfd75ff..0a8e6914 100644 --- a/boc/boc_test.go +++ b/boc/boc_test.go @@ -18,8 +18,8 @@ func TestBigCell(t *testing.T) { t.Fail() return } - bag := newBagOfCells() - x, err := bag.serializeBoc([]*Cell{cells[0]}, false, true, false, 0) + bag := NewBagOfCells() + x, err := bag.SerializeBoc([]*Cell{cells[0]}, false, true, false, 0) if err != nil { fmt.Println(err) t.Fail() @@ -379,10 +379,10 @@ func Test_bagOfCells_serializeBoc(t *testing.T) { if err != nil { t.Fatalf("failed to deserialize boc: %v", err) } - bag := newBagOfCells() - bs, err := bag.serializeBoc(cells, tt.hasIndex, tt.hasCrc, tt.hasCacheBits, 0) + bag := NewBagOfCells() + bs, err := bag.SerializeBoc(cells, tt.hasIndex, tt.hasCrc, tt.hasCacheBits, 0) if err != nil { - t.Fatalf("serializeBoc() failed: %v", err) + t.Fatalf("SerializeBoc() failed: %v", err) } if tt.hexBoc != hex.EncodeToString(bs) { t.Fatalf("serializedBoc differs from hexBoc") diff --git a/boc/cell.go b/boc/cell.go index 496fe44f..f84b4782 100644 --- a/boc/cell.go +++ b/boc/cell.go @@ -115,8 +115,8 @@ func (c *Cell) hash(cache map[*Cell]*immutableCell) ([]byte, error) { } func (c *Cell) ToBoc() ([]byte, error) { - bag := newBagOfCells() - return bag.serializeBoc([]*Cell{c}, false, false, false, 0) + bag := NewBagOfCells() + return bag.SerializeBoc([]*Cell{c}, false, false, false, 0) } func (c *Cell) ToBocString() (string, error) { @@ -128,8 +128,8 @@ func (c *Cell) ToBocBase64() (string, error) { } func (c *Cell) ToBocCustom(idx bool, hasCrc32 bool, cacheBits bool, flags uint) ([]byte, error) { - bag := newBagOfCells() - return bag.serializeBoc([]*Cell{c}, idx, hasCrc32, cacheBits, 0) + bag := NewBagOfCells() + return bag.SerializeBoc([]*Cell{c}, idx, hasCrc32, cacheBits, 0) } func (c *Cell) ToBocStringCustom(idx bool, hasCrc32 bool, cacheBits bool, flags uint) (string, error) { diff --git a/tlb/benchmark_test.go b/tlb/benchmark_test.go index c732e5cb..b87f9b61 100644 --- a/tlb/benchmark_test.go +++ b/tlb/benchmark_test.go @@ -44,4 +44,11 @@ func Test_block(b *testing.T) { if err != nil { b.Errorf("Unmarshal() failed: %v", err) } + + for _, tx := range block.AllTransactions() { + _, err := tx.ToBoc() + if err != nil { + b.Errorf("ToBoc() failed: %v", err) + } + } } diff --git a/tlb/decoder.go b/tlb/decoder.go index 88cdb3a8..2b2f8cac 100644 --- a/tlb/decoder.go +++ b/tlb/decoder.go @@ -294,5 +294,10 @@ func decodeBitString(c *boc.Cell, val reflect.Value) error { // Hasher returns boc.Hasher that is used to calculate hashes when decoding. func (dec *Decoder) Hasher() *boc.Hasher { return dec.hasher +} +// BagOfCells returns boc.BagOfCells with the same hasher as the decoder. +// This speeds up the process of hashing cells. +func (dec *Decoder) BagOfCells() *boc.BagOfCells { + return boc.NewBagOfCells(boc.BagWithHasher(dec.hasher)) } diff --git a/tlb/transactions.go b/tlb/transactions.go index f3891562..9519af94 100644 --- a/tlb/transactions.go +++ b/tlb/transactions.go @@ -33,6 +33,8 @@ type Transaction struct { Description TransactionDescr `tlb:"^"` hash Bits256 + + lazyToBoc func() ([]byte, error) } // Hash returns a hash of this transaction. @@ -40,14 +42,32 @@ func (tx *Transaction) Hash() Bits256 { return tx.hash } +// ToBoc returns a BOC of this transaction. +// It works only if the transaction was unmarshalled from a cell. +func (tx *Transaction) ToBoc() ([]byte, error) { + if tx.lazyToBoc != nil { + return tx.lazyToBoc() + } + return nil, fmt.Errorf("transaction was not unmarshalled from cell") +} + func (tx *Transaction) UnmarshalTLB(c *boc.Cell, decoder *Decoder) error { var ( hash []byte err error ) if decoder.hasher != nil { + tx.lazyToBoc = func() ([]byte, error) { + bag := decoder.BagOfCells() + c.ResetCounters() + return bag.SerializeBoc([]*boc.Cell{c}, false, false, false, 0) + } hash, err = decoder.hasher.Hash(c) } else { + tx.lazyToBoc = func() ([]byte, error) { + c.ResetCounters() + return boc.SerializeBoc(c, false, false, false, 0) + } hash, err = c.Hash() } if err != nil {