Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create file storage for save wallet dump #193

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/BurntSushi/toml v1.2.1
github.com/Code-Hex/go-generics-cache v1.3.0
github.com/avast/retry-go v3.0.0+incompatible
github.com/aws/aws-sdk-go v1.45.8
github.com/caarlos0/env/v6 v6.10.1
github.com/getsentry/sentry-go v0.22.0
github.com/ghodss/yaml v1.0.0
Expand Down Expand Up @@ -45,6 +46,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
Expand Down
12 changes: 10 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/aws/aws-sdk-go v1.45.8 h1:QbOMBTuRYx11fBwNSAJuztXmQf47deFz+CVYjakqmRo=
github.com/aws/aws-sdk-go v1.45.8/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
Expand Down Expand Up @@ -161,6 +163,10 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
Expand Down Expand Up @@ -264,8 +270,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tonkeeper/scam_backoffice_rules v0.0.0-20230911103049-ddd89c5c0c4e h1:rePeUIT9A9FrJH3X6usFgdxvrnRPU+pmJ1R2HqBmhp4=
github.com/tonkeeper/scam_backoffice_rules v0.0.0-20230911103049-ddd89c5c0c4e/go.mod h1:JS7CStRKXjl+nZtRk+f0PReL+mVityMP4O3HupHfyfg=
github.com/tonkeeper/scam_backoffice_rules v0.0.0-20230911133119-702402af5714 h1:HwtfwtwZK6OyeqvTCqFTxiY8p7gPSS23pV8nir/tKmk=
github.com/tonkeeper/scam_backoffice_rules v0.0.0-20230911133119-702402af5714/go.mod h1:JS7CStRKXjl+nZtRk+f0PReL+mVityMP4O3HupHfyfg=
github.com/tonkeeper/tongo v1.2.3-0.20230906163250-fcf4e9b56841 h1:ls2rVf+DV45opFCm/fLSfztjx3JeeDYza5rrOnh1kBw=
Expand Down Expand Up @@ -374,6 +378,7 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
Expand Down Expand Up @@ -439,11 +444,13 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down Expand Up @@ -591,6 +598,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
Expand Down
24 changes: 23 additions & 1 deletion pkg/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package api
import (
"context"
"fmt"
"reflect"

"github.com/go-faster/errors"
"github.com/tonkeeper/opentonapi/pkg/chainstate"
"github.com/tonkeeper/opentonapi/pkg/core"
"github.com/tonkeeper/opentonapi/pkg/file_storage"
"github.com/tonkeeper/opentonapi/pkg/rates"
"github.com/tonkeeper/opentonapi/pkg/spam"
"github.com/tonkeeper/tongo"
Expand Down Expand Up @@ -39,6 +41,7 @@ type Handler struct {
metaCache metadataCache
mempoolEmulate mempoolEmulate
tonConnect *tonconnect.Server
fileStorage fileStorage
}

// Options configures behavior of a Handler instance.
Expand All @@ -52,6 +55,7 @@ type Options struct {
spamFilter spamFilter
ratesSource ratesSource
tonConnectSecret string
fileStorage fileStorage
}

type Option func(o *Options)
Expand Down Expand Up @@ -109,6 +113,19 @@ func WithTonConnectSecret(tonConnectSecret string) Option {
}
}

func WithFileStorage(fileStorage fileStorage) Option {
return func(o *Options) {
if isNil(fileStorage) {
fileStorage = nil
}
o.fileStorage = fileStorage
}
}

func isNil(i any) bool {
return i == nil || reflect.ValueOf(i).IsNil()
}

