Skip to content

Commit

Permalink
Client.GetBlock() to verify hash of requested block
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksej-paschenko committed Dec 21, 2023
1 parent bea54d2 commit 59128f6
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 6 deletions.
52 changes: 47 additions & 5 deletions liteapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ var (
ErrAccountNotFound = errors.New("account not found")
)

// ProofPolicy specifies a policy for proof checks.
// This feature is experimental and can be changed or removed in the future.
type ProofPolicy uint64

const (
// ProofPolicyUnsafe disables proof checks.
ProofPolicyUnsafe ProofPolicy = 0
ProofPolicyVerifyRequestedBlockHash ProofPolicy = 1 << 0
)

func (p ProofPolicy) enabled(policy ProofPolicy) bool {
return p&policy != 0
}

// Client provides a convenient way to interact with TON blockchain.
//
// By default, it uses a single connection to a lite server.
Expand All @@ -53,7 +67,10 @@ var (
// To avoid this, you can use WithBlock() method to specify a target block for all requests.
type Client struct {
pool *pool.FailoverPool
// proofPolicy specifies a policy for proof checks.
proofPolicy ProofPolicy

// mu protects targetBlockID.
mu sync.RWMutex
targetBlockID *ton.BlockIDExt
}
Expand All @@ -66,6 +83,8 @@ type Options struct {
MaxConnections int
// InitCtx is used when opening a new connection to lite servers during the initialization.
InitCtx context.Context
// ProofPolicy specifies a policy for proof checks.
ProofPolicy ProofPolicy
}

type Option func(o *Options) error
Expand Down Expand Up @@ -95,6 +114,13 @@ func WithTimeout(timeout time.Duration) Option {
}
}

func WithProofPolicy(policy ProofPolicy) Option {
return func(o *Options) error {
o.ProofPolicy = policy
return nil
}
}

// WithInitializationContext specifies a context to be used
// when opening a new connection to lite servers during the initialization.
func WithInitializationContext(ctx context.Context) Option {
Expand Down Expand Up @@ -211,6 +237,7 @@ func NewClient(opts ...Option) (*Client, error) {
Timeout: 60 * time.Second,
MaxConnections: defaultMaxConnectionsNumber,
InitCtx: context.Background(),
ProofPolicy: ProofPolicyUnsafe,
}
for _, o := range opts {
if err := o(options); err != nil {
Expand Down Expand Up @@ -239,7 +266,8 @@ func NewClient(opts ...Option) (*Client, error) {
return nil, fmt.Errorf("all liteservers are unavailable")
}
client := Client{
pool: pool.NewFailoverPool(liteclients),
pool: pool.NewFailoverPool(liteclients),
proofPolicy: options.ProofPolicy,
}
go client.pool.Run(context.TODO())
return &client, nil
Expand Down Expand Up @@ -298,12 +326,26 @@ func (c *Client) GetBlock(ctx context.Context, blockID ton.BlockIDExt) (tlb.Bloc
if len(cells) != 1 {
return tlb.Block{}, boc.ErrNotSingleRoot
}
var data tlb.Block
err = tlb.NewDecoder().Unmarshal(cells[0], &data)
if err != nil {
root := cells[0]
decoder := tlb.NewDecoder()
var block tlb.Block
if err := decoder.Unmarshal(root, &block); err != nil {
return tlb.Block{}, err
}
return data, nil
if c.proofPolicy.enabled(ProofPolicyVerifyRequestedBlockHash) {
// this should be quite fast because
// when unmarshalling a block, we calculate hashes for transactions and messages.
// so most of the cells' hashes should be in the cache.
root.ResetCounters()
hash, err := decoder.Hasher().Hash(root)
if err != nil {
return tlb.Block{}, fmt.Errorf("failed to calculate block hash: %w", err)
}
if !bytes.Equal(hash[:], blockID.RootHash[:]) {
return tlb.Block{}, fmt.Errorf("block hash mismatch")
}
}
return block, nil
}

func (c *Client) GetBlockRaw(ctx context.Context, blockID ton.BlockIDExt) (liteclient.LiteServerBlockDataC, error) {
Expand Down
2 changes: 1 addition & 1 deletion liteapi/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func TestGetAllShards(t *testing.T) {
}

func TestGetBlock(t *testing.T) {
api, err := NewClient(Mainnet(), FromEnvs())
api, err := NewClient(Mainnet(), FromEnvs(), WithProofPolicy(ProofPolicyVerifyRequestedBlockHash))
if err != nil {
t.Fatal(err)
}
Expand Down
6 changes: 6 additions & 0 deletions tlb/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,9 @@ func decodeCell(c *boc.Cell, val reflect.Value) error {
func decodeBitString(c *boc.Cell, val reflect.Value) error {
return fmt.Errorf("bigString decoding not supported")
}

// Hasher returns boc.Hasher that is used to calculate hashes when decoding.
func (dec *Decoder) Hasher() *boc.Hasher {
return dec.hasher

}

0 comments on commit 59128f6

Please sign in to comment.