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
3 changes: 2 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ jobs:
env:
LD_LIBRARY_PATH: ${{ github.workspace }}/libs
LITE_SERVERS: ${{ secrets.LITE_SERVERS }}
TEST_CI: 1
run: |
sudo apt-get install -y libsecp256k1-0
mkdir -p ${{ env.LD_LIBRARY_PATH}}
wget https://github.com/tonkeeper/tongo/raw/master/lib/linux/libemulator.so -O ${{ env.LD_LIBRARY_PATH}}/libemulator.so
make test
make test-ci
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ tlb/parser/testdata/*.output.out
vendor/
cover.out
*.output.json
.cursor/plans
.zed
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@

.PHONY: all imports fmt test
.PHONY: all imports fmt test test-ci

all: imports fmt test

imports:
goimports -l -w $$(go list -f {{.Dir}} ./... | grep -v /vendor/)
fmt:
gofmt -s -l -w $$(go list -f {{.Dir}} ./... | grep -v /vendor/)
test:
test:
go test $$(go list ./... | grep -v /vendor/) -timeout 5m -race -coverprofile cover.out
test-ci:
go test -v $$(go list ./... | grep -v /vendor/) -race
12 changes: 10 additions & 2 deletions abi/generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"math/big"
"os"
"reflect"
"testing"

Expand Down Expand Up @@ -93,6 +94,9 @@ func mustToMsgAddress(x string) tlb.MsgAddress {
}

func TestGetMethods(t *testing.T) {
if os.Getenv("TEST_CI") == "1" {
t.SkipNow()
}
extensionAddrBytes, err := hex.DecodeString("3a8f7e96e21bd7018207282a9071ef467d88250589ca6e25f7c8f45093282a4c")
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -844,6 +848,9 @@ func TestGetMethods(t *testing.T) {
}

func TestWhalesNominators(t *testing.T) {
if os.Getenv("TEST_CI") == "1" {
t.SkipNow()
}
address := ton.MustParseAccountID("EQBI-wGVp_x0VFEjd7m9cEUD3tJ_bnxMSp0Tb9qz757ATEAM")
client, err := liteapi.NewClient(liteapi.Mainnet(), liteapi.FromEnvs())
if err != nil {
Expand Down Expand Up @@ -946,7 +953,9 @@ func mustAccountIDToMsgAddress(account string) tlb.MsgAddress {
}

func TestMessageDecoder(t *testing.T) {

if os.Getenv("TEST_CI") == "1" {
t.SkipNow()
}
tests := []struct {
name string
boc string
Expand Down Expand Up @@ -1406,7 +1415,6 @@ func TestMessageDecoder(t *testing.T) {
if !reflect.DeepEqual(value, body) {
t.Fatalf("got: %v, want: %v", value, body)
}

},
},

Expand Down
5 changes: 4 additions & 1 deletion abi/inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/hex"
"fmt"
"os"
"reflect"
"testing"

Expand All @@ -24,7 +25,9 @@ const (
)

func Test_contractInspector_InspectContract(t *testing.T) {

if os.Getenv("TEST_CI") == "1" {
t.SkipNow()
}
//mainnetConfig, _ := boc.DeserializeBocBase64(mainnetConfig)
testnetConfig, _ := boc.DeserializeBocBase64(testnetConfig)

Expand Down
3 changes: 3 additions & 0 deletions address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tongo

import (
"context"
"os"
"testing"

"github.com/tonkeeper/tongo/contract/dns"
Expand All @@ -28,6 +29,7 @@ func TestParseAddress(t *testing.T) {
typeParse int
request string
response string
disabled bool
}

for _, test := range []testCase{
Expand All @@ -44,6 +46,7 @@ func TestParseAddress(t *testing.T) {
response: "0:91d73056e035232f09aaf8242a1d51eea98b6a5bebbf8ac0c9e521d02a1a4bdb",
},
{
disabled: os.Getenv("TEST_CI") == "1",
name: "Parse dns to raw address",
typeParse: parseDnsToRawAddress,
request: "blackpepper.ton",
Expand Down
2 changes: 1 addition & 1 deletion code/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (c *FunCCompiler) Compile(files map[string]string) (string, []byte, error)
return "", nil, err
}
if !respBody.Success {
return "", nil, fmt.Errorf(respBody.Error)
return "", nil, fmt.Errorf("%s", respBody.Error)
}
boc, err := hex.DecodeString(respBody.Hex)
return respBody.Fift, boc, err
Expand Down
4 changes: 4 additions & 0 deletions contract/dns/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/hex"
"fmt"
"log"
"os"
"testing"

"github.com/tonkeeper/tongo/liteapi"
Expand All @@ -13,6 +14,9 @@ import (
)

func TestResolve(t *testing.T) {
if os.Getenv("TEST_CI") == "1" {
t.Skip("Skipping test in CI")
}
client, err := liteapi.NewClient(liteapi.Mainnet(), liteapi.FromEnvs())
if err != nil {
log.Fatalf("Unable to create tongo client: %v", err)
Expand Down
162 changes: 154 additions & 8 deletions liteapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ const (
ProofPolicyFast
)

type connPool interface {
BestMasterchainInfoClient() *pool.MasterchainInfoClient
BestMasterchainClient(ctx context.Context) (*liteclient.Client, ton.BlockIDExt, error)
BestClientByAccountID(ctx context.Context, accountID ton.AccountID, archiveRequired bool) (*liteclient.Client, ton.BlockIDExt, error)
BestClientByBlockID(ctx context.Context, blockID ton.BlockID) (*liteclient.Client, error)
WaitMasterchainSeqno(ctx context.Context, seqno uint32, timeout time.Duration) error
ConnectionsNumber() int
Status() pool.Status
}

type connPoolProxy struct {
primary connPool
fallback connPool
}

// Client provides a convenient way to interact with TON blockchain.
//
// By default, it uses a single connection to a lite server.
Expand All @@ -62,7 +77,7 @@ const (
// the account state can be obtained from a block that is earlier in the blockchain than the master head you obtained at step 1.
// To avoid this, you can use WithBlock() method to specify a target block for all requests.
type Client struct {
pool *pool.ConnPool
pool connPool
// proofPolicy specifies a policy for proof checks.
proofPolicy ProofPolicy

Expand Down Expand Up @@ -93,8 +108,19 @@ type Options struct {

SyncConnectionsInitialization bool
PoolStrategy pool.Strategy

// Use public servers if the main ones are unavailable
FallbackPolicy FallbackPolicy
}

type FallbackPolicy int

const (
FallbackNone = iota
FallbackMainnet // https://ton.org/global.config.json
FallbackTestnet // https://ton.org/testnet-global.config.json
)

type Option func(o *Options) error

func WithLiteServers(servers []config.LiteServer) Option {
Expand Down Expand Up @@ -166,6 +192,13 @@ func WithInitializationContext(ctx context.Context) Option {
}
}

func WithFallbackPolicy(policy FallbackPolicy) Option {
return func(o *Options) error {
o.FallbackPolicy = policy
return nil
}
}

// FromEnvsOrMainnet configures a client to use lite servers from the LITE_SERVERS env variable.
// If LITE_SERVERS is not set, it downloads public config for mainnet from ton.org.
func FromEnvsOrMainnet() Option {
Expand Down Expand Up @@ -281,22 +314,55 @@ func NewClient(options ...Option) (*Client, error) {
return nil, err
}
}
if len(opts.LiteServers) == 0 {
fallbackOpts := *opts // copy
switch opts.FallbackPolicy {
case FallbackMainnet:
if err := setLiteServersFromURL("https://ton.org/global.config.json", &fallbackOpts); err != nil {
return nil, err
}
case FallbackTestnet:
if err := setLiteServersFromURL("https://ton.org/testnet-global.config.json", &fallbackOpts); err != nil {
return nil, err
}
default:
fallbackOpts.LiteServers = nil
}
if len(opts.LiteServers) == 0 && len(fallbackOpts.LiteServers) == 0 {
return nil, fmt.Errorf("server list empty")
}
connPool := pool.New(opts.PoolStrategy)
initCh := connPool.InitializeConnections(opts.InitCtx, opts.Timeout, opts.MaxConnections, opts.WorkersPerConnection, opts.DetectArchiveNodes, opts.LiteServers)
if opts.SyncConnectionsInitialization {
if err := <-initCh; err != nil {
return nil, err
var primary *pool.ConnPool
if len(opts.LiteServers) != 0 {
primary = pool.New(opts.PoolStrategy)
initCh := primary.InitializeConnections(opts.InitCtx, opts.Timeout, opts.MaxConnections, opts.WorkersPerConnection, opts.DetectArchiveNodes, opts.LiteServers)
if opts.SyncConnectionsInitialization {
if err := <-initCh; err != nil {
return nil, err
}
}
go primary.Run(context.Background())
}
var fallback *pool.ConnPool
if len(fallbackOpts.LiteServers) != 0 {
fallback = pool.New(opts.PoolStrategy)
_ = fallback.InitializeConnections(opts.InitCtx, opts.Timeout, opts.MaxConnections, opts.WorkersPerConnection, opts.DetectArchiveNodes, fallbackOpts.LiteServers)
go fallback.Run(context.Background())
}
var connPool connPool
if primary == nil {
connPool = fallback
} else if fallback == nil {
connPool = primary
} else {
connPool = &connPoolProxy{
primary: primary,
fallback: fallback,
}
}
client := Client{
pool: connPool,
proofPolicy: opts.ProofPolicy,
archiveDetectionEnabled: opts.DetectArchiveNodes,
}
go client.pool.Run(context.TODO())
return &client, nil
}

Expand Down Expand Up @@ -1042,6 +1108,86 @@ func (c *Client) GetOutMsgQueueSizes(ctx context.Context) (liteclient.LiteServer
return res, nil
}

func (p *connPoolProxy) usePrimary() bool {
if p.primary == nil {
return false
}
status := p.primary.Status()
for _, c := range status.Connections {
if c.Connected {
return true
}
}
return false
}

func (p *connPoolProxy) BestMasterchainInfoClient() *pool.MasterchainInfoClient {
if p.usePrimary() {
return p.primary.BestMasterchainInfoClient()
}
if p.fallback != nil {
return p.fallback.BestMasterchainInfoClient()
}
return nil
}

func (p *connPoolProxy) BestMasterchainClient(ctx context.Context) (*liteclient.Client, ton.BlockIDExt, error) {
if p.usePrimary() {
return p.primary.BestMasterchainClient(ctx)
}
if p.fallback != nil {
return p.fallback.BestMasterchainClient(ctx)
}
return nil, ton.BlockIDExt{}, pool.ErrNoConnections
}

func (p *connPoolProxy) BestClientByAccountID(ctx context.Context, accountID ton.AccountID, archiveRequired bool) (*liteclient.Client, ton.BlockIDExt, error) {
if p.usePrimary() {
return p.primary.BestClientByAccountID(ctx, accountID, archiveRequired)
}
if p.fallback != nil {
return p.fallback.BestClientByAccountID(ctx, accountID, archiveRequired)
}
return nil, ton.BlockIDExt{}, pool.ErrNoConnections
}

func (p *connPoolProxy) BestClientByBlockID(ctx context.Context, blockID ton.BlockID) (*liteclient.Client, error) {
if p.usePrimary() {
return p.primary.BestClientByBlockID(ctx, blockID)
}
if p.fallback != nil {
return p.fallback.BestClientByBlockID(ctx, blockID)
}
return nil, pool.ErrNoConnections
}

func (p *connPoolProxy) WaitMasterchainSeqno(ctx context.Context, seqno uint32, timeout time.Duration) error {
if p.usePrimary() {
return p.primary.WaitMasterchainSeqno(ctx, seqno, timeout)
}
if p.fallback != nil {
return p.fallback.WaitMasterchainSeqno(ctx, seqno, timeout)
}
return pool.ErrNoConnections
}

func (p *connPoolProxy) Status() pool.Status {
if p == nil {
return pool.Status{}
}
if p.usePrimary() {
return p.primary.Status()
}
if p.fallback != nil {
return p.fallback.Status()
}
return pool.Status{}
}

func (p *connPoolProxy) ConnectionsNumber() int {
return len(p.Status().Connections)
}

var configCache = make(map[string]*config.GlobalConfigurationFile)
var configCacheMutex sync.RWMutex

Expand Down
Loading