func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) {
options := &Options{}
for _, o := range opts {
Expand Down Expand Up @@ -136,6 +153,10 @@ func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) {
if options.executor == nil {
return nil, fmt.Errorf("executor is not configured")
}
if options.fileStorage == nil {
logger.Warn("using local file storage")
options.fileStorage = filestorage.NewLocalFileStorage()
}
tonConnect, err := tonconnect.NewTonConnect(options.executor, options.tonConnectSecret)
if err != nil {
return nil, fmt.Errorf("failed to init tonconnect")
Expand All @@ -161,7 +182,8 @@ func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) {
traces: cache.NewLRUCache[string, *core.Trace](10000, "mempool_traces_cache"),
accountsTraces: cache.NewLRUCache[tongo.AccountID, []string](10000, "accounts_traces_cache"),
},
tonConnect: tonConnect,
tonConnect: tonConnect,
fileStorage: options.fileStorage,
}, nil
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/api/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ type spamFilter interface {
CheckJettonAction(address tongo.AccountID, symbol string) rules.TypeOfAction
}

type fileStorage interface {
SaveWalletBackup(bucketName, fileName string, data []byte) error
GetWalletBackup(bucketName, fileName string) ([]byte, error)
}

type metadataCache struct {
collectionsCache cache.Cache[tongo.AccountID, tep64.Metadata]
jettonsCache cache.Cache[tongo.AccountID, tep64.Metadata]
Expand Down
23 changes: 8 additions & 15 deletions pkg/api/wallet_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/tonkeeper/tongo/ton"
"io"
"io/ioutil"
"net/http"
"os"
"time"

filestorage "github.com/tonkeeper/opentonapi/pkg/file_storage"
"github.com/tonkeeper/tongo/ton"

"github.com/tonkeeper/opentonapi/pkg/oas"
"github.com/tonkeeper/opentonapi/pkg/wallet"
Expand All @@ -38,19 +38,12 @@ func (h Handler) SetWalletBackup(ctx context.Context, request oas.SetWalletBacku
return toError(http.StatusBadRequest, fmt.Errorf("wallet must have more than 1 TON"))
}

fileName := fmt.Sprintf("%x.dump", pubKey)
tempFileName := fileName + fmt.Sprintf(".temp%v", time.Now().Nanosecond()+time.Now().Second())
file, err := os.Create(tempFileName)
data, err := ioutil.ReadAll(request.Data)
if err != nil {
return toError(http.StatusInternalServerError, err)
}
defer file.Close()
_, err = io.Copy(file, io.LimitReader(request.Data, 640*1024)) //640K ought to be enough for anybody
if err != nil {
return toError(http.StatusInternalServerError, err)
}
file.Close()
err = os.Rename(tempFileName, fileName)

err = h.fileStorage.SaveWalletBackup(filestorage.WalletBackupBucket, fmt.Sprintf("%v.dump", hex.EncodeToString(pubKey)), data)
if err != nil {
return toError(http.StatusInternalServerError, err)
}
Expand All @@ -66,7 +59,7 @@ func (h Handler) GetWalletBackup(ctx context.Context, params oas.GetWalletBackup
return nil, toError(http.StatusBadRequest, fmt.Errorf("failed verify"))
}

dump, err := os.ReadFile(fmt.Sprintf("%v.dump", hex.EncodeToString(pubKey)))
dump, err := h.fileStorage.GetWalletBackup(filestorage.WalletBackupBucket, fmt.Sprintf("%v.dump", hex.EncodeToString(pubKey)))
if err != nil {
return nil, toError(http.StatusInternalServerError, err)
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ type Config struct {
Accounts accountsList `env:"ACCOUNTS"`
LiteServers []config.LiteServer `env:"LITE_SERVERS"`
}
AWS struct {
AccessKeyID string `env:"AWS_ACCESS_KEY_ID"`
SecretAccessKey string `env:"AWS_SECRET_ACCESS_KEY"`
}
TonConnect struct {
Secret string `env:"TON_CONNECT_SECRET"`
}
Expand Down
48 changes: 48 additions & 0 deletions pkg/file_storage/local_storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package filestorage

import (
"bytes"
"fmt"
"io"
"os"
"time"
)

type LocalFileStorage struct{}

func NewLocalFileStorage() *LocalFileStorage {
return &LocalFileStorage{}
}

func (s *LocalFileStorage) SaveWalletBackup(bucketName, fileName string, data []byte) error {
tempFileName := fileName + fmt.Sprintf(".temp%v", time.Now().Nanosecond()+time.Now().Second())
file, err := os.Create(tempFileName)
if err != nil {
return err
}
defer file.Close()

reader := bytes.NewReader(data)
buffer := make([]byte, len(data))
if _, err = reader.Read(buffer); err != nil {
return err
}
_, err = io.Copy(file, io.LimitReader(reader, 640*1024)) //640K ought to be enough for anybody
if err != nil {
return err
}
err = os.Rename(tempFileName, fileName)
if err != nil {
return err
}

return nil
}

func (s *LocalFileStorage) GetWalletBackup(bucketName, fileName string) ([]byte, error) {
dump, err := os.ReadFile(fileName)
if err != nil {
return nil, err
}
return dump, nil
}
81 changes: 81 additions & 0 deletions pkg/file_storage/s3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package filestorage

import (
"bytes"
"fmt"
"io/ioutil"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)

const (
WalletBackupBucket = "wallet-backup"
)

type FileStorage struct {
Client *s3.S3
}

func NewFileStorageConnection(accessKeyID, secretAccessKey string) *FileStorage {
if accessKeyID == "" || secretAccessKey == "" {
return nil
}
s3Config := &aws.Config{
Credentials: credentials.NewStaticCredentials(
accessKeyID,
secretAccessKey,
""),
Endpoint: aws.String("https://ams3.digitaloceanspaces.com"),
Region: aws.String("us-east-1"),
}
newSession, err := session.NewSession(s3Config)
if err != nil {
log.Fatalf("failed to create session file_storage: %v", err)
}
s3Client := s3.New(newSession)

_, err = s3Client.ListBuckets(nil) // ping for success connect
if err != nil {
log.Fatalf("failed to connect to file_storage: %v", err)
}

return &FileStorage{
Client: s3Client,
}
}

func (s *FileStorage) SaveWalletBackup(bucketName, fileName string, data []byte) error {
_, err := s.Client.PutObject(&s3.PutObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(fileName),
Body: bytes.NewReader(data),
ACL: aws.String("public-read"),
})
if err != nil {
return fmt.Errorf("failed to upload file to S3: %v", err)
}

return nil
}

func (s *FileStorage) GetWalletBackup(bucketName, fileName string) ([]byte, error) {
resp, err := s.Client.GetObject(&s3.GetObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(fileName),
})
if err != nil {
return nil, fmt.Errorf("failed to get file from S3: %v", err)
}
defer resp.Body.Close()

fileData, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read file data: %v", err)
}

return fileData, nil
}