diff --git a/README.md b/README.md index 5b8eb43..401fa97 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ For more information on Jettons compatibility, see [Jettons compatibility](/jett |------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `LITESERVER` | IP and port of lite server, example: `185.86.76.183:5815` | | `LITESERVER_KEY` | public key of lite server `5v2dHtSclsGsZVbNVwTj4hQDso5xvQjzL/yPEHJevHk=`.
Be careful with base64 encoding and ENV var. Use '' | +| `LITESERVER_RATE_LIMIT` | If you have a rented node with an RPS limit, set the RPS value here equal to (or preferably slightly less than) the limit. Default: 100. | | `SEED` | seed phrase for main hot wallet. 24 words compatible with standard TON wallets | | `DB_URI` | URI for DB connection, example:
`postgresql://db_user:db_password@localhost:5432/payment_processor` | | `POSTGRES_DB` | name of database for storing payments data | diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 6824334..e9066c2 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -53,7 +53,7 @@ type contract struct { } // NewConnection creates new Blockchain connection -func NewConnection(addr, key string) (*Connection, error) { +func NewConnection(addr, key string, rateLimit int) (*Connection, error) { client := liteclient.NewConnectionPool() ctx, cancel := context.WithTimeout(context.Background(), time.Second*120) @@ -64,6 +64,8 @@ func NewConnection(addr, key string) (*Connection, error) { return nil, fmt.Errorf("connection err: %v", err.Error()) } + limitedClient := newLimitedClient(client, rateLimit) + var wrappedClient ton.APIClientWrapped if config.Config.ProofCheckEnabled { @@ -77,7 +79,7 @@ func NewConnection(addr, key string) (*Connection, error) { return nil, fmt.Errorf("get network config from url err: %s", err.Error()) } - wrappedClient = ton.NewAPIClient(client, ton.ProofCheckPolicySecure).WithRetry() + wrappedClient = ton.NewAPIClient(limitedClient, ton.ProofCheckPolicySecure).WithRetry() wrappedClient.SetTrustedBlockFromConfig(cfg) log.Infof("Fetching and checking proofs since config init block ...") @@ -88,7 +90,7 @@ func NewConnection(addr, key string) (*Connection, error) { log.Infof("Proof checks are completed") } else { - wrappedClient = ton.NewAPIClient(client, ton.ProofCheckPolicyUnsafe).WithRetry() + wrappedClient = ton.NewAPIClient(limitedClient, ton.ProofCheckPolicyUnsafe).WithRetry() } // TODO: replace after tonutils fix diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go index 0582a33..f68c2cc 100644 --- a/blockchain/blockchain_test.go +++ b/blockchain/blockchain_test.go @@ -30,7 +30,7 @@ func connect(t *testing.T) *Connection { if key == "" { t.Fatal("empty key var") } - c, err := NewConnection(server, key) + c, err := NewConnection(server, key, 100) if err != nil { t.Fatal("connections err: ", err) } diff --git a/blockchain/limited_client.go b/blockchain/limited_client.go new file mode 100644 index 0000000..fb1d5da --- /dev/null +++ b/blockchain/limited_client.go @@ -0,0 +1,46 @@ +package blockchain + +import ( + "context" + "fmt" + + "github.com/xssnick/tonutils-go/tl" + "github.com/xssnick/tonutils-go/ton" + "golang.org/x/time/rate" +) + +type limitedLiteClient struct { + limiter *rate.Limiter + original ton.LiteClient +} + +func newLimitedClient(lc ton.LiteClient, rateLimit int) *limitedLiteClient { + return &limitedLiteClient{ + original: lc, + limiter: rate.NewLimiter(rate.Limit(rateLimit), 1), + } +} + +func (w *limitedLiteClient) QueryLiteserver(ctx context.Context, payload tl.Serializable, result tl.Serializable) error { + err := w.limiter.Wait(ctx) + if err != nil { + return fmt.Errorf("limiter err: %w", err) + } + return w.original.QueryLiteserver(ctx, payload, result) +} + +func (w *limitedLiteClient) StickyContext(ctx context.Context) context.Context { + return w.original.StickyContext(ctx) +} + +func (w *limitedLiteClient) StickyNodeID(ctx context.Context) uint32 { + return w.original.StickyNodeID(ctx) +} + +func (w *limitedLiteClient) StickyContextNextNode(ctx context.Context) (context.Context, error) { + return w.original.StickyContextNextNode(ctx) +} + +func (w *limitedLiteClient) StickyContextNextNodeBalanced(ctx context.Context) (context.Context, error) { + return w.original.StickyContextNextNodeBalanced(ctx) +} diff --git a/cmd/processor/main.go b/cmd/processor/main.go index 5fdca78..a185e06 100644 --- a/cmd/processor/main.go +++ b/cmd/processor/main.go @@ -32,7 +32,7 @@ func main() { signal.Notify(sigChannel, os.Interrupt, syscall.SIGTERM) wg := new(sync.WaitGroup) - bcClient, err := blockchain.NewConnection(config.Config.LiteServer, config.Config.LiteServerKey) + bcClient, err := blockchain.NewConnection(config.Config.LiteServer, config.Config.LiteServerKey, config.Config.LiteServerRateLimit) if err != nil { log.Fatalf("blockchain connection error: %v", err) } diff --git a/cmd/testutil/main.go b/cmd/testutil/main.go index 2083e62..7863e0d 100644 --- a/cmd/testutil/main.go +++ b/cmd/testutil/main.go @@ -51,7 +51,7 @@ func main() { log.Fatalf("invalid HOT_WALLET_B env var") } - bcClient, err := blockchain.NewConnection(config.Config.LiteServer, config.Config.LiteServerKey) + bcClient, err := blockchain.NewConnection(config.Config.LiteServer, config.Config.LiteServerKey, config.Config.LiteServerRateLimit) if err != nil { log.Fatalf("blockchain connection error: %v", err) } diff --git a/config/config.go b/config/config.go index dda3955..7a1ff5d 100644 --- a/config/config.go +++ b/config/config.go @@ -1,15 +1,16 @@ package config import ( + "log" + "math/big" + "strings" + "time" + "github.com/caarlos0/env/v6" "github.com/shopspring/decimal" "github.com/tonkeeper/tongo/boc" "github.com/xssnick/tonutils-go/address" "github.com/xssnick/tonutils-go/tlb" - "log" - "math/big" - "strings" - "time" ) const MaxJettonForwardTonAmount = 20_000_000 @@ -39,6 +40,7 @@ const MaxCommentLength = 1000 // qty in chars var Config = struct { LiteServer string `env:"LITESERVER,required"` LiteServerKey string `env:"LITESERVER_KEY,required"` + LiteServerRateLimit int `env:"LITESERVER_RATE_LIMIT" envDefault:"100"` Seed string `env:"SEED,required"` DatabaseURI string `env:"DB_URI,required"` APIPort int `env:"API_PORT,required"` diff --git a/go.mod b/go.mod index 28bb0e1..95e4e8c 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/tonkeeper/tongo v1.9.9 github.com/xssnick/tonutils-go v1.10.2 + golang.org/x/time v0.10.0 ) require ( diff --git a/go.sum b/go.sum index 8081be6..4db737c 100644 --- a/go.sum +++ b/go.sum @@ -196,6 +196,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=