feat: add IPFS rate limiter for downloading stickers and use http server for retrieving stickers (#2611)

This commit is contained in:
Richard Ramos 2022-05-09 09:07:57 -04:00 committed by GitHub
parent 2485e84bf5
commit 0048aaebcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 550 additions and 219 deletions

View File

@ -1 +1 @@
0.98.5 0.98.6

View File

@ -690,11 +690,11 @@ func (b *GethStatusBackend) loadNodeConfig(inputNodeCfg *params.NodeConfig) erro
// Start WakuV1 if WakuV2 is not enabled // Start WakuV1 if WakuV2 is not enabled
conf.WakuConfig.Enabled = !conf.WakuV2Config.Enabled conf.WakuConfig.Enabled = !conf.WakuV2Config.Enabled
// NodeConfig.Version should be taken from params.Version // NodeConfig.Version should be taken from params.Version
// which is set at the compile time. // which is set at the compile time.
// What's cached is usually outdated so we overwrite it here. // What's cached is usually outdated so we overwrite it here.
conf.Version = params.Version conf.Version = params.Version
conf.RootDataDir = b.rootDataDir
conf.DataDir = filepath.Join(b.rootDataDir, conf.DataDir) conf.DataDir = filepath.Join(b.rootDataDir, conf.DataDir)
conf.ShhextConfig.BackupDisabledDataDir = filepath.Join(b.rootDataDir, conf.ShhextConfig.BackupDisabledDataDir) conf.ShhextConfig.BackupDisabledDataDir = filepath.Join(b.rootDataDir, conf.ShhextConfig.BackupDisabledDataDir)
if len(conf.LogDir) == 0 { if len(conf.LogDir) == 0 {
@ -1193,7 +1193,7 @@ func (b *GethStatusBackend) injectAccountsIntoWakuService(w types.WakuKeyManager
} }
if st != nil { if st != nil {
if err := st.InitProtocol(b.statusNode.GethNode().Config().Name, identity, b.appDB, b.multiaccountsDB, acc, logutils.ZapLogger()); err != nil { if err := st.InitProtocol(b.statusNode.GethNode().Config().Name, identity, b.appDB, b.statusNode.HTTPServer(), b.multiaccountsDB, acc, logutils.ZapLogger()); err != nil {
return err return err
} }
// Set initial connection state // Set initial connection state

View File

@ -360,7 +360,7 @@ func _1649164719_add_community_archives_info_tableUpSql() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1649164719_add_community_archives_info_table.up.sql", size: 208, mode: os.FileMode(0664), modTime: time.Unix(1649593899, 0)} info := bindataFileInfo{name: "1649164719_add_community_archives_info_table.up.sql", size: 208, mode: os.FileMode(0664), modTime: time.Unix(1652098406, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd1, 0x4f, 0x80, 0x45, 0xb9, 0xd9, 0x15, 0xe2, 0x78, 0xd0, 0xcb, 0x71, 0xc1, 0x1b, 0xb7, 0x1b, 0x1b, 0x97, 0xfe, 0x47, 0x53, 0x3c, 0x62, 0xbc, 0xdd, 0x3a, 0x94, 0x1a, 0xc, 0x48, 0x76, 0xe}} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd1, 0x4f, 0x80, 0x45, 0xb9, 0xd9, 0x15, 0xe2, 0x78, 0xd0, 0xcb, 0x71, 0xc1, 0x1b, 0xb7, 0x1b, 0x1b, 0x97, 0xfe, 0x47, 0x53, 0x3c, 0x62, 0xbc, 0xdd, 0x3a, 0x94, 0x1a, 0xc, 0x48, 0x76, 0xe}}
return a, nil return a, nil
} }
@ -380,7 +380,7 @@ func _1649174829_add_visitble_tokenUpSql() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1649174829_add_visitble_token.up.sql", size: 84, mode: os.FileMode(0664), modTime: time.Unix(1649882240, 0)} info := bindataFileInfo{name: "1649174829_add_visitble_token.up.sql", size: 84, mode: os.FileMode(0664), modTime: time.Unix(1652098406, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa3, 0x22, 0xc0, 0x2b, 0x3f, 0x4f, 0x3d, 0x5e, 0x4c, 0x68, 0x7c, 0xd0, 0x15, 0x36, 0x9f, 0xec, 0xa1, 0x2a, 0x7b, 0xb4, 0xe3, 0xc6, 0xc9, 0xb4, 0x81, 0x50, 0x4a, 0x11, 0x3b, 0x35, 0x7, 0xcf}} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa3, 0x22, 0xc0, 0x2b, 0x3f, 0x4f, 0x3d, 0x5e, 0x4c, 0x68, 0x7c, 0xd0, 0x15, 0x36, 0x9f, 0xec, 0xa1, 0x2a, 0x7b, 0xb4, 0xe3, 0xc6, 0xc9, 0xb4, 0x81, 0x50, 0x4a, 0x11, 0x3b, 0x35, 0x7, 0xcf}}
return a, nil return a, nil
} }
@ -400,7 +400,7 @@ func _1649882262_add_derived_from_accountsUpSql() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "1649882262_add_derived_from_accounts.up.sql", size: 110, mode: os.FileMode(0664), modTime: time.Unix(1649882324, 0)} info := bindataFileInfo{name: "1649882262_add_derived_from_accounts.up.sql", size: 110, mode: os.FileMode(0664), modTime: time.Unix(1652098406, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x11, 0xb9, 0x44, 0x4d, 0x85, 0x8d, 0x7f, 0xb4, 0xae, 0x4f, 0x5c, 0x66, 0x64, 0xb6, 0xe2, 0xe, 0x3d, 0xad, 0x9d, 0x8, 0x4f, 0xab, 0x6e, 0xa8, 0x7d, 0x76, 0x3, 0xad, 0x96, 0x1, 0xee, 0x5c}} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x11, 0xb9, 0x44, 0x4d, 0x85, 0x8d, 0x7f, 0xb4, 0xae, 0x4f, 0x5c, 0x66, 0x64, 0xb6, 0xe2, 0xe, 0x3d, 0xad, 0x9d, 0x8, 0x4f, 0xab, 0x6e, 0xa8, 0x7d, 0x76, 0x3, 0xad, 0x96, 0x1, 0xee, 0x5c}}
return a, nil return a, nil
} }

236
ipfs/ipfs.go Normal file
View File

