diff --git a/example/block-scan/main.go b/example/block-scan/main.go index da30c3f5..ccf8e3ad 100644 --- a/example/block-scan/main.go +++ b/example/block-scan/main.go @@ -42,6 +42,9 @@ func getNotSeenShards(ctx context.Context, api ton.APIClientWrapped, shard *ton. return ret, nil } +// FYI: You can find more advanced, optimized and parallelized block scanner in payment network implementation: +// https://github.com/xssnick/ton-payment-network/blob/master/tonpayments/chain/block-scan.go + func main() { client := liteclient.NewConnectionPool() diff --git a/tl/loader.go b/tl/loader.go index 83b200a2..762d6ea2 100644 --- a/tl/loader.go +++ b/tl/loader.go @@ -319,7 +319,7 @@ func serializeField(tags []string, value reflect.Value) (buf []byte, err error) } else if value.Type() == cellArrType { cells := value.Interface().([]*cell.Cell) if num > 0 && num != len(cells) { - return nil, fmt.Errorf("incorrect cells len in field %s", value.Type().String()) + return nil, fmt.Errorf("incorrect cells len %d in field %s", len(cells), value.Type().String()) } return ToBytes(cell.ToBOCWithFlags(cells, false)), nil } @@ -528,7 +528,7 @@ func parseField(data []byte, tags []string, value *reflect.Value) (_ []byte, err return data, nil } else if value.Type() == cellArrType { if num > 0 && num != len(cells) { - return nil, fmt.Errorf("incorrect cells len in field %s", value.Type().String()) + return nil, fmt.Errorf("incorrect cells len %d in field %s", len(cells), value.Type().String()) } value.Set(reflect.ValueOf(cells)) return data, nil diff --git a/ton/block.go b/ton/block.go index b54557e1..48d57c43 100644 --- a/ton/block.go +++ b/ton/block.go @@ -174,7 +174,7 @@ type ZeroStateIDExt struct { type AllShardsInfo struct { ID *BlockIDExt `tl:"struct"` - Proof []*cell.Cell `tl:"cell 2"` + Proof []*cell.Cell `tl:"cell"` Data *cell.Cell `tl:"cell"` } @@ -524,28 +524,46 @@ func (c *APIClient) GetBlockShardsInfo(ctx context.Context, master *BlockIDExt) } if c.proofCheckPolicy != ProofCheckPolicyUnsafe { - shardState, err := CheckBlockShardStateProof(t.Proof, master.RootHash) - if err != nil { - return nil, fmt.Errorf("failed to check proof: %w", err) + if len(t.Proof) == 0 { + return nil, fmt.Errorf("empty proof") } - mcShort := shardState.McStateExtra.BeginParse() - if v, err := mcShort.LoadUInt(16); err != nil || v != 0xcc26 { - return nil, fmt.Errorf("invalic mc extra in proof") - } + switch len(t.Proof) { + case 1: + blockProof, err := CheckBlockProof(t.Proof[0], master.RootHash) + if err != nil { + return nil, fmt.Errorf("failed to check proof: %w", err) + } - dictProof, err := mcShort.LoadMaybeRef() - if err != nil { - return nil, fmt.Errorf("failed to load dict proof: %w", err) - } + if blockProof.Extra == nil || blockProof.Extra.Custom == nil || !bytes.Equal(blockProof.Extra.Custom.ShardHashes.AsCell().Hash(0), t.Data.MustPeekRef(0).Hash()) { + return nil, fmt.Errorf("incorrect proof") + } + case 2: // old LS compatibility + shardState, err := CheckBlockShardStateProof(t.Proof, master.RootHash) + if err != nil { + return nil, fmt.Errorf("failed to check proof: %w", err) + } - if dictProof == nil && inf.ShardHashes.IsEmpty() { - return []*BlockIDExt{}, nil - } + mcShort := shardState.McStateExtra.BeginParse() + if v, err := mcShort.LoadUInt(16); err != nil || v != 0xcc26 { + return nil, fmt.Errorf("invalic mc extra in proof") + } + + dictProof, err := mcShort.LoadMaybeRef() + if err != nil { + return nil, fmt.Errorf("failed to load dict proof: %w", err) + } - if (dictProof == nil) != inf.ShardHashes.IsEmpty() || - !bytes.Equal(dictProof.MustToCell().Hash(0), t.Data.MustPeekRef(0).Hash()) { - return nil, fmt.Errorf("incorrect proof") + if dictProof == nil && inf.ShardHashes.IsEmpty() { + return []*BlockIDExt{}, nil + } + + if (dictProof == nil) != inf.ShardHashes.IsEmpty() || + !bytes.Equal(dictProof.MustToCell().Hash(0), t.Data.MustPeekRef(0).Hash()) { + return nil, fmt.Errorf("incorrect proof") + } + default: + return nil, fmt.Errorf("incorrect proof roots num") } } diff --git a/ton/jetton/integration_test.go b/ton/jetton/integration_test.go index cae66635..d7c4170d 100644 --- a/ton/jetton/integration_test.go +++ b/ton/jetton/integration_test.go @@ -131,6 +131,12 @@ func TestJettonMasterClient_Transfer(t *testing.T) { panic(err) } + // wait next block to be sure everything updated + block, err = api.WaitForBlock(block.SeqNo + 3).GetMasterchainInfo(ctx) + if err != nil { + t.Fatal("Wait master err:", err.Error()) + } + b2, err := tokenWallet.GetBalanceAtBlock(ctx, block) if err != nil { t.Fatal(err) diff --git a/ton/nft/integration_test.go b/ton/nft/integration_test.go index b5dc262b..cf63a473 100644 --- a/ton/nft/integration_test.go +++ b/ton/nft/integration_test.go @@ -108,6 +108,12 @@ func Test_NftMintTransfer(t *testing.T) { t.Fatal("Send err:", err.Error()) } + // wait next block to be sure everything updated + block, err = api.WaitForBlock(block.SeqNo + 3).GetMasterchainInfo(ctx) + if err != nil { + t.Fatal("Wait master err:", err.Error()) + } + newData, err := nft.GetNFTDataAtBlock(ctx, block) if err != nil { t.Fatal("GetNFTData err:", err.Error()) diff --git a/ton/payments/integration_test.go b/ton/payments/integration_test.go index d8f2ef20..6fa6dde9 100644 --- a/ton/payments/integration_test.go +++ b/ton/payments/integration_test.go @@ -73,6 +73,11 @@ func TestClient_DeployAsyncChannel(t *testing.T) { t.Fatal(fmt.Errorf("failed to deploy channel: %w", err)) } + block, err = client.api.WaitForBlock(block.SeqNo + 3).GetMasterchainInfo(context.Background()) + if err != nil { + t.Fatal(fmt.Errorf("failed to wait block: %w", err)) + } + ch, err := client.GetAsyncChannel(context.Background(), block, channelAddr, true) if err != nil { t.Fatal(fmt.Errorf("failed to get channel: %w", err)) diff --git a/ton/wallet/integration_test.go b/ton/wallet/integration_test.go index dfc85c38..b8e3fe2c 100644 --- a/ton/wallet/integration_test.go +++ b/ton/wallet/integration_test.go @@ -170,7 +170,7 @@ func TestWallet_DeployContract(t *testing.T) { } t.Logf("contract address: %s", addr.String()) - res, err := api.RunGetMethod(ctx, block, addr, "dappka", 5, 10) + res, err := api.WaitForBlock(block.SeqNo+1).RunGetMethod(ctx, block, addr, "dappka", 5, 10) if err != nil { t.Fatal("run err:", err) }