Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions boc/cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,5 +436,37 @@ func (c *Cell) GetMerkleRoot() ([32]byte, error) {
var hash [32]byte
copy(hash[:], bytes[1:])
return hash, nil
}

// TODO: move to deserializer
func (c *Cell) isValidMerkleProofCell() bool {
return c.cellType == MerkleProofCell && c.RefsSize() == 1 && c.BitSize() == 280
}

func (c *Cell) CalculateMerkleProofMeta() (int, [32]byte, error) {
if !c.isValidMerkleProofCell() {
return 0, [32]byte{}, errors.New("not valid merkle proof cell")
}
imc, err := newImmutableCell(c.Refs()[0], map[*Cell]*immutableCell{})
if err != nil {
return 0, [32]byte{}, fmt.Errorf("get immutable cell: %w", err)
}
h := imc.Hash(0)
var hash [32]byte
copy(hash[:], h)
depth := imc.Depth(0)
return depth, hash, nil
}

// TODO: or add level as optional parameter to Hash256()
func (c *Cell) Hash256WithLevel(level int) ([32]byte, error) {
// TODO: or check for pruned cell and read hash directly from cell
imc, err := newImmutableCell(c, map[*Cell]*immutableCell{})
if err != nil {
return [32]byte{}, err
}
b := imc.Hash(level)
var h [32]byte
copy(h[:], b)
return h, nil
}
33 changes: 32 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/tonkeeper/tongo/ton"
"io"
"os"
)
Expand All @@ -19,15 +20,28 @@ type liteServerId struct {
Key string `json:"key"`
}

type initBlockConfig struct {
Workchain int32 `json:"workchain"`
Shard int64 `json:"shard"`
Seqno int64 `json:"seqno"`
RootHash []byte `json:"root_hash"`
FileHash []byte `json:"file_hash"`
}

type validatorConfig struct {
InitBlock initBlockConfig `json:"init_block"`
}

type configGlobal struct {
LiteServers []liteServerConfig `json:"liteservers"`
//Validator ValidatorConfig `json:"validator"`
Validator validatorConfig `json:"validator"`
}

// GlobalConfigurationFile contains global configuration of the TON Blockchain.
// It is shared by all nodes and includes information about network, init block, hardforks, etc.
type GlobalConfigurationFile struct {
LiteServers []LiteServer
Validator Validator
}

// LiteServer TODO: clarify struct
Expand All @@ -36,6 +50,10 @@ type LiteServer struct {
Key string
}

type Validator struct {
InitBlock ton.BlockIDExt
}