@ -0,0 +1,236 @@
package ipfs
import (
"errors"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"sync"
"time"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multibase"
"github.com/wealdtech/go-multicodec"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
)
const infuraAPIURL = "https://ipfs.infura.io:5001/api/v0/cat?arg="
const maxRequestsPerSecond = 3
type taskResponse struct {
err error
response []byte
}
type taskRequest struct {
cid string
download bool
doneChan chan taskResponse
}
type Downloader struct {
ipfsDir string
wg sync.WaitGroup
rateLimiterChan chan taskRequest
inputTaskChan chan taskRequest
client *http.Client
quit chan struct{}
}
func NewDownloader(rootDir string) *Downloader {
ipfsDir := filepath.Clean(filepath.Join(rootDir, "./ipfs"))
if err := os.MkdirAll(ipfsDir, 0700); err != nil {
panic("could not create IPFSDir")
}
d := &Downloader{
ipfsDir: ipfsDir,
rateLimiterChan: make(chan taskRequest, maxRequestsPerSecond),
inputTaskChan: make(chan taskRequest, 1000),
wg: sync.WaitGroup{},
client: &http.Client{
Timeout: time.Second * 5,
},
quit: make(chan struct{}, 1),
}
go d.taskDispatcher()
go d.worker()
return d
}
func (d *Downloader) Stop() {
close(d.quit)
d.wg.Wait()
close(d.inputTaskChan)
close(d.rateLimiterChan)
}
func (d *Downloader) worker() {
for {
select {
case <-d.quit:
return
case request := <-d.rateLimiterChan:
resp, err := d.download(request.cid, request.download)
request.doneChan <- taskResponse{
err: err,
response: resp,
}
}
}
}
func (d *Downloader) taskDispatcher() {
ticker := time.NewTicker(time.Second / maxRequestsPerSecond)
defer ticker.Stop()
for {
select {
case <-d.quit:
return
case <-ticker.C:
request, ok := <-d.inputTaskChan
if !ok {
return
}
d.rateLimiterChan <- request
}
}
}
func hashToCid(hash []byte) (string, error) {
// contract response includes a contenthash, which needs to be decoded to reveal
// an IPFS identifier. Once decoded, download the content from IPFS. This content
// is in EDN format, ie https://ipfs.infura.io/ipfs/QmWVVLwVKCwkVNjYJrRzQWREVvEk917PhbHYAUhA1gECTM
// and it also needs to be decoded in to a nim type
data, codec, err := multicodec.RemoveCodec(hash)
if err != nil {
return "", err
}
codecName, err := multicodec.Name(codec)
if err != nil {
return "", err
}
if codecName != "ipfs-ns" {
return "", errors.New("codecName is not ipfs-ns")
}
thisCID, err := cid.Parse(data)
if err != nil {
return "", err
}
return thisCID.StringOfBase(multibase.Base32)
}
func decodeStringHash(input string) (string, error) {
hash, err := hexutil.Decode("0x" + input)
if err != nil {
return "", err
}
cid, err := hashToCid(hash)
if err != nil {
return "", err
}
return cid, nil
}
// Get checks if an IPFS image exists and returns it from cache
// otherwise downloads it from INFURA's ipfs gateway
func (d *Downloader) Get(hash string, download bool) ([]byte, error) {
cid, err := decodeStringHash(hash)
if err != nil {
return nil, err
}
exists, content, err := d.exists(cid)
if err != nil {
return nil, err
}
if exists {
return content, nil
}
doneChan := make(chan taskResponse, 1)
d.wg.Add(1)
d.inputTaskChan <- taskRequest{
cid: cid,
download: download,
doneChan: doneChan,
}
done := <-doneChan
close(doneChan)
d.wg.Done()
return done.response, done.err
}
func (d *Downloader) exists(cid string) (bool, []byte, error) {
path := filepath.Join(d.ipfsDir, cid)
_, err := os.Stat(path)
if err == nil {
fileContent, err := os.ReadFile(path)
return true, fileContent, err
}
return false, nil, nil
}
func (d *Downloader) download(cid string, download bool) ([]byte, error) {
path := filepath.Join(d.ipfsDir, cid)
req, err := http.NewRequest(http.MethodPost, infuraAPIURL+cid, nil)
if err != nil {
return nil, err
}
resp, err := d.client.Do(req)
if err != nil {
return nil, err
}
defer func() {
if err := resp.Body.Close(); err != nil {
log.Error("failed to close the stickerpack request body", "err", err)
}
}()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
log.Error("could not load data for", "cid", cid, "code", resp.StatusCode)
return nil, errors.New("could not load ipfs data")
}
fileContent, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if download {
// #nosec G306
err = os.WriteFile(path, fileContent, 0700)
if err != nil {
return nil, err
}
}
return fileContent, nil
}

View File

