feat: implement collectibles ownership update timestamp db

This commit is contained in:
Dario Gabriel Lipicar 2023-09-12 15:35:31 -03:00 committed by dlipicar
parent daf3fc79bf
commit b4d5c22050
5 changed files with 153 additions and 29 deletions

View File

@ -137,6 +137,7 @@ func (c *loadOwnedCollectiblesCommand) Run(parent context.Context) (err error) {
pageNr := 0
cursor := thirdparty.FetchFromStartCursor
start := time.Now()
c.triggerEvent(EventCollectiblesOwnershipUpdateStarted, c.chainID, c.account, "")
// Fetch collectibles in chunks
@ -162,7 +163,7 @@ func (c *loadOwnedCollectiblesCommand) Run(parent context.Context) (err error) {
cursor = partialOwnership.NextCursor
if cursor == thirdparty.FetchFromStartCursor {
err = c.ownershipDB.Update(c.chainID, c.account, c.partialOwnership)
err = c.ownershipDB.Update(c.chainID, c.account, c.partialOwnership, start.Unix())
if err != nil {
log.Error("failed updating ownershipDB in loadOwnedCollectiblesCommand", "chain", c.chainID, "account", c.account, "error", err)
c.err = err

View File

@ -11,10 +11,13 @@ import (
"github.com/status-im/status-go/services/wallet/bigint"
w_common "github.com/status-im/status-go/services/wallet/common"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/sqlite"
)
const InvalidTimestamp = int64(-1)
type OwnershipDB struct {
db *sql.DB
}
@ -28,6 +31,9 @@ func NewOwnershipDB(sqlDb *sql.DB) *OwnershipDB {
const ownershipColumns = "chain_id, contract_address, token_id, owner_address"
const selectOwnershipColumns = "chain_id, contract_address, token_id"
const ownershipTimestampColumns = "owner_address, chain_id, timestamp"
const selectOwnershipTimestampColumns = "timestamp"
func removeAddressOwnership(creator sqlite.StatementCreator, chainID w_common.ChainID, ownerAddress common.Address) error {
deleteOwnership, err := creator.Prepare("DELETE FROM collectibles_ownership_cache WHERE chain_id = ? AND owner_address = ?")
if err != nil {
@ -59,7 +65,19 @@ func insertAddressOwnership(creator sqlite.StatementCreator, ownerAddress common
return nil
}
func (o *OwnershipDB) Update(chainID w_common.ChainID, ownerAddress common.Address, collectibles []thirdparty.CollectibleUniqueID) (err error) {
func updateAddressOwnershipTimestamp(creator sqlite.StatementCreator, ownerAddress common.Address, chainID w_common.ChainID, timestamp int64) error {
updateTimestamp, err := creator.Prepare(fmt.Sprintf(`INSERT OR REPLACE INTO collectibles_ownership_update_timestamps (%s)
VALUES (?, ?, ?)`, ownershipTimestampColumns))
if err != nil {
return err
}
_, err = updateTimestamp.Exec(ownerAddress, chainID, timestamp)
return err
}
func (o *OwnershipDB) Update(chainID w_common.ChainID, ownerAddress common.Address, collectibles []thirdparty.CollectibleUniqueID, timestamp int64) (err error) {
var (
tx *sql.Tx
)
@ -87,6 +105,9 @@ func (o *OwnershipDB) Update(chainID w_common.ChainID, ownerAddress common.Addre
return err
}
// Update timestamp
err = updateAddressOwnershipTimestamp(tx, ownerAddress, chainID, timestamp)
return
}
@ -163,3 +184,29 @@ func (o *OwnershipDB) GetOwnedCollectible(chainID w_common.ChainID, ownerAddress
return &ids[0], nil
}
func (o *OwnershipDB) GetOwnershipUpdateTimestamp(owner common.Address, chainID walletCommon.ChainID) (int64, error) {
query := fmt.Sprintf(`SELECT %s
FROM collectibles_ownership_update_timestamps
WHERE owner_address = ? AND chain_id = ?`, selectOwnershipTimestampColumns)
stmt, err := o.db.Prepare(query)
if err != nil {
return InvalidTimestamp, err
}
defer stmt.Close()
row := stmt.QueryRow(owner, chainID)
var timestamp int64
err = row.Scan(&timestamp)
if err == sql.ErrNoRows {
return InvalidTimestamp, nil
} else if err != nil {
return InvalidTimestamp, err
}
return timestamp, nil
}

View File

@ -46,14 +46,17 @@ func TestUpdateOwnership(t *testing.T) {
ownerAddress1 := common.HexToAddress("0x1234")
chainID0 := w_common.ChainID(0)
ownedListChain0 := generateTestCollectibles(chainID0, 10)
timestampChain0 := int64(1234567890)
chainID1 := w_common.ChainID(1)
ownedListChain1 := generateTestCollectibles(chainID1, 15)
timestampChain1 := int64(1234567891)
ownedList1 := append(ownedListChain0, ownedListChain1...)
ownerAddress2 := common.HexToAddress("0x5678")
chainID2 := w_common.ChainID(2)
ownedListChain2 := generateTestCollectibles(chainID2, 20)
timestampChain2 := int64(1234567892)
ownedList2 := ownedListChain2
@ -63,15 +66,55 @@ func TestUpdateOwnership(t *testing.T) {
var err error
err = oDB.Update(chainID0, ownerAddress1, ownedListChain0)
var loadedTimestamp int64
var loadedList []thirdparty.CollectibleUniqueID
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID0)
require.NoError(t, err)
err = oDB.Update(chainID1, ownerAddress1, ownedListChain1)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID1)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress2, chainID2)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
err = oDB.Update(chainID0, ownerAddress1, ownedListChain0, timestampChain0)
require.NoError(t, err)
err = oDB.Update(chainID2, ownerAddress2, ownedListChain2)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID0)
require.NoError(t, err)
require.Equal(t, timestampChain0, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID1)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress2, chainID2)
require.NoError(t, err)
require.Equal(t, InvalidTimestamp, loadedTimestamp)
err = oDB.Update(chainID1, ownerAddress1, ownedListChain1, timestampChain1)
require.NoError(t, err)
loadedList, err := oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0}, []common.Address{ownerAddress1}, 0, len(fullList))
err = oDB.Update(chainID2, ownerAddress2, ownedListChain2, timestampChain2)
require.NoError(t, err)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID0)
require.NoError(t, err)
require.Equal(t, timestampChain0, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID1)
require.NoError(t, err)
require.Equal(t, timestampChain1, loadedTimestamp)
loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress2, chainID2)
require.NoError(t, err)
require.Equal(t, timestampChain2, loadedTimestamp)
loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0}, []common.Address{ownerAddress1}, 0, len(fullList))
require.NoError(t, err)
require.Equal(t, ownedListChain0, loadedList)
@ -109,9 +152,11 @@ func TestLargeTokenID(t *testing.T) {
},
}
timestamp := int64(1234567890)
var err error
err = oDB.Update(chainID, ownerAddress, ownedListChain)
err = oDB.Update(chainID, ownerAddress, ownedListChain, timestamp)
require.NoError(t, err)
loadedList, err := oDB.GetOwnedCollectibles([]w_common.ChainID{chainID}, []common.Address{ownerAddress}, 0, len(ownedListChain))