func ParseConfigFile(path string) (*GlobalConfigurationFile, error) {
jsonFile, err := os.Open(path)
if err != nil {
Expand Down Expand Up @@ -74,6 +92,19 @@ func ParseConfig(data io.Reader) (*GlobalConfigurationFile, error) {
}
options.LiteServers = append(options.LiteServers, ls)
}
var rootHash [32]byte
copy(rootHash[:], conf.Validator.InitBlock.RootHash)
var fileHash [32]byte
copy(fileHash[:], conf.Validator.InitBlock.FileHash)
options.Validator.InitBlock = ton.BlockIDExt{
BlockID: ton.BlockID{
Workchain: conf.Validator.InitBlock.Workchain,
Shard: uint64(conf.Validator.InitBlock.Shard),
Seqno: uint32(conf.Validator.InitBlock.Seqno),
},
RootHash: rootHash,
FileHash: fileHash,
}
if len(options.LiteServers) == 0 {
return nil, fmt.Errorf("no one supported liteservers")
}
Expand Down
147 changes: 147 additions & 0 deletions liteapi/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package liteapi

import (
"context"
"errors"
"fmt"
"github.com/tonkeeper/tongo/boc"
"github.com/tonkeeper/tongo/tlb"
"github.com/tonkeeper/tongo/ton"
)

// GetAccountWithProof
// For safe operation, always use GetAccountWithProof with WithBlock(proofedBlock ton.BlockIDExt), as the proof of masterchain cashed blocks is not implemented yet!
func (c *Client) GetAccountWithProof(ctx context.Context, accountID ton.AccountID) (*tlb.ShardAccount, *tlb.ShardStateUnsplit, error) {
res, err := c.GetAccountStateRaw(ctx, accountID) // TODO: add proof check for masterHead
if err != nil {
return nil, nil, err
}
blockID := res.Id.ToBlockIdExt()
if len(res.Proof) == 0 {
return nil, nil, errors.New("empty proof")
}

var blockHash ton.Bits256
if (accountID.Workchain == -1 && blockID.Workchain == -1) || blockID == res.Shardblk.ToBlockIdExt() {
blockHash = blockID.RootHash
} else {
if len(res.ShardProof) == 0 {
return nil, nil, errors.New("empty shard proof")
}
if res.Shardblk.RootHash == [32]byte{} { // TODO: how to check for empty shard?
return nil, nil, errors.New("shard block not passed")
}
shardHash := ton.Bits256(res.Shardblk.RootHash)
if _, err := checkShardInMasterProof(blockID, res.ShardProof, accountID.Workchain, shardHash); err != nil {
return nil, nil, fmt.Errorf("shard proof is incorrect: %w", err)
}
blockHash = shardHash
}
cellsMap := make(map[[32]byte]*boc.Cell)
if len(res.State) > 0 {
stateCells, err := boc.DeserializeBoc(res.State)
if err != nil {
return nil, nil, fmt.Errorf("state deserialization failed: %w", err)
}
hash, err := stateCells[0].Hash256()
if err != nil {
return nil, nil, fmt.Errorf("get hash err: %w", err)
}
cellsMap[hash] = stateCells[0]
}
proofCells, err := boc.DeserializeBoc(res.Proof)
if err != nil {
return nil, nil, err
}
shardState, err := checkBlockShardStateProof(proofCells, blockHash, cellsMap)
if err != nil {
return nil, nil, fmt.Errorf("incorrect block proof: %w", err)
}
values := shardState.ShardStateUnsplit.Accounts.Values()
keys := shardState.ShardStateUnsplit.Accounts.Keys()
for i, k := range keys {
if k == accountID.Address {
return &values[i], shardState, nil
}
}
if len(res.State) == 0 {
return &tlb.ShardAccount{Account: tlb.Account{SumType: "AccountNone"}}, shardState, nil
}
return nil, nil, errors.New("invalid account state")
}

func checkShardInMasterProof(master ton.BlockIDExt, shardProof []byte, workchain int32, shardRootHash ton.Bits256) (*tlb.McStateExtra, error) {
shardProofCells, err := boc.DeserializeBoc(shardProof)
if err != nil {
return nil, err
}
shardState, err := checkBlockShardStateProof(shardProofCells, master.RootHash, nil)
if err != nil {
return nil, fmt.Errorf("check block proof failed: %w", err)
}
if !shardState.ShardStateUnsplit.Custom.Exists {
return nil, fmt.Errorf("not a masterchain block")
}
stateExtra := shardState.ShardStateUnsplit.Custom.Value.Value
keys := stateExtra.ShardHashes.Keys()
values := stateExtra.ShardHashes.Values()
for i, k := range keys {
binTreeValues := values[i].Value.BinTree.Values
for _, b := range binTreeValues {
switch b.SumType {
case "Old":
if int32(k) == workchain && ton.Bits256(b.Old.RootHash) == shardRootHash {
return &stateExtra, nil
}
case "New":
if int32(k) == workchain && ton.Bits256(b.New.RootHash) == shardRootHash {
return &stateExtra, nil
}
}
}
}
return nil, fmt.Errorf("required shard hash not found in proof")
}

func checkBlockShardStateProof(proof []*boc.Cell, blockRootHash ton.Bits256, cellsMap map[[32]byte]*boc.Cell) (*tlb.ShardStateUnsplit, error) {
if len(proof) != 2 {
return nil, errors.New("must be two root cells")
}
block, err := checkBlockProof(*proof[0], blockRootHash)
if err != nil {
return nil, fmt.Errorf("incorrect block proof: %w", err)
}
var stateProof struct {
Proof tlb.MerkleProof[tlb.ShardStateUnsplit]
}
decoder := tlb.NewDecoder()
if cellsMap != nil {
decoder = decoder.WithPrunedResolver(func(hash tlb.Bits256) (*boc.Cell, error) {
cell, ok := cellsMap[hash]
if ok {
return cell, nil
}
return nil, errors.New("not found")
})
}
err = decoder.Unmarshal(proof[1], &stateProof)
if err != nil {
return nil, err
}
if stateProof.Proof.VirtualHash != block.VirtualRoot.StateUpdate.ToHash {
return nil, errors.New("invalid virtual hash")
}
return &stateProof.Proof.VirtualRoot, nil
}

func checkBlockProof(proof boc.Cell, blockRootHash ton.Bits256) (*tlb.MerkleProof[tlb.Block], error) {
var res tlb.MerkleProof[tlb.Block]
err := tlb.Unmarshal(&proof, &res) // merkle hash and depth checks inside
if err != nil {
return nil, fmt.Errorf("failed to unmarshal block proof: %w", err)
}
if ton.Bits256(res.VirtualHash) != blockRootHash {
return nil, fmt.Errorf("invalid block root hash")
}
return &res, nil // return new_hash field of MerkleUpdate of ShardState
}
Loading
Loading