@ -25,10 +25,12 @@ import (
"github.com/status-im/status-go/connection" "github.com/status-im/status-go/connection"
"github.com/status-im/status-go/db" "github.com/status-im/status-go/db"
"github.com/status-im/status-go/discovery" "github.com/status-im/status-go/discovery"
"github.com/status-im/status-go/ipfs"
"github.com/status-im/status-go/multiaccounts" "github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/peers" "github.com/status-im/status-go/peers"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/server"
accountssvc "github.com/status-im/status-go/services/accounts" accountssvc "github.com/status-im/status-go/services/accounts"
appmetricsservice "github.com/status-im/status-go/services/appmetrics" appmetricsservice "github.com/status-im/status-go/services/appmetrics"
"github.com/status-im/status-go/services/browsers" "github.com/status-im/status-go/services/browsers"
@ -78,6 +80,9 @@ type StatusNode struct {
gethNode *node.Node // reference to Geth P2P stack/node gethNode *node.Node // reference to Geth P2P stack/node
rpcClient *rpc.Client // reference to an RPC client rpcClient *rpc.Client // reference to an RPC client
downloader *ipfs.Downloader
httpServer *server.Server
discovery discovery.Discovery discovery discovery.Discovery
register *peers.Register register *peers.Register
peerPool *peers.PeerPool peerPool *peers.PeerPool
@ -145,6 +150,13 @@ func (n *StatusNode) GethNode() *node.Node {
return n.gethNode return n.gethNode
} }
func (n *StatusNode) HTTPServer() *server.Server {
n.mu.RLock()
defer n.mu.RUnlock()
return n.httpServer
}
// Server retrieves the currently running P2P network layer. // Server retrieves the currently running P2P network layer.
func (n *StatusNode) Server() *p2p.Server { func (n *StatusNode) Server() *p2p.Server {
n.mu.RLock() n.mu.RLock()
@ -222,6 +234,19 @@ func (n *StatusNode) startWithDB(config *params.NodeConfig, accs *accounts.Manag
return err return err
} }
n.downloader = ipfs.NewDownloader(config.RootDataDir)
httpServer, err := server.NewServer(n.appDB, n.downloader)
if err != nil {
return err
}
if err := httpServer.Start(); err != nil {
return err
}
n.httpServer = httpServer
if err := n.initServices(config); err != nil { if err := n.initServices(config); err != nil {
return err return err
} }
@ -399,6 +424,15 @@ func (n *StatusNode) stop() error {
n.gethNode = nil n.gethNode = nil
n.config = nil n.config = nil
err := n.httpServer.Stop()
if err != nil {
return err
}
n.httpServer = nil
n.downloader.Stop()
n.downloader = nil
if n.db != nil { if n.db != nil {
err := n.db.Close() err := n.db.Close()

View File

@ -392,7 +392,7 @@ func (b *StatusNode) ensService() *ens.Service {
func (b *StatusNode) stickersService(accountDB *accounts.Database) *stickers.Service { func (b *StatusNode) stickersService(accountDB *accounts.Database) *stickers.Service {
if b.stickersSrvc == nil { if b.stickersSrvc == nil {
b.stickersSrvc = stickers.NewService(accountDB, b.rpcClient, b.gethAccountManager, b.rpcFiltersSrvc, b.config) b.stickersSrvc = stickers.NewService(accountDB, b.rpcClient, b.gethAccountManager, b.rpcFiltersSrvc, b.config, b.downloader, b.httpServer)
} }
return b.stickersSrvc return b.stickersSrvc
} }
@ -566,8 +566,8 @@ func (b *StatusNode) Cleanup() error {
} }
} }
} }
return nil
return nil
} }
type RPCCall struct { type RPCCall struct {

View File

@ -325,6 +325,8 @@ func insertClusterConfigNodes(tx *sql.Tx, c *params.NodeConfig) error {
return nil return nil
} }
// List of inserts to be executed when upgrading a node
// These INSERT queries should not be modified
func nodeConfigUpgradeInserts() []insertFn { func nodeConfigUpgradeInserts() []insertFn {
return []insertFn{ return []insertFn{
insertNodeConfig, insertNodeConfig,
@ -346,6 +348,32 @@ func nodeConfigUpgradeInserts() []insertFn {
} }
} }
func nodeConfigNormalInserts() []insertFn {
// WARNING: if you are modifying one of the node config tables
// you need to edit `nodeConfigUpgradeInserts` to guarantee that
// the selects being used there are not affected.
return []insertFn{
insertNodeConfig,
insertHTTPConfig,
insertIPCConfig,
insertLogConfig,
insertUpstreamConfig,
insertNetworkConfig,
insertClusterConfig,
insertClusterConfigNodes,
insertLightETHConfig,
insertLightETHConfigTrustedNodes,
insertRegisterTopics,
insertRequireTopics,
insertPushNotificationsServerConfig,
insertShhExtConfig,
insertWakuConfig,
insertWakuV2Config,
insertTorrentConfig,
}
}
func execInsertFns(inFn []insertFn, tx *sql.Tx, c *params.NodeConfig) error { func execInsertFns(inFn []insertFn, tx *sql.Tx, c *params.NodeConfig) error {
for _, fn := range inFn { for _, fn := range inFn {
err := fn(tx, c) err := fn(tx, c)
@ -362,10 +390,7 @@ func insertNodeConfigUpgrade(tx *sql.Tx, c *params.NodeConfig) error {
} }
func SaveConfigWithTx(tx *sql.Tx, c *params.NodeConfig) error { func SaveConfigWithTx(tx *sql.Tx, c *params.NodeConfig) error {
insertFNs := append(nodeConfigUpgradeInserts(), insertFNs := nodeConfigNormalInserts()
insertTorrentConfig,
)
return execInsertFns(insertFNs, tx, c) return execInsertFns(insertFNs, tx, c)
} }

View File

@ -313,6 +313,8 @@ type NodeConfig struct {
// NetworkID sets network to use for selecting peers to connect to // NetworkID sets network to use for selecting peers to connect to
NetworkID uint64 `json:"NetworkId" validate:"required"` NetworkID uint64 `json:"NetworkId" validate:"required"`
RootDataDir string `json:"-"`
// DataDir is the file system folder the node should use for any data storage needs. // DataDir is the file system folder the node should use for any data storage needs.
DataDir string `validate:"required"` DataDir string `validate:"required"`

View File

@ -137,6 +137,8 @@ type Message struct {
ImageLocalURL string `json:"imageLocalUrl,omitempty"` ImageLocalURL string `json:"imageLocalUrl,omitempty"`
// AudioLocalURL is the local url of the audio // AudioLocalURL is the local url of the audio
AudioLocalURL string `json:"audioLocalUrl,omitempty"` AudioLocalURL string `json:"audioLocalUrl,omitempty"`
// StickerLocalURL is the local url of the sticker
StickerLocalURL string `json:"stickerLocalUrl,omitempty"`
// CommunityID is the id of the community to advertise // CommunityID is the id of the community to advertise
CommunityID string `json:"communityId,omitempty"` CommunityID string `json:"communityId,omitempty"`
@ -176,12 +178,16 @@ func (m *Message) PrepareServerURLs(port int) {
if m.ContentType == protobuf.ChatMessage_AUDIO { if m.ContentType == protobuf.ChatMessage_AUDIO {
m.AudioLocalURL = fmt.Sprintf("https://localhost:%d/messages/audio?messageId=%s", port, m.ID) m.AudioLocalURL = fmt.Sprintf("https://localhost:%d/messages/audio?messageId=%s", port, m.ID)
} }
if m.ContentType == protobuf.ChatMessage_STICKER {
m.StickerLocalURL = fmt.Sprintf("https://localhost:%d/ipfs?hash=%s", port, m.GetSticker().Hash)
}
} }
func (m *Message) MarshalJSON() ([]byte, error) { func (m *Message) MarshalJSON() ([]byte, error) {
type StickerAlias struct { type StickerAlias struct {
Hash string `json:"hash"` Hash string `json:"hash"`
Pack int32 `json:"pack"` Pack int32 `json:"pack"`
URL string `json:"url"`
} }
item := struct { item := struct {
ID string `json:"id"` ID string `json:"id"`
@ -258,6 +264,7 @@ func (m *Message) MarshalJSON() ([]byte, error) {
item.Sticker = &StickerAlias{ item.Sticker = &StickerAlias{
Pack: sticker.Pack, Pack: sticker.Pack,
Hash: sticker.Hash, Hash: sticker.Hash,
URL: m.StickerLocalURL,
} }
} }

View File

@ -391,11 +391,6 @@ func NewMessenger(
} }
mailservers := mailserversDB.NewDB(database) mailservers := mailserversDB.NewDB(database)
httpServer, err := server.NewServer(database, logger)
if err != nil {
return nil, err
}
messenger = &Messenger{ messenger = &Messenger{
config: &c, config: &c,
@ -434,14 +429,13 @@ func NewMessenger(
quit: make(chan struct{}), quit: make(chan struct{}),
requestedCommunities: make(map[string]*transport.Filter), requestedCommunities: make(map[string]*transport.Filter),
browserDatabase: c.browserDatabase, browserDatabase: c.browserDatabase,
httpServer: httpServer, httpServer: c.httpServer,
shutdownTasks: []func() error{ shutdownTasks: []func() error{
ensVerifier.Stop, ensVerifier.Stop,
pushNotificationClient.Stop, pushNotificationClient.Stop,
communitiesManager.Stop, communitiesManager.Stop,
encryptionProtocol.Stop, encryptionProtocol.Stop,
transp.ResetFilters, transp.ResetFilters,
httpServer.Stop,
transp.Stop, transp.Stop,
func() error { sender.Stop(); return nil }, func() error { sender.Stop(); return nil },
// Currently this often fails, seems like it's safe to ignore them // Currently this often fails, seems like it's safe to ignore them
@ -671,9 +665,11 @@ func (m *Messenger) Start() (*MessengerResponse, error) {
} }
} }
err = m.httpServer.Start() if m.httpServer != nil {
if err != nil { err = m.httpServer.Start()
return nil, err if err != nil {
return nil, err
}
} }
return response, nil return response, nil
@ -4051,18 +4047,21 @@ func (m *Messenger) MessageByChatID(chatID, cursor string, limit int) ([]*common
} }
} }
for idx := range msgs { if m.httpServer != nil {
msgs[idx].PrepareServerURLs(m.httpServer.Port) for idx := range msgs {
msgs[idx].PrepareServerURLs(m.httpServer.Port)
}
} }
return msgs, nextCursor, nil return msgs, nextCursor, nil
} }
func (m *Messenger) prepareMessages(messages map[string]*common.Message) { func (m *Messenger) prepareMessages(messages map[string]*common.Message) {
for idx := range messages { if m.httpServer != nil {
messages[idx].PrepareServerURLs(m.httpServer.Port) for idx := range messages {
messages[idx].PrepareServerURLs(m.httpServer.Port)
}
} }
} }
func (m *Messenger) AllMessageByChatIDWhichMatchTerm(chatID string, searchTerm string, caseSensitive bool) ([]*common.Message, error) { func (m *Messenger) AllMessageByChatIDWhichMatchTerm(chatID string, searchTerm string, caseSensitive bool) ([]*common.Message, error) {

View File

@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/services/browsers" "github.com/status-im/status-go/services/browsers"
"go.uber.org/zap" "go.uber.org/zap"
@ -68,6 +69,7 @@ type config struct {
clusterConfig params.ClusterConfig clusterConfig params.ClusterConfig
browserDatabase *browsers.Database browserDatabase *browsers.Database
torrentConfig *params.TorrentConfig torrentConfig *params.TorrentConfig
httpServer *server.Server
verifyTransactionClient EthClient verifyTransactionClient EthClient
verifyENSURL string verifyENSURL string
@ -275,3 +277,10 @@ func WithTorrentConfig(tc *params.TorrentConfig) Option {
return nil return nil
} }
} }
func WithHTTPServer(s *server.Server) Option {
return func(c *config) error {
c.httpServer = s
return nil
}
}

View File

@ -14,6 +14,8 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/status-im/status-go/ipfs"
"github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/protocol/identity/identicon" "github.com/status-im/status-go/protocol/identity/identicon"
"github.com/status-im/status-go/protocol/images" "github.com/status-im/status-go/protocol/images"
) )
@ -78,6 +80,11 @@ type identiconHandler struct {
logger *zap.Logger logger *zap.Logger
} }
type ipfsHandler struct {
logger *zap.Logger
downloader *ipfs.Downloader
}
func (s *identiconHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *identiconHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
pks, ok := r.URL.Query()["publicKey"] pks, ok := r.URL.Query()["publicKey"]
if !ok || len(pks) == 0 { if !ok || len(pks) == 0 {
@ -160,23 +167,48 @@ func (s *audioHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
} }
type Server struct { func (s *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Port int hashes, ok := r.URL.Query()["hash"]
run bool if !ok || len(hashes) == 0 {
server *http.Server s.logger.Error("no hash")
logger *zap.Logger return
db *sql.DB }
cert *tls.Certificate
_, download := r.URL.Query()["download"]
content, err := s.downloader.Get(hashes[0], download)
if err != nil {
s.logger.Error("could not download hash", zap.Error(err))
return
}
w.Header().Set("Cache-Control", "max-age:290304000, public")
w.Header().Set("Expires", time.Now().AddDate(60, 0, 0).Format(http.TimeFormat))
_, err = w.Write(content)
if err != nil {
s.logger.Error("failed to write ipfs resource", zap.Error(err))
}
} }
func NewServer(db *sql.DB, logger *zap.Logger) (*Server, error) { type Server struct {
Port int
run bool
server *http.Server
logger *zap.Logger
db *sql.DB
cert *tls.Certificate
downloader *ipfs.Downloader
}
func NewServer(db *sql.DB, downloader *ipfs.Downloader) (*Server, error) {
err := generateTLSCert() err := generateTLSCert()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Server{db: db, logger: logger, cert: globalCertificate, Port: 0}, nil return &Server{db: db, logger: logutils.ZapLogger(), cert: globalCertificate, Port: 0, downloader: downloader}, nil
} }
func (s *Server) listenAndServe() { func (s *Server) listenAndServe() {
@ -216,6 +248,8 @@ func (s *Server) Start() error {
handler.Handle("/messages/images", &imageHandler{db: s.db, logger: s.logger}) handler.Handle("/messages/images", &imageHandler{db: s.db, logger: s.logger})
handler.Handle("/messages/audio", &audioHandler{db: s.db, logger: s.logger}) handler.Handle("/messages/audio", &audioHandler{db: s.db, logger: s.logger})
handler.Handle("/messages/identicons", &identiconHandler{logger: s.logger}) handler.Handle("/messages/identicons", &identiconHandler{logger: s.logger})
handler.Handle("/ipfs", &ipfsHandler{logger: s.logger, downloader: s.downloader})
s.server = &http.Server{Handler: handler} s.server = &http.Server{Handler: handler}
go s.listenAndServe() go s.listenAndServe()

View File

@ -11,6 +11,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/services/browsers" "github.com/status-im/status-go/services/browsers"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
@ -104,7 +105,7 @@ func (s *Service) GetPeer(rawURL string) (*enode.Node, error) {
return enode.ParseV4(rawURL) return enode.ParseV4(rawURL)
} }
func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, db *sql.DB, multiAccountDb *multiaccounts.Database, acc *multiaccounts.Account, logger *zap.Logger) error { func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, db *sql.DB, httpServer *server.Server, multiAccountDb *multiaccounts.Database, acc *multiaccounts.Account, logger *zap.Logger) error {
var err error var err error
if !s.config.ShhextConfig.PFSEnabled { if !s.config.ShhextConfig.PFSEnabled {
return nil return nil
@ -143,7 +144,7 @@ func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, db *
s.multiAccountsDB = multiAccountDb s.multiAccountsDB = multiAccountDb
s.account = acc s.account = acc
options, err := buildMessengerOptions(s.config, identity, db, s.multiAccountsDB, acc, envelopesMonitorConfig, s.accountsDB, logger, &MessengerSignalsHandler{}) options, err := buildMessengerOptions(s.config, identity, db, httpServer, s.multiAccountsDB, acc, envelopesMonitorConfig, s.accountsDB, logger, &MessengerSignalsHandler{})
if err != nil { if err != nil {
return err return err
} }
@ -389,6 +390,7 @@ func buildMessengerOptions(
config params.NodeConfig, config params.NodeConfig,
identity *ecdsa.PrivateKey, identity *ecdsa.PrivateKey,
db *sql.DB, db *sql.DB,
httpServer *server.Server,
multiAccounts *multiaccounts.Database, multiAccounts *multiaccounts.Database,
account *multiaccounts.Account, account *multiaccounts.Account,
envelopesMonitorConfig *transport.EnvelopesMonitorConfig, envelopesMonitorConfig *transport.EnvelopesMonitorConfig,
@ -409,6 +411,7 @@ func buildMessengerOptions(
protocol.WithENSVerificationConfig(publishMessengerResponse, config.ShhextConfig.VerifyENSURL, config.ShhextConfig.VerifyENSContractAddress), protocol.WithENSVerificationConfig(publishMessengerResponse, config.ShhextConfig.VerifyENSURL, config.ShhextConfig.VerifyENSContractAddress),
protocol.WithClusterConfig(config.ClusterConfig), protocol.WithClusterConfig(config.ClusterConfig),
protocol.WithTorrentConfig(&config.TorrentConfig), protocol.WithTorrentConfig(&config.TorrentConfig),
protocol.WithHTTPServer(httpServer),
} }
if config.ShhextConfig.DataSyncEnabled { if config.ShhextConfig.DataSyncEnabled {

View File

@ -2,15 +2,9 @@ package stickers
import ( import (
"context" "context"
"errors" "fmt"
"io/ioutil"
"math/big" "math/big"
"net/http"
"time"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multibase"
"github.com/wealdtech/go-multicodec"
"github.com/zenthangplus/goccm" "github.com/zenthangplus/goccm"
"olympos.io/encoding/edn" "olympos.io/encoding/edn"
@ -22,14 +16,14 @@ import (
"github.com/status-im/status-go/contracts" "github.com/status-im/status-go/contracts"
"github.com/status-im/status-go/contracts/stickers" "github.com/status-im/status-go/contracts/stickers"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/ipfs"
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/services/rpcfilters" "github.com/status-im/status-go/services/rpcfilters"
"github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/bigint"
) )
const ipfsGateway = ".ipfs.infura-ipfs.io/"
const maxConcurrentRequests = 3 const maxConcurrentRequests = 3
// ConnectionType constants // ConnectionType constants
@ -47,9 +41,12 @@ type API struct {
accountsManager *account.GethManager accountsManager *account.GethManager
accountsDB *accounts.Database accountsDB *accounts.Database
rpcFiltersSrvc *rpcfilters.Service rpcFiltersSrvc *rpcfilters.Service
config *params.NodeConfig
ctx context.Context keyStoreDir string
client *http.Client downloader *ipfs.Downloader
httpServer *server.Server
ctx context.Context
} }
type Sticker struct { type Sticker struct {
@ -68,7 +65,7 @@ type StickerPack struct {
Thumbnail string `json:"thumbnail"` Thumbnail string `json:"thumbnail"`
Stickers []Sticker `json:"stickers"` Stickers []Sticker `json:"stickers"`
Status stickerStatus `json:"status,omitempty"` Status stickerStatus `json:"status"`
} }
type StickerPackCollection map[uint]StickerPack type StickerPackCollection map[uint]StickerPack
@ -88,20 +85,21 @@ type ednStickerPackInfo struct {
Meta ednStickerPack Meta ednStickerPack
} }
func NewAPI(ctx context.Context, acc *accounts.Database, rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, config *params.NodeConfig) *API { func NewAPI(ctx context.Context, acc *accounts.Database, rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, keyStoreDir string, downloader *ipfs.Downloader, httpServer *server.Server) *API {
return &API{ result := &API{
contractMaker: &contracts.ContractMaker{ contractMaker: &contracts.ContractMaker{
RPCClient: rpcClient, RPCClient: rpcClient,
}, },
accountsManager: accountsManager, accountsManager: accountsManager,
accountsDB: acc, accountsDB: acc,
rpcFiltersSrvc: rpcFiltersSrvc, rpcFiltersSrvc: rpcFiltersSrvc,
config: config, keyStoreDir: keyStoreDir,
downloader: downloader,
ctx: ctx, ctx: ctx,
client: &http.Client{ httpServer: httpServer,
Timeout: time.Second * 5,
},
} }
return result
} }
func (api *API) Market(chainID uint64) ([]StickerPack, error) { func (api *API) Market(chainID uint64) ([]StickerPack, error) {
@ -232,39 +230,6 @@ func (api *API) getPurchasedPackIDs(chainID uint64, account types.Address) ([]*b
return api.getTokenPackIDs(chainID, tokenIDs) return api.getTokenPackIDs(chainID, tokenIDs)
} }
func hashToURL(hash []byte) (string, error) {
// contract response includes a contenthash, which needs to be decoded to reveal
// an IPFS identifier. Once decoded, download the content from IPFS. This content
// is in EDN format, ie https://ipfs.infura.io/ipfs/QmWVVLwVKCwkVNjYJrRzQWREVvEk917PhbHYAUhA1gECTM
// and it also needs to be decoded in to a nim type
data, codec, err := multicodec.RemoveCodec(hash)
if err != nil {
return "", err
}
codecName, err := multicodec.Name(codec)
if err != nil {
return "", err
}
if codecName != "ipfs-ns" {
return "", errors.New("codecName is not ipfs-ns")
}
thisCID, err := cid.Parse(data)
if err != nil {
return "", err
}
str, err := thisCID.StringOfBase(multibase.Base32)
if err != nil {
return "", err
}
return "https://" + str + ipfsGateway, nil
}
func (api *API) fetchStickerPacks(chainID uint64, resultChan chan<- *StickerPack, errChan chan<- error, doneChan chan<- struct{}) { func (api *API) fetchStickerPacks(chainID uint64, resultChan chan<- *StickerPack, errChan chan<- error, doneChan chan<- struct{}) {
defer close(doneChan) defer close(doneChan)
defer close(errChan) defer close(errChan)
@ -339,18 +304,13 @@ func (api *API) fetchPackData(stickerType *stickers.StickerType, packID *big.Int
return nil, err return nil, err
} }
packDetailsURL, err := hashToURL(packData.Contenthash)
if err != nil {
return nil, err
}
stickerPack := &StickerPack{ stickerPack := &StickerPack{
ID: &bigint.BigInt{Int: packID}, ID: &bigint.BigInt{Int: packID},
Owner: packData.Owner, Owner: packData.Owner,
Price: &bigint.BigInt{Int: packData.Price}, Price: &bigint.BigInt{Int: packData.Price},
} }
err = api.downloadIPFSData(stickerPack, packDetailsURL, translateHashes) err = api.downloadPackData(stickerPack, packData.Contenthash, translateHashes)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -358,34 +318,19 @@ func (api *API) fetchPackData(stickerType *stickers.StickerType, packID *big.Int
return stickerPack, nil return stickerPack, nil
} }
func (api *API) downloadIPFSData(stickerPack *StickerPack, packDetailsURL string, translateHashes bool) error { func (api *API) downloadPackData(stickerPack *StickerPack, contentHash []byte, translateHashes bool) error {
// This can be improved by adding a cache using packDetailsURL as key fileContent, err := api.downloader.Get(hexutil.Encode(contentHash)[2:], true)
req, err := http.NewRequest(http.MethodGet, packDetailsURL, nil)
if err != nil { if err != nil {
return err return err
} }
return api.populateStickerPackAttributes(stickerPack, fileContent, translateHashes)
resp, err := api.client.Do(req)
if err != nil {
return err
}
defer func() {
if err := resp.Body.Close(); err != nil {
log.Error("failed to close the stickerpack request body", "err", err)
}
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return populateStickerPackAttributes(stickerPack, body, translateHashes)
} }
func populateStickerPackAttributes(stickerPack *StickerPack, ednSource []byte, translateHashes bool) error { func (api *API) hashToURL(hash string) string {
return fmt.Sprintf("https://localhost:%d/ipfs?hash=%s", api.httpServer.Port, hash)
}
func (api *API) populateStickerPackAttributes(stickerPack *StickerPack, ednSource []byte, translateHashes bool) error {
var stickerpackIPFSInfo ednStickerPackInfo var stickerpackIPFSInfo ednStickerPackInfo
err := edn.Unmarshal(ednSource, &stickerpackIPFSInfo) err := edn.Unmarshal(ednSource, &stickerpackIPFSInfo)
if err != nil { if err != nil {
@ -396,15 +341,8 @@ func populateStickerPackAttributes(stickerPack *StickerPack, ednSource []byte, t
stickerPack.Name = stickerpackIPFSInfo.Meta.Name stickerPack.Name = stickerpackIPFSInfo.Meta.Name
if translateHashes { if translateHashes {
stickerPack.Preview, err = decodeStringHash(stickerpackIPFSInfo.Meta.Preview) stickerPack.Preview = api.hashToURL(stickerpackIPFSInfo.Meta.Preview)
if err != nil { stickerPack.Thumbnail = api.hashToURL(stickerpackIPFSInfo.Meta.Thumbnail)
return err
}
stickerPack.Thumbnail, err = decodeStringHash(stickerpackIPFSInfo.Meta.Thumbnail)
if err != nil {
return err
}
} else { } else {
stickerPack.Preview = stickerpackIPFSInfo.Meta.Preview stickerPack.Preview = stickerpackIPFSInfo.Meta.Preview
stickerPack.Thumbnail = stickerpackIPFSInfo.Meta.Thumbnail stickerPack.Thumbnail = stickerpackIPFSInfo.Meta.Thumbnail
@ -413,15 +351,7 @@ func populateStickerPackAttributes(stickerPack *StickerPack, ednSource []byte, t
for _, s := range stickerpackIPFSInfo.Meta.Stickers { for _, s := range stickerpackIPFSInfo.Meta.Stickers {
url := "" url := ""
if translateHashes { if translateHashes {
hash, err := hexutil.Decode("0x" + s.Hash) url = api.hashToURL(s.Hash)
if err != nil {
return err
}
url, err = hashToURL(hash)
if err != nil {
return err
}
} }
stickerPack.Stickers = append(stickerPack.Stickers, Sticker{ stickerPack.Stickers = append(stickerPack.Stickers, Sticker{
@ -434,20 +364,6 @@ func populateStickerPackAttributes(stickerPack *StickerPack, ednSource []byte, t
return nil return nil
} }
func decodeStringHash(input string) (string, error) {
hash, err := hexutil.Decode("0x" + input)
if err != nil {
return "", err
}
url, err := hashToURL(hash)
if err != nil {
return "", err
}
return url, nil
}
func (api *API) getContractPacks(chainID uint64) ([]StickerPack, error) { func (api *API) getContractPacks(chainID uint64) ([]StickerPack, error) {
stickerPackChan := make(chan *StickerPack) stickerPackChan := make(chan *StickerPack)
errChan := make(chan error) errChan := make(chan error)

View File

@ -68,25 +68,15 @@ func (api *API) Installed() (StickerPackCollection, error) {
for packID, stickerPack := range stickerPacks { for packID, stickerPack := range stickerPacks {
stickerPack.Status = statusInstalled stickerPack.Status = statusInstalled
stickerPack.Preview = api.hashToURL(stickerPack.Preview)
stickerPack.Preview, err = decodeStringHash(stickerPack.Preview) stickerPack.Thumbnail = api.hashToURL(stickerPack.Thumbnail)
if err != nil {
return nil, err
}
stickerPack.Thumbnail, err = decodeStringHash(stickerPack.Thumbnail)
if err != nil {
return nil, err
}
for i, sticker := range stickerPack.Stickers { for i, sticker := range stickerPack.Stickers {
sticker.URL, err = decodeStringHash(sticker.Hash) sticker.URL = api.hashToURL(sticker.Hash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
stickerPack.Stickers[i] = sticker stickerPack.Stickers[i] = sticker
} }
stickerPacks[packID] = stickerPack stickerPacks[packID] = stickerPack
} }

View File

@ -3,6 +3,7 @@ package stickers
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"math/big"
"github.com/status-im/status-go/multiaccounts/settings" "github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/bigint"
@ -61,31 +62,61 @@ func (api *API) Pending() (StickerPackCollection, error) {
for packID, stickerPack := range stickerPacks { for packID, stickerPack := range stickerPacks {
stickerPack.Status = statusPending stickerPack.Status = statusPending
stickerPack.Preview = api.hashToURL(stickerPack.Preview)
stickerPack.Preview, err = decodeStringHash(stickerPack.Preview) stickerPack.Thumbnail = api.hashToURL(stickerPack.Thumbnail)
if err != nil {
return nil, err
}
stickerPack.Thumbnail, err = decodeStringHash(stickerPack.Thumbnail)
if err != nil {
return nil, err
}
for i, sticker := range stickerPack.Stickers { for i, sticker := range stickerPack.Stickers {
sticker.URL, err = decodeStringHash(sticker.Hash) sticker.URL = api.hashToURL(sticker.Hash)
if err != nil {
return nil, err
}
stickerPack.Stickers[i] = sticker stickerPack.Stickers[i] = sticker
} }
stickerPacks[packID] = stickerPack stickerPacks[packID] = stickerPack
} }
return stickerPacks, nil return stickerPacks, nil
} }
func (api *API) ProcessPending(chainID uint64) (pendingChanged StickerPackCollection, err error) {
pendingStickerPacks, err := api.pendingStickerPacks()
if err != nil {
return nil, err
}
accs, err := api.accountsDB.GetAccounts()
if err != nil {
return nil, err
}
purchasedPacks := make(map[uint]struct{})
purchasedPackChan := make(chan *big.Int)
errChan := make(chan error)
doneChan := make(chan struct{}, 1)
go api.getAccountsPurchasedPack(chainID, accs, purchasedPackChan, errChan, doneChan)
for {
select {
case err := <-errChan:
if err != nil {
return nil, err
}
case packID := <-purchasedPackChan:
if packID != nil {
purchasedPacks[uint(packID.Uint64())] = struct{}{}
}
case <-doneChan:
result := make(StickerPackCollection)
for _, stickerPack := range pendingStickerPacks {
packID := uint(stickerPack.ID.Uint64())
if _, exists := purchasedPacks[packID]; !exists {
continue
}
delete(pendingStickerPacks, packID)
stickerPack.Status = statusPurchased
result[packID] = stickerPack
}
err = api.accountsDB.SaveSettingField(settings.StickersPacksPending, pendingStickerPacks)
return result, err
}
}
}
func (api *API) RemovePending(packID *bigint.BigInt) error { func (api *API) RemovePending(packID *bigint.BigInt) error {
pendingPacks, err := api.pendingStickerPacks() pendingPacks, err := api.pendingStickerPacks()
if err != nil { if err != nil {

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"github.com/status-im/status-go/multiaccounts/settings" "github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/services/wallet/bigint"
) )
const maxNumberRecentStickers = 24 const maxNumberRecentStickers = 24
@ -40,17 +41,19 @@ func (api *API) Recent() ([]Sticker, error) {
} }
for i, sticker := range recentStickersList { for i, sticker := range recentStickersList {
sticker.URL, err = decodeStringHash(sticker.Hash) sticker.URL = api.hashToURL(sticker.Hash)
if err != nil {
return nil, err
}
recentStickersList[i] = sticker recentStickersList[i] = sticker
} }
return recentStickersList, nil return recentStickersList, nil
} }
func (api *API) AddRecent(sticker Sticker) error { func (api *API) AddRecent(packID *bigint.BigInt, hash string) error {
sticker := Sticker{
PackID: packID,
Hash: hash,
}
recentStickersList, err := api.recentStickers() recentStickersList, err := api.recentStickers()
if err != nil { if err != nil {
return err return err

View File

@ -6,14 +6,16 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
ethRpc "github.com/ethereum/go-ethereum/rpc" ethRpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/ipfs"
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/services/rpcfilters" "github.com/status-im/status-go/services/rpcfilters"
) )
// NewService initializes service instance. // NewService initializes service instance.
func NewService(acc *accounts.Database, rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, config *params.NodeConfig) *Service { func NewService(acc *accounts.Database, rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, config *params.NodeConfig, downloader *ipfs.Downloader, httpServer *server.Server) *Service {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
return &Service{ return &Service{
@ -21,10 +23,11 @@ func NewService(acc *accounts.Database, rpcClient *rpc.Client, accountsManager *
rpcClient: rpcClient, rpcClient: rpcClient,
accountsManager: accountsManager, accountsManager: accountsManager,
rpcFiltersSrvc: rpcFiltersSrvc, rpcFiltersSrvc: rpcFiltersSrvc,
config: config, keyStoreDir: config.KeyStoreDir,
downloader: downloader,
ctx: ctx, httpServer: httpServer,
cancel: cancel, ctx: ctx,
cancel: cancel,
} }
} }
@ -34,10 +37,11 @@ type Service struct {
rpcClient *rpc.Client rpcClient *rpc.Client
accountsManager *account.GethManager accountsManager *account.GethManager
rpcFiltersSrvc *rpcfilters.Service rpcFiltersSrvc *rpcfilters.Service
config *params.NodeConfig downloader *ipfs.Downloader
keyStoreDir string
ctx context.Context httpServer *server.Server
cancel context.CancelFunc ctx context.Context
cancel context.CancelFunc
} }
// Start a service. // Start a service.
@ -57,7 +61,7 @@ func (s *Service) APIs() []ethRpc.API {
{ {
Namespace: "stickers", Namespace: "stickers",
Version: "0.1.0", Version: "0.1.0",
Service: NewAPI(s.ctx, s.accountsDB, s.rpcClient, s.accountsManager, s.rpcFiltersSrvc, s.config), Service: NewAPI(s.ctx, s.accountsDB, s.rpcClient, s.accountsManager, s.rpcFiltersSrvc, s.keyStoreDir, s.downloader, s.httpServer),
}, },
} }
} }

View File

@ -5,6 +5,8 @@ import (
"math/big" "math/big"
"strings" "strings"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
@ -19,7 +21,7 @@ import (
func (api *API) getSigner(chainID uint64, from types.Address, password string) bind.SignerFn { func (api *API) getSigner(chainID uint64, from types.Address, password string) bind.SignerFn {
return func(addr common.Address, tx *ethTypes.Transaction) (*ethTypes.Transaction, error) { return func(addr common.Address, tx *ethTypes.Transaction) (*ethTypes.Transaction, error) {
selectedAccount, err := api.accountsManager.VerifyAccountPassword(api.config.KeyStoreDir, from.Hex(), password) selectedAccount, err := api.accountsManager.VerifyAccountPassword(api.keyStoreDir, from.Hex(), password)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -84,63 +86,99 @@ func (api *API) Buy(ctx context.Context, chainID uint64, txArgs transactions.Sen
return tx.Hash().String(), nil return tx.Hash().String(), nil
} }
func (api *API) BuyEstimate(ctx context.Context, chainID uint64, from types.Address, packID *bigint.BigInt) (uint64, error) { func (api *API) BuyPrepareTxCallMsg(chainID uint64, from types.Address, packID *bigint.BigInt) (ethereum.CallMsg, error) {
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false} callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
stickerType, err := api.contractMaker.NewStickerType(chainID) stickerType, err := api.contractMaker.NewStickerType(chainID)
if err != nil { if err != nil {
return 0, err return ethereum.CallMsg{}, err
} }
packInfo, err := stickerType.GetPackData(callOpts, packID.Int) packInfo, err := stickerType.GetPackData(callOpts, packID.Int)
if err != nil { if err != nil {
return 0, err return ethereum.CallMsg{}, err
} }
stickerMarketABI, err := abi.JSON(strings.NewReader(stickers.StickerMarketABI)) stickerMarketABI, err := abi.JSON(strings.NewReader(stickers.StickerMarketABI))
if err != nil { if err != nil {
return 0, err return ethereum.CallMsg{}, err
} }
extraData, err := stickerMarketABI.Pack("buyToken", packID.Int, from, packInfo.Price) extraData, err := stickerMarketABI.Pack("buyToken", packID.Int, from, packInfo.Price)
if err != nil { if err != nil {
return 0, err return ethereum.CallMsg{}, err
} }
sntABI, err := abi.JSON(strings.NewReader(snt.SNTABI)) sntABI, err := abi.JSON(strings.NewReader(snt.SNTABI))
if err != nil { if err != nil {
return 0, err return ethereum.CallMsg{}, err
} }
stickerMarketAddress, err := stickers.StickerMarketContractAddress(chainID) stickerMarketAddress, err := stickers.StickerMarketContractAddress(chainID)
if err != nil { if err != nil {
return 0, err return ethereum.CallMsg{}, err
} }
data, err := sntABI.Pack("approveAndCall", stickerMarketAddress, packInfo.Price, extraData) data, err := sntABI.Pack("approveAndCall", stickerMarketAddress, packInfo.Price, extraData)
if err != nil { if err != nil {
return 0, err return ethereum.CallMsg{}, err
} }
sntAddress, err := snt.ContractAddress(chainID)
if err != nil {
return ethereum.CallMsg{}, err
}
return ethereum.CallMsg{
From: common.Address(from),
To: &sntAddress,
Value: big.NewInt(0),
Data: data,
}, nil
}
func (api *API) BuyPrepareTx(ctx context.Context, chainID uint64, from types.Address, packID *bigint.BigInt) (interface{}, error) {
callMsg, err := api.BuyPrepareTxCallMsg(chainID, from, packID)
if err != nil {
return nil, err
}
return toCallArg(callMsg), nil
}
func (api *API) BuyEstimate(ctx context.Context, chainID uint64, from types.Address, packID *bigint.BigInt) (uint64, error) {
callMsg, err := api.BuyPrepareTxCallMsg(chainID, from, packID)
if err != nil {
return 0, err
}
ethClient, err := api.contractMaker.RPCClient.EthClient(chainID) ethClient, err := api.contractMaker.RPCClient.EthClient(chainID)
if err != nil { if err != nil {
return 0, err return 0, err
} }
sntAddress, err := snt.ContractAddress(chainID) return ethClient.EstimateGas(ctx, callMsg)
if err != nil {
return 0, err
}
return ethClient.EstimateGas(ctx, ethereum.CallMsg{
From: common.Address(from),
To: &sntAddress,
Value: big.NewInt(0),
Data: data,
})
} }
func (api *API) StickerMarketAddress(ctx context.Context, chainID uint64) (common.Address, error) { func (api *API) StickerMarketAddress(ctx context.Context, chainID uint64) (common.Address, error) {
return stickers.StickerMarketContractAddress(chainID) return stickers.StickerMarketContractAddress(chainID)
} }
func toCallArg(msg ethereum.CallMsg) interface{} {
arg := map[string]interface{}{
"from": msg.From,
"to": msg.To,
}
if len(msg.Data) > 0 {
arg["data"] = hexutil.Bytes(msg.Data)
}
if msg.Value != nil {
arg["value"] = (*hexutil.Big)(msg.Value)
}
if msg.Gas != 0 {
arg["gas"] = hexutil.Uint64(msg.Gas)
}
if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
}
return arg
}

View File

@ -136,7 +136,7 @@ func TestInitProtocol(t *testing.T) {
acc := &multiaccounts.Account{KeyUID: "0xdeadbeef"} acc := &multiaccounts.Account{KeyUID: "0xdeadbeef"}
err = service.InitProtocol("Test", privateKey, sqlDB, multiAccounts, acc, zap.NewNop()) err = service.InitProtocol("Test", privateKey, sqlDB, nil, multiAccounts, acc, zap.NewNop())
require.NoError(t, err) require.NoError(t, err)
} }
@ -201,7 +201,7 @@ func (s *ShhExtSuite) createAndAddNode() {
acc := &multiaccounts.Account{KeyUID: "0xdeadbeef"} acc := &multiaccounts.Account{KeyUID: "0xdeadbeef"}
err = service.InitProtocol("Test", privateKey, sqlDB, multiAccounts, acc, zap.NewNop()) err = service.InitProtocol("Test", privateKey, sqlDB, nil, multiAccounts, acc, zap.NewNop())
s.NoError(err) s.NoError(err)
stack.RegisterLifecycle(service) stack.RegisterLifecycle(service)

View File

@ -12,7 +12,7 @@ import (
func fetchCryptoComparePrices(symbols []string, currency string) (map[string]float64, error) { func fetchCryptoComparePrices(symbols []string, currency string) (map[string]float64, error) {
httpClient := http.Client{Timeout: time.Minute} httpClient := http.Client{Timeout: time.Minute}
url := fmt.Sprintf("https://min-api.cryptocompare.com/data/pricemulti?fsyms=%s&tsyms=%s", strings.Join(symbols, ","), currency) url := fmt.Sprintf("https://min-api.cryptocompare.com/data/pricemulti?fsyms=%s&tsyms=%s&extraParams=Status.im", strings.Join(symbols, ","), currency)
resp, err := httpClient.Get(url) resp, err := httpClient.Get(url)
if err != nil { if err != nil {
return nil, err return nil, err