View File

@ -3,6 +3,7 @@
// 1691753758_initial.up.sql (5.738kB)
// 1692701329_add_collectibles_and_collections_data_cache.up.sql (1.808kB)
// 1692701339_add_scope_to_pending.up.sql (576B)
// 1694540071_add_collectibles_ownership_update_timestamp.up.sql (349B)
// doc.go (74B)
package migrations
@ -13,7 +14,6 @@ import (
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -23,7 +23,7 @@ import (
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
var buf bytes.Buffer
@ -31,7 +31,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
if clErr != nil {
return nil, err
@ -87,7 +87,7 @@ func _1691753758_initialUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1691753758_initial.up.sql", size: 5738, mode: os.FileMode(0644), modTime: time.Unix(1692726720, 0)}
info := bindataFileInfo{name: "1691753758_initial.up.sql", size: 5738, mode: os.FileMode(0644), modTime: time.Unix(1694431782, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6b, 0x25, 0x31, 0xc8, 0x27, 0x3, 0x6b, 0x9f, 0x15, 0x42, 0x2f, 0x85, 0xfb, 0xe3, 0x6, 0xea, 0xf7, 0x97, 0x12, 0x56, 0x3c, 0x9a, 0x5b, 0x1a, 0xca, 0xb1, 0x23, 0xfa, 0xcd, 0x57, 0x25, 0x5c}}
return a, nil
}
@ -107,7 +107,7 @@ func _1692701329_add_collectibles_and_collections_data_cacheUpSql() (*asset, err
return nil, err
}
info := bindataFileInfo{name: "1692701329_add_collectibles_and_collections_data_cache.up.sql", size: 1808, mode: os.FileMode(0644), modTime: time.Unix(1692726720, 0)}
info := bindataFileInfo{name: "1692701329_add_collectibles_and_collections_data_cache.up.sql", size: 1808, mode: os.FileMode(0644), modTime: time.Unix(1694431782, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1, 0x51, 0xf4, 0x2b, 0x92, 0xde, 0x59, 0x65, 0xd8, 0x9b, 0x57, 0xe0, 0xfd, 0x7b, 0x12, 0xb, 0x29, 0x6e, 0x9d, 0xb5, 0x90, 0xe, 0xfa, 0x12, 0x97, 0xd, 0x61, 0x60, 0x7f, 0x32, 0x1d, 0xc3}}
return a, nil
}
@ -127,11 +127,31 @@ func _1692701339_add_scope_to_pendingUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1692701339_add_scope_to_pending.up.sql", size: 576, mode: os.FileMode(0644), modTime: time.Unix(1692726720, 0)}
info := bindataFileInfo{name: "1692701339_add_scope_to_pending.up.sql", size: 576, mode: os.FileMode(0644), modTime: time.Unix(1694431782, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x36, 0x8a, 0x5e, 0xe2, 0x63, 0x15, 0x37, 0xba, 0x55, 0x18, 0xf3, 0xcc, 0xe0, 0x5, 0x84, 0xe1, 0x5b, 0xe8, 0x1, 0x32, 0x6b, 0x9f, 0x7d, 0x9f, 0xd9, 0x23, 0x6c, 0xa9, 0xb5, 0xdc, 0xf4, 0x93}}
return a, nil
}
var __1694540071_add_collectibles_ownership_update_timestampUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\xcf\x41\x4b\x80\x30\x18\xc6\xf1\xfb\x3e\xc5\x7b\x4c\xf0\x1b\x78\x9a\xba\x6c\x20\xaf\xa4\x5b\x78\x1b\xcb\xbd\xe1\x40\xa7\xb8\x45\xf8\xed\x03\x09\xa1\xa0\x43\xdd\xff\x3c\x3c\xbf\xaa\x17\x5c\x09\x50\xbc\x6c\x05\xc8\x47\xc0\x4e\x81\x18\xe5\xa0\x06\x98\xb6\x65\xa1\x29\xf9\xd7\x85\xa2\xd9\x3e\x02\x1d\x71\xf6\xbb\x79\xdf\x9d\x4d\x64\x92\x5f\x29\x26\xbb\xee\x11\x1e\x18\x00\xc0\x55\x18\xeb\xdc\x41\x31\xc2\x0b\xef\xab\x27\xde\x5f\x7b\xa8\xdb\x36\xbf\x9a\x69\xb6\x3e\x18\xef\x40\xe3\x20\x1b\x14\x35\x94\xb2\x91\xa8\x7e\x64\xf7\xf6\xaf\x1d\xcb\x0a\xc6\xbe\xbe\x6b\x94\xcf\x5a\x80\xc4\x5a\x8c\xff\x24\x18\xef\x28\x24\xff\x76\x1a\x0a\xe9\x38\xa1\xc3\x3f\xe8\xbf\xc1\xf3\xdb\x98\x15\xec\x33\x00\x00\xff\xff\x02\x64\xbc\xba\x5d\x01\x00\x00")
func _1694540071_add_collectibles_ownership_update_timestampUpSqlBytes() ([]byte, error) {
return bindataRead(
__1694540071_add_collectibles_ownership_update_timestampUpSql,
"1694540071_add_collectibles_ownership_update_timestamp.up.sql",
)
}
func _1694540071_add_collectibles_ownership_update_timestampUpSql() (*asset, error) {
bytes, err := _1694540071_add_collectibles_ownership_update_timestampUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1694540071_add_collectibles_ownership_update_timestamp.up.sql", size: 349, mode: os.FileMode(0644), modTime: time.Unix(1694540373, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7f, 0x45, 0xc7, 0xce, 0x79, 0x63, 0xbc, 0x6f, 0x83, 0x5f, 0xe2, 0x3, 0x56, 0xcc, 0x5, 0x2f, 0x85, 0xda, 0x7e, 0xea, 0xf5, 0xd2, 0xac, 0x19, 0xd4, 0xd8, 0x5e, 0xdd, 0xed, 0xe2, 0xa9, 0x97}}
return a, nil
}
var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xc9\xb1\x0d\xc4\x20\x0c\x05\xd0\x9e\x29\xfe\x02\xd8\xfd\x6d\xe3\x4b\xac\x2f\x44\x82\x09\x78\x7f\xa5\x49\xfd\xa6\x1d\xdd\xe8\xd8\xcf\x55\x8a\x2a\xe3\x47\x1f\xbe\x2c\x1d\x8c\xfa\x6f\xe3\xb4\x34\xd4\xd9\x89\xbb\x71\x59\xb6\x18\x1b\x35\x20\xa2\x9f\x0a\x03\xa2\xe5\x0d\x00\x00\xff\xff\x60\xcd\x06\xbe\x4a\x00\x00\x00")
func docGoBytes() ([]byte, error) {
@ -147,7 +167,7 @@ func docGo() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "doc.go", size: 74, mode: os.FileMode(0644), modTime: time.Unix(1692726720, 0)}
info := bindataFileInfo{name: "doc.go", size: 74, mode: os.FileMode(0644), modTime: time.Unix(1694431782, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xde, 0x7c, 0x28, 0xcd, 0x47, 0xf2, 0xfa, 0x7c, 0x51, 0x2d, 0xd8, 0x38, 0xb, 0xb0, 0x34, 0x9d, 0x4c, 0x62, 0xa, 0x9e, 0x28, 0xc3, 0x31, 0x23, 0xd9, 0xbb, 0x89, 0x9f, 0xa0, 0x89, 0x1f, 0xe8}}
return a, nil
}
@ -244,23 +264,26 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"1691753758_initial.up.sql": _1691753758_initialUpSql,
"1692701329_add_collectibles_and_collections_data_cache.up.sql": _1692701329_add_collectibles_and_collections_data_cacheUpSql,
"1692701339_add_scope_to_pending.up.sql": _1692701339_add_scope_to_pendingUpSql,
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": _1694540071_add_collectibles_ownership_update_timestampUpSql,
"doc.go": docGo,
}
// AssetDebug is true if the assets were built with the debug flag enabled.
const AssetDebug = false
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
//
// data/
// foo.txt
// img/
// a.png
// b.png
//
// then AssetDir("data") would return []string{"foo.txt", "img"},
// AssetDir("data/img") would return []string{"a.png", "b.png"},
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
@ -293,10 +316,11 @@ type bintree struct {
}
var _bintree = &bintree{nil, map[string]*bintree{
"1691753758_initial.up.sql": &bintree{_1691753758_initialUpSql, map[string]*bintree{}},
"1692701329_add_collectibles_and_collections_data_cache.up.sql": &bintree{_1692701329_add_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1692701339_add_scope_to_pending.up.sql": &bintree{_1692701339_add_scope_to_pendingUpSql, map[string]*bintree{}},
"doc.go": &bintree{docGo, map[string]*bintree{}},
"1691753758_initial.up.sql": {_1691753758_initialUpSql, map[string]*bintree{}},
"1692701329_add_collectibles_and_collections_data_cache.up.sql": {_1692701329_add_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1692701339_add_scope_to_pending.up.sql": {_1692701339_add_scope_to_pendingUpSql, map[string]*bintree{}},
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": {_1694540071_add_collectibles_ownership_update_timestampUpSql, map[string]*bintree{}},
"doc.go": {docGo, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory.
@ -313,7 +337,7 @@ func RestoreAsset(dir, name string) error {
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
err = os.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS collectibles_ownership_update_timestamps (
owner_address VARCHAR NOT NULL,
chain_id UNSIGNED BIGINT NOT NULL,
timestamp UNSIGNED BIGINT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS collectibles_ownership_update_timestamps_identify_entry ON collectibles_ownership_update_timestamps (owner_address, chain_id);