2020-11-18 09:16:51 +00:00
package communities
import (
2021-01-11 10:32:51 +00:00
"context"
2020-11-18 09:16:51 +00:00
"crypto/ecdsa"
"database/sql"
2021-01-11 10:32:51 +00:00
"errors"
2022-03-21 14:18:36 +00:00
"fmt"
2023-06-21 11:20:43 +00:00
"math/big"
2023-06-06 18:33:09 +00:00
"strconv"
"strings"
2023-06-17 08:19:05 +00:00
"time"
2020-11-18 09:16:51 +00:00
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto"
2022-03-08 15:25:00 +00:00
"github.com/status-im/status-go/eth-node/types"
2021-01-11 10:32:51 +00:00
"github.com/status-im/status-go/protocol/common"
2020-11-18 09:16:51 +00:00
"github.com/status-im/status-go/protocol/protobuf"
2023-06-21 11:20:43 +00:00
"github.com/status-im/status-go/services/wallet/bigint"
2020-11-18 09:16:51 +00:00
)
type Persistence struct {
db * sql . DB
logger * zap . Logger
}
2021-11-11 16:37:04 +00:00
var ErrOldRequestToJoin = errors . New ( "old request to join" )
2022-08-22 10:10:31 +00:00
var ErrOldRequestToLeave = errors . New ( "old request to leave" )
2021-11-11 16:37:04 +00:00
2022-03-21 14:18:36 +00:00
const OR = " OR "
2023-07-18 15:06:12 +00:00
const communitiesBaseQuery = `
2023-06-28 10:53:46 +00:00
SELECT c . id , c . private_key , c . description , c . joined , c . spectated , c . verified , c . muted , c . muted_till , r . clock , ae . raw_events , ae . raw_description
2023-07-18 15:06:12 +00:00
FROM communities_communities c
LEFT JOIN communities_requests_to_join r ON c . id = r . community_id AND r . public_key = ?
LEFT JOIN communities_events ae ON c . id = ae . id `
2021-01-11 10:32:51 +00:00
2020-11-18 09:16:51 +00:00
func ( p * Persistence ) SaveCommunity ( community * Community ) error {
id := community . ID ( )
privateKey := community . PrivateKey ( )
description , err := community . ToBytes ( )
if err != nil {
return err
}
2023-07-18 15:06:12 +00:00
_ , err = p . db . Exec ( `
2023-07-19 12:14:42 +00:00
INSERT INTO communities_communities ( id , private_key , description , joined , spectated , verified , muted , muted_till ) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? ) ` ,
id , crypto . FromECDSA ( privateKey ) , description , community . config . Joined , community . config . Spectated , community . config . Verified , community . config . Muted , community . config . MuteTill )
2023-07-18 15:06:12 +00:00
return err
}
func ( p * Persistence ) DeleteCommunityEvents ( id types . HexBytes ) error {
_ , err := p . db . Exec ( ` DELETE FROM communities_events WHERE id = ?; ` , id )
return err
}
func ( p * Persistence ) SaveCommunityEvents ( community * Community ) error {
id := community . ID ( )
if community . config . EventsData == nil {
return nil
}
rawEvents , err := communityEventsToJSONEncodedBytes ( community . config . EventsData . Events )
if err != nil {
return err
}
_ , err = p . db . Exec ( `
INSERT INTO communities_events ( id , raw_events , raw_description ) VALUES ( ? , ? , ? ) ; ` ,
id , rawEvents , community . config . EventsData . EventsBaseCommunityDescription )
2020-11-18 09:16:51 +00:00
return err
}
2022-09-29 11:50:23 +00:00
func ( p * Persistence ) DeleteCommunity ( id types . HexBytes ) error {
2023-07-18 15:06:12 +00:00
_ , err := p . db . Exec ( ` DELETE FROM communities_communities WHERE id = ? ;
DELETE FROM communities_events WHERE id = ? ; ` , id , id )
2022-09-29 11:50:23 +00:00
return err
}
2022-06-01 07:55:48 +00:00
func ( p * Persistence ) ShouldHandleSyncCommunitySettings ( settings * protobuf . SyncCommunitySettings ) ( bool , error ) {
qr := p . db . QueryRow ( ` SELECT * FROM communities_settings WHERE community_id = ? AND clock > ? ` , settings . CommunityId , settings . Clock )
_ , err := p . scanRowToStruct ( qr . Scan )
switch err {
case sql . ErrNoRows :
// Query does not match, therefore clock value is not older than the new clock value or id was not found
return true , nil
case nil :
// Error is nil, therefore query matched and clock is older than the new clock
return false , nil
default :
// Error is not nil and is not sql.ErrNoRows, therefore pass out the error
return false , err
}
}
2021-08-06 15:40:23 +00:00
func ( p * Persistence ) ShouldHandleSyncCommunity ( community * protobuf . SyncCommunity ) ( bool , error ) {
// TODO see if there is a way to make this more elegant
// Keep the "*".
// When the test for this function fails because the table has changed we should update sync functionality
qr := p . db . QueryRow ( ` SELECT * FROM communities_communities WHERE id = ? AND synced_at > ? ` , community . Id , community . Clock )
_ , err := p . scanRowToStruct ( qr . Scan )
switch err {
case sql . ErrNoRows :
// Query does not match, therefore synced_at value is not older than the new clock value or id was not found
return true , nil
case nil :
// Error is nil, therefore query matched and synced_at is older than the new clock
return false , nil
default :
// Error is not nil and is not sql.ErrNoRows, therefore pass out the error
return false , err
}
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) queryCommunities ( memberIdentity * ecdsa . PublicKey , query string ) ( response [ ] * Community , err error ) {
2020-11-18 09:16:51 +00:00
2021-01-11 10:32:51 +00:00
rows , err := p . db . Query ( query , common . PubkeyToHex ( memberIdentity ) )
2020-11-18 09:16:51 +00:00
if err != nil {
return nil , err
}
2020-12-22 11:00:13 +00:00
defer func ( ) {
if err != nil {
// Don't shadow original error
_ = rows . Close ( )
return
}
err = rows . Close ( )
} ( )
2020-11-18 09:16:51 +00:00
for rows . Next ( ) {
var publicKeyBytes , privateKeyBytes , descriptionBytes [ ] byte
2022-09-20 19:57:39 +00:00
var joined , spectated , verified , muted bool
2023-06-17 08:19:05 +00:00
var muteTill sql . NullTime
2021-01-11 10:32:51 +00:00
var requestedToJoinAt sql . NullInt64
2023-07-18 15:06:12 +00:00
// Community events specific fields
var eventsBytes , eventsDescriptionBytes [ ] byte
2023-06-28 10:53:46 +00:00
err := rows . Scan ( & publicKeyBytes , & privateKeyBytes , & descriptionBytes , & joined , & spectated , & verified , & muted , & muteTill , & requestedToJoinAt , & eventsBytes , & eventsDescriptionBytes )
2020-11-18 09:16:51 +00:00
if err != nil {
return nil , err
}
2023-06-28 10:53:46 +00:00
org , err := unmarshalCommunityFromDB ( memberIdentity , publicKeyBytes , privateKeyBytes , descriptionBytes , joined , spectated , verified , muted , muteTill . Time , uint64 ( requestedToJoinAt . Int64 ) , eventsBytes , eventsDescriptionBytes , p . logger )
2020-11-18 09:16:51 +00:00
if err != nil {
return nil , err
}
response = append ( response , org )
}
return response , nil
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) AllCommunities ( memberIdentity * ecdsa . PublicKey ) ( [ ] * Community , error ) {
return p . queryCommunities ( memberIdentity , communitiesBaseQuery )
2020-11-18 09:16:51 +00:00
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) JoinedCommunities ( memberIdentity * ecdsa . PublicKey ) ( [ ] * Community , error ) {
query := communitiesBaseQuery + ` WHERE c.joined `
return p . queryCommunities ( memberIdentity , query )
2020-11-18 09:16:51 +00:00
}
2022-09-20 19:57:39 +00:00
func ( p * Persistence ) SpectatedCommunities ( memberIdentity * ecdsa . PublicKey ) ( [ ] * Community , error ) {
query := communitiesBaseQuery + ` WHERE c.spectated `
return p . queryCommunities ( memberIdentity , query )
}
2022-04-11 16:14:08 +00:00
func ( p * Persistence ) rowsToCommunities ( memberIdentity * ecdsa . PublicKey , rows * sql . Rows ) ( comms [ ] * Community , err error ) {
2021-08-06 15:40:23 +00:00
defer func ( ) {
if err != nil {
// Don't shadow original error
_ = rows . Close ( )
return
}
err = rows . Close ( )
} ( )
for rows . Next ( ) {
var comm * Community
// Community specific fields
var publicKeyBytes , privateKeyBytes , descriptionBytes [ ] byte
2022-09-20 19:57:39 +00:00
var joined , spectated , verified , muted bool
2023-06-28 10:53:46 +00:00
var muteTill sql . NullTime
2021-08-06 15:40:23 +00:00
// Request to join specific fields
var rtjID , rtjCommunityID [ ] byte
var rtjPublicKey , rtjENSName , rtjChatID sql . NullString
var rtjClock , rtjState sql . NullInt64
2023-07-18 15:06:12 +00:00
// Community events specific fields
var eventsBytes , eventsDescriptionBytes [ ] byte
2021-08-06 15:40:23 +00:00
err = rows . Scan (
2023-06-28 10:53:46 +00:00
& publicKeyBytes , & privateKeyBytes , & descriptionBytes , & joined , & spectated , & verified , & muted , & muteTill ,
2023-07-18 15:06:12 +00:00
& rtjID , & rtjPublicKey , & rtjClock , & rtjENSName , & rtjChatID , & rtjCommunityID , & rtjState , & eventsBytes , & eventsDescriptionBytes )
2021-08-06 15:40:23 +00:00
if err != nil {
return nil , err
}
2023-06-28 10:53:46 +00:00
comm , err = unmarshalCommunityFromDB ( memberIdentity , publicKeyBytes , privateKeyBytes , descriptionBytes , joined , spectated , verified , muted , muteTill . Time , uint64 ( rtjClock . Int64 ) , eventsBytes , eventsDescriptionBytes , p . logger )
2021-08-06 15:40:23 +00:00
if err != nil {
return nil , err
}
rtj := unmarshalRequestToJoinFromDB ( rtjID , rtjCommunityID , rtjPublicKey , rtjENSName , rtjChatID , rtjClock , rtjState )
if ! rtj . Empty ( ) {
comm . AddRequestToJoin ( rtj )
}
comms = append ( comms , comm )
}
return comms , nil
}
2022-04-11 16:14:08 +00:00
func ( p * Persistence ) JoinedAndPendingCommunitiesWithRequests ( memberIdentity * ecdsa . PublicKey ) ( comms [ ] * Community , err error ) {
query := ` SELECT
2023-06-28 10:53:46 +00:00
c . id , c . private_key , c . description , c . joined , c . spectated , c . verified , c . muted , c . muted_till ,
2023-07-18 15:06:12 +00:00
r . id , r . public_key , r . clock , r . ens_name , r . chat_id , r . community_id , r . state , ae . raw_events , ae . raw_description
2022-04-11 16:14:08 +00:00
FROM communities_communities c
LEFT JOIN communities_requests_to_join r ON c . id = r . community_id AND r . public_key = ?
2023-07-18 15:06:12 +00:00
LEFT JOIN communities_events ae ON c . id = ae . id
2022-04-11 16:14:08 +00:00
WHERE c . Joined OR r . state = ? `
rows , err := p . db . Query ( query , common . PubkeyToHex ( memberIdentity ) , RequestToJoinStatePending )
if err != nil {
return nil , err
}
return p . rowsToCommunities ( memberIdentity , rows )
}
func ( p * Persistence ) DeletedCommunities ( memberIdentity * ecdsa . PublicKey ) ( comms [ ] * Community , err error ) {
query := ` SELECT
2023-06-28 10:53:46 +00:00
c . id , c . private_key , c . description , c . joined , c . spectated , c . verified , c . muted , c . muted_till ,
2023-07-18 15:06:12 +00:00
r . id , r . public_key , r . clock , r . ens_name , r . chat_id , r . community_id , r . state , ae . raw_events , ae . raw_description
2022-04-11 16:14:08 +00:00
FROM communities_communities c
LEFT JOIN communities_requests_to_join r ON c . id = r . community_id AND r . public_key = ?
2023-07-18 15:06:12 +00:00
LEFT JOIN communities_events ae ON c . id = ae . id
2022-04-11 16:14:08 +00:00
WHERE NOT c . Joined AND ( r . community_id IS NULL or r . state != ? ) `
rows , err := p . db . Query ( query , common . PubkeyToHex ( memberIdentity ) , RequestToJoinStatePending )
if err != nil {
return nil , err
}
return p . rowsToCommunities ( memberIdentity , rows )
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) CreatedCommunities ( memberIdentity * ecdsa . PublicKey ) ( [ ] * Community , error ) {
query := communitiesBaseQuery + ` WHERE c.private_key IS NOT NULL `
return p . queryCommunities ( memberIdentity , query )
2020-11-18 09:16:51 +00:00
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) GetByID ( memberIdentity * ecdsa . PublicKey , id [ ] byte ) ( * Community , error ) {
2020-11-18 09:16:51 +00:00
var publicKeyBytes , privateKeyBytes , descriptionBytes [ ] byte
var joined bool
2022-09-20 19:57:39 +00:00
var spectated bool
2020-11-18 09:16:51 +00:00
var verified bool
2021-06-30 13:29:43 +00:00
var muted bool
2023-06-17 08:19:05 +00:00
var muteTill sql . NullTime
2021-01-11 10:32:51 +00:00
var requestedToJoinAt sql . NullInt64
2020-11-18 09:16:51 +00:00
2023-07-18 15:06:12 +00:00
// Community events specific fields
var eventsBytes , eventsDescriptionBytes [ ] byte
2023-06-28 10:53:46 +00:00
err := p . db . QueryRow ( communitiesBaseQuery + ` WHERE c.id = ? ` , common . PubkeyToHex ( memberIdentity ) , id ) . Scan ( & publicKeyBytes , & privateKeyBytes , & descriptionBytes , & joined , & spectated , & verified , & muted , & muteTill , & requestedToJoinAt , & eventsBytes , & eventsDescriptionBytes )
2020-11-18 09:16:51 +00:00
if err == sql . ErrNoRows {
return nil , nil
} else if err != nil {
return nil , err
}
2023-06-28 10:53:46 +00:00
return unmarshalCommunityFromDB ( memberIdentity , publicKeyBytes , privateKeyBytes , descriptionBytes , joined , spectated , verified , muted , muteTill . Time , uint64 ( requestedToJoinAt . Int64 ) , eventsBytes , eventsDescriptionBytes , p . logger )
2020-11-18 09:16:51 +00:00
}
2023-07-18 15:06:12 +00:00
func unmarshalCommunityFromDB ( memberIdentity * ecdsa . PublicKey , publicKeyBytes , privateKeyBytes , descriptionBytes [ ] byte , joined ,
2023-06-28 10:53:46 +00:00
spectated , verified , muted bool , muteTill time . Time , requestedToJoinAt uint64 , eventsBytes [ ] byte ,
2023-07-18 15:06:12 +00:00
eventsDescriptionBytes [ ] byte , logger * zap . Logger ) ( * Community , error ) {
2020-11-18 09:16:51 +00:00
var privateKey * ecdsa . PrivateKey
var err error
if privateKeyBytes != nil {
privateKey , err = crypto . ToECDSA ( privateKeyBytes )
if err != nil {
return nil , err
}
}
2023-07-18 15:06:12 +00:00
description , err := decodeCommunityDescription ( descriptionBytes )
2020-11-18 09:16:51 +00:00
if err != nil {
return nil , err
}
2023-07-18 15:06:12 +00:00
id , err := crypto . DecompressPubkey ( publicKeyBytes )
2020-11-18 09:16:51 +00:00
if err != nil {
return nil , err
}
2023-07-18 15:06:12 +00:00
eventsData , err := decodeEventsData ( eventsBytes , eventsDescriptionBytes )
2020-11-18 09:16:51 +00:00
if err != nil {
return nil , err
}
config := Config {
PrivateKey : privateKey ,
CommunityDescription : description ,
2021-01-11 10:32:51 +00:00
MemberIdentity : memberIdentity ,
2020-11-18 09:16:51 +00:00
MarshaledCommunityDescription : descriptionBytes ,
Logger : logger ,
ID : id ,
Verified : verified ,
2021-06-30 13:29:43 +00:00
Muted : muted ,
2023-06-17 08:19:05 +00:00
MuteTill : muteTill ,
2021-01-11 10:32:51 +00:00
RequestedToJoinAt : requestedToJoinAt ,
2020-11-18 09:16:51 +00:00
Joined : joined ,
2022-09-20 19:57:39 +00:00
Spectated : spectated ,
2023-07-18 15:06:12 +00:00
EventsData : eventsData ,
}
community , err := New ( config )
if err != nil {
return nil , err
2020-11-18 09:16:51 +00:00
}
2023-07-18 15:06:12 +00:00
return community , nil
2020-11-18 09:16:51 +00:00
}
2021-01-11 10:32:51 +00:00
2021-08-06 15:40:23 +00:00
func unmarshalRequestToJoinFromDB ( ID , communityID [ ] byte , publicKey , ensName , chatID sql . NullString , clock , state sql . NullInt64 ) * RequestToJoin {
return & RequestToJoin {
ID : ID ,
PublicKey : publicKey . String ,
Clock : uint64 ( clock . Int64 ) ,
ENSName : ensName . String ,
ChatID : chatID . String ,
CommunityID : communityID ,
State : RequestToJoinState ( state . Int64 ) ,
}
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) SaveRequestToJoin ( request * RequestToJoin ) ( err error ) {
tx , err := p . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
var clock uint64
// Fetch any existing request to join
err = tx . QueryRow ( ` SELECT clock FROM communities_requests_to_join WHERE state = ? AND public_key = ? AND community_id = ? ` , RequestToJoinStatePending , request . PublicKey , request . CommunityID ) . Scan ( & clock )
if err != nil && err != sql . ErrNoRows {
return err
}
// This is already processed
if clock >= request . Clock {
2021-11-11 16:37:04 +00:00
return ErrOldRequestToJoin
2021-01-11 10:32:51 +00:00
}
_ , err = tx . Exec ( ` INSERT INTO communities_requests_to_join(id,public_key,clock,ens_name,chat_id,community_id,state) VALUES (?, ?, ?, ?, ?, ?, ?) ` , request . ID , request . PublicKey , request . Clock , request . ENSName , request . ChatID , request . CommunityID , request . State )
return err
}
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
func ( p * Persistence ) SaveRequestToJoinRevealedAddresses ( request * RequestToJoin ) ( err error ) {
tx , err := p . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
return
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
2023-07-14 17:33:47 +00:00
query := ` INSERT OR REPLACE INTO communities_requests_to_join_revealed_addresses (request_id, address, chain_ids, is_airdrop_address) VALUES (?, ?, ?, ?) `
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
stmt , err := tx . Prepare ( query )
if err != nil {
return
}
defer stmt . Close ( )
2023-06-06 18:33:09 +00:00
for _ , account := range request . RevealedAccounts {
var chainIDs [ ] string
for _ , ID := range account . ChainIds {
chainIDs = append ( chainIDs , strconv . Itoa ( int ( ID ) ) )
}
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
_ , err = stmt . Exec (
request . ID ,
2023-06-06 18:33:09 +00:00
account . Address ,
strings . Join ( chainIDs , "," ) ,
2023-07-14 17:33:47 +00:00
account . IsAirdropAddress ,
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
)
if err != nil {
return
}
}
return
}
2023-06-22 06:54:58 +00:00
func ( p * Persistence ) SaveCheckChannelPermissionResponse ( communityID string , chatID string , response * CheckChannelPermissionsResponse ) error {
tx , err := p . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
viewOnlyPermissionIDs := make ( [ ] string , 0 )
viewAndPostPermissionIDs := make ( [ ] string , 0 )
for permissionID := range response . ViewOnlyPermissions . Permissions {
viewOnlyPermissionIDs = append ( viewOnlyPermissionIDs , permissionID )
}
for permissionID := range response . ViewAndPostPermissions . Permissions {
viewAndPostPermissionIDs = append ( viewAndPostPermissionIDs , permissionID )
}
_ , err = tx . Exec ( ` INSERT INTO communities_check_channel_permission_responses (community_id,chat_id,view_only_permissions_satisfied,view_and_post_permissions_satisfied, view_only_permission_ids, view_and_post_permission_ids) VALUES (?, ?, ?, ?, ?, ?) ` , communityID , chatID , response . ViewOnlyPermissions . Satisfied , response . ViewAndPostPermissions . Satisfied , strings . Join ( viewOnlyPermissionIDs [ : ] , "," ) , strings . Join ( viewAndPostPermissionIDs [ : ] , "," ) )
if err != nil {
return err
}
saveCriteriaResults := func ( permissions map [ string ] * PermissionTokenCriteriaResult ) error {
for permissionID , criteriaResult := range permissions {
criteria := make ( [ ] string , 0 )
for _ , val := range criteriaResult . Criteria {
criteria = append ( criteria , strconv . FormatBool ( val ) )
}
_ , err = tx . Exec ( ` INSERT INTO communities_permission_token_criteria_results (permission_id,community_id, chat_id, criteria) VALUES (?, ?, ?, ?) ` , permissionID , communityID , chatID , strings . Join ( criteria [ : ] , "," ) )
if err != nil {
return err
}
}
return nil
}
err = saveCriteriaResults ( response . ViewOnlyPermissions . Permissions )
if err != nil {
return err
}
return saveCriteriaResults ( response . ViewAndPostPermissions . Permissions )
}
func ( p * Persistence ) GetCheckChannelPermissionResponses ( communityID string ) ( map [ string ] * CheckChannelPermissionsResponse , error ) {
rows , err := p . db . Query ( ` SELECT chat_id, view_only_permissions_satisfied, view_and_post_permissions_satisfied, view_only_permission_ids, view_and_post_permission_ids FROM communities_check_channel_permission_responses WHERE community_id = ? ` , communityID )
if err != nil {
return nil , err
}
defer rows . Close ( )
checkChannelPermissionResponses := make ( map [ string ] * CheckChannelPermissionsResponse , 0 )
for rows . Next ( ) {
permissionResponse := & CheckChannelPermissionsResponse {
ViewOnlyPermissions : & CheckChannelViewOnlyPermissionsResult {
Satisfied : false ,
Permissions : make ( map [ string ] * PermissionTokenCriteriaResult ) ,
} ,
ViewAndPostPermissions : & CheckChannelViewAndPostPermissionsResult {
Satisfied : false ,
Permissions : make ( map [ string ] * PermissionTokenCriteriaResult ) ,
} ,
}
var chatID string
var viewOnlyPermissionIDsString string
var viewAndPostPermissionIDsString string
err := rows . Scan ( & chatID , & permissionResponse . ViewOnlyPermissions . Satisfied , & permissionResponse . ViewAndPostPermissions . Satisfied , & viewOnlyPermissionIDsString , & viewAndPostPermissionIDsString )
if err != nil {
return nil , err
}
for _ , permissionID := range strings . Split ( viewOnlyPermissionIDsString , "," ) {
if permissionID != "" {
permissionResponse . ViewOnlyPermissions . Permissions [ permissionID ] = & PermissionTokenCriteriaResult { Criteria : make ( [ ] bool , 0 ) }
}
}
for _ , permissionID := range strings . Split ( viewAndPostPermissionIDsString , "," ) {
if permissionID != "" {
permissionResponse . ViewAndPostPermissions . Permissions [ permissionID ] = & PermissionTokenCriteriaResult { Criteria : make ( [ ] bool , 0 ) }
}
}
checkChannelPermissionResponses [ chatID ] = permissionResponse
}
addCriteriaResult := func ( channelResponses map [ string ] * CheckChannelPermissionsResponse , permissions map [ string ] * PermissionTokenCriteriaResult , chatID string , viewOnly bool ) error {
for permissionID := range permissions {
criteria , err := p . GetPermissionTokenCriteriaResult ( permissionID , communityID , chatID )
if err != nil {
return err
}
if viewOnly {
channelResponses [ chatID ] . ViewOnlyPermissions . Permissions [ permissionID ] = criteria
} else {
channelResponses [ chatID ] . ViewAndPostPermissions . Permissions [ permissionID ] = criteria
}
}
return nil
}
for chatID , response := range checkChannelPermissionResponses {
err := addCriteriaResult ( checkChannelPermissionResponses , response . ViewOnlyPermissions . Permissions , chatID , true )
if err != nil {
return nil , err
}
err = addCriteriaResult ( checkChannelPermissionResponses , response . ViewAndPostPermissions . Permissions , chatID , false )
if err != nil {
return nil , err
}
}
return checkChannelPermissionResponses , nil
}
func ( p * Persistence ) GetPermissionTokenCriteriaResult ( permissionID string , communityID string , chatID string ) ( * PermissionTokenCriteriaResult , error ) {
tx , err := p . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
return nil , err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
criteriaString := ""
err = tx . QueryRow ( ` SELECT criteria FROM communities_permission_token_criteria_results WHERE permission_id = ? AND community_id = ? AND chat_id = ? ` , permissionID , communityID , chatID ) . Scan ( & criteriaString )
if err != nil {
return nil , err
}
criteria := make ( [ ] bool , 0 )
for _ , r := range strings . Split ( criteriaString , "," ) {
val , err := strconv . ParseBool ( r )
if err != nil {
return nil , err
}
criteria = append ( criteria , val )
}
return & PermissionTokenCriteriaResult { Criteria : criteria } , nil
}
2023-06-06 18:33:09 +00:00
func ( p * Persistence ) GetRequestToJoinRevealedAddresses ( requestID [ ] byte ) ( [ ] * protobuf . RevealedAccount , error ) {
revealedAccounts := make ( [ ] * protobuf . RevealedAccount , 0 )
2023-07-14 17:33:47 +00:00
rows , err := p . db . Query ( ` SELECT address, chain_ids, is_airdrop_address FROM communities_requests_to_join_revealed_addresses WHERE request_id = ? ` , requestID )
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
address := ""
2023-06-06 18:33:09 +00:00
chainIDsStr := ""
2023-07-19 19:23:00 +00:00
var isAirdropAddress sql . NullBool
err := rows . Scan ( & address , & chainIDsStr , & isAirdropAddress )
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
if err != nil {
return nil , err
}
2023-06-06 18:33:09 +00:00
chainIDs := make ( [ ] uint64 , 0 )
for _ , chainIDstr := range strings . Split ( chainIDsStr , "," ) {
if chainIDstr != "" {
chainID , err := strconv . Atoi ( chainIDstr )
if err != nil {
return nil , err
}
chainIDs = append ( chainIDs , uint64 ( chainID ) )
}
}
revealedAccount := & protobuf . RevealedAccount {
2023-07-14 17:33:47 +00:00
Address : address ,
ChainIds : chainIDs ,
2023-07-19 19:23:00 +00:00
IsAirdropAddress : false ,
}
if isAirdropAddress . Valid {
revealedAccount . IsAirdropAddress = isAirdropAddress . Bool
2023-06-06 18:33:09 +00:00
}
revealedAccounts = append ( revealedAccounts , revealedAccount )
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
}
2023-06-06 18:33:09 +00:00
return revealedAccounts , nil
feat: add verified wallet accounts to community requests
This commit extends the `CommunityRequestToJoin` with `RevealedAddresses` which represent wallet addresses and signatures provided by the sender, to proof a community owner ownership of those wallet addresses.
**Note: This only works with keystore files maanged by status-go**
At high level, the follwing happens:
1. User instructs Status to send a request to join to a community. By adding a password hash to the instruction, Status will try to unlock the users keystore and verify each wallet account.
2. For every verified wallet account, a signature is created for the following payload, using each wallet's private key
``` keccak256(chatkey + communityID + requestToJoinID) ``` A map of walletAddress->signature is then attached to the community request to join, which will be sent to the community owner
3. The owner node receives the request, and if the community requires users to hold tokens to become a member, it will check and verify whether the given wallet addresses are indeed owned by the sender. If any signature provided by the request cannot be recovered, the request is immediately declined by the owner.
4. The verified addresses are then added to the owner node's database such that, once the request should be accepted, the addresses can be used to check on chain whether they own the necessary funds to fulfill the community's permissions
The checking of required funds is **not** part of this commit. It will be added in a follow-up commit.
2023-03-17 09:19:40 +00:00
}
2022-08-22 10:10:31 +00:00
func ( p * Persistence ) SaveRequestToLeave ( request * RequestToLeave ) error {
tx , err := p . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
var clock uint64
// Fetch any existing request to leave
err = tx . QueryRow ( ` SELECT clock FROM communities_requests_to_leave WHERE public_key = ? AND community_id = ? ` , request . PublicKey , request . CommunityID ) . Scan ( & clock )
if err != nil && err != sql . ErrNoRows {
return err
}
// This is already processed
if clock >= request . Clock {
return ErrOldRequestToLeave
}
_ , err = tx . Exec ( ` INSERT INTO communities_requests_to_leave(id,public_key,clock,community_id) VALUES (?, ?, ?, ?) ` , request . ID , request . PublicKey , request . Clock , request . CommunityID )
return err
}
2022-10-28 08:41:20 +00:00
func ( p * Persistence ) CanceledRequestsToJoinForUser ( pk string ) ( [ ] * RequestToJoin , error ) {
var requests [ ] * RequestToJoin
rows , err := p . db . Query ( ` SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE state = ? AND public_key = ? ` , RequestToJoinStateCanceled , pk )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
request := & RequestToJoin { }
err := rows . Scan ( & request . ID , & request . PublicKey , & request . Clock , & request . ENSName , & request . ChatID , & request . CommunityID , & request . State )
if err != nil {
return nil , err
}
requests = append ( requests , request )
}
return requests , nil
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) PendingRequestsToJoinForUser ( pk string ) ( [ ] * RequestToJoin , error ) {
var requests [ ] * RequestToJoin
rows , err := p . db . Query ( ` SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE state = ? AND public_key = ? ` , RequestToJoinStatePending , pk )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
request := & RequestToJoin { }
err := rows . Scan ( & request . ID , & request . PublicKey , & request . Clock , & request . ENSName , & request . ChatID , & request . CommunityID , & request . State )
if err != nil {
return nil , err
}
requests = append ( requests , request )
}
return requests , nil
}
func ( p * Persistence ) HasPendingRequestsToJoinForUserAndCommunity ( userPk string , communityID [ ] byte ) ( bool , error ) {
var count int
err := p . db . QueryRow ( ` SELECT count(1) FROM communities_requests_to_join WHERE state = ? AND public_key = ? AND community_id = ? ` , RequestToJoinStatePending , userPk , communityID ) . Scan ( & count )
if err != nil {
return false , err
}
return count > 0 , nil
}
2022-08-04 07:44:35 +00:00
func ( p * Persistence ) RequestsToJoinForCommunityWithState ( id [ ] byte , state RequestToJoinState ) ( [ ] * RequestToJoin , error ) {
2021-01-11 10:32:51 +00:00
var requests [ ] * RequestToJoin
2022-08-04 07:44:35 +00:00
rows , err := p . db . Query ( ` SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE state = ? AND community_id = ? ` , state , id )
2021-01-11 10:32:51 +00:00
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
request := & RequestToJoin { }
err := rows . Scan ( & request . ID , & request . PublicKey , & request . Clock , & request . ENSName , & request . ChatID , & request . CommunityID , & request . State )
if err != nil {
return nil , err
}
requests = append ( requests , request )
}
return requests , nil
}
2023-04-21 09:18:47 +00:00
func ( p * Persistence ) PendingRequestsToJoin ( ) ( [ ] * RequestToJoin , error ) {
var requests [ ] * RequestToJoin
rows , err := p . db . Query ( ` SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE state = ? ` , RequestToJoinStatePending )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
request := & RequestToJoin { }
err := rows . Scan ( & request . ID , & request . PublicKey , & request . Clock , & request . ENSName , & request . ChatID , & request . CommunityID , & request . State )
if err != nil {
return nil , err
}
requests = append ( requests , request )
}
return requests , nil
}
2022-08-04 07:44:35 +00:00
func ( p * Persistence ) PendingRequestsToJoinForCommunity ( id [ ] byte ) ( [ ] * RequestToJoin , error ) {
return p . RequestsToJoinForCommunityWithState ( id , RequestToJoinStatePending )
}
func ( p * Persistence ) DeclinedRequestsToJoinForCommunity ( id [ ] byte ) ( [ ] * RequestToJoin , error ) {
return p . RequestsToJoinForCommunityWithState ( id , RequestToJoinStateDeclined )
}
2022-10-28 08:41:20 +00:00
func ( p * Persistence ) CanceledRequestsToJoinForCommunity ( id [ ] byte ) ( [ ] * RequestToJoin , error ) {
return p . RequestsToJoinForCommunityWithState ( id , RequestToJoinStateCanceled )
}
2023-06-14 14:15:46 +00:00
func ( p * Persistence ) AcceptedRequestsToJoinForCommunity ( id [ ] byte ) ( [ ] * RequestToJoin , error ) {
return p . RequestsToJoinForCommunityWithState ( id , RequestToJoinStateAccepted )
}
2021-08-06 15:40:23 +00:00
func ( p * Persistence ) SetRequestToJoinState ( pk string , communityID [ ] byte , state RequestToJoinState ) error {
2021-01-11 10:32:51 +00:00
_ , err := p . db . Exec ( ` UPDATE communities_requests_to_join SET state = ? WHERE community_id = ? AND public_key = ? ` , state , communityID , pk )
return err
}
2023-04-21 09:18:47 +00:00
func ( p * Persistence ) DeletePendingRequestToJoin ( id [ ] byte ) error {
_ , err := p . db . Exec ( ` DELETE FROM communities_requests_to_join WHERE id = ? ` , id )
return err
}
// UpdateClockInRequestToJoin method is used for testing
func ( p * Persistence ) UpdateClockInRequestToJoin ( id [ ] byte , clock uint64 ) error {
_ , err := p . db . Exec ( ` UPDATE communities_requests_to_join SET clock = ? WHERE id = ? ` , clock , id )
return err
}
2023-07-19 12:14:42 +00:00
func ( p * Persistence ) SetMuted ( communityID [ ] byte , muted bool ) error {
_ , err := p . db . Exec ( ` UPDATE communities_communities SET muted = ? WHERE id = ? ` , muted , communityID )
return err
}
func ( p * Persistence ) MuteCommunityTill ( communityID [ ] byte , mutedTill time . Time ) error {
2023-06-17 08:19:05 +00:00
mutedTillFormatted := mutedTill . Format ( time . RFC3339 )
2023-07-19 12:14:42 +00:00
_ , err := p . db . Exec ( ` UPDATE communities_communities SET muted_till = ? WHERE id = ? ` , mutedTillFormatted , communityID )
2021-06-30 13:29:43 +00:00
return err
}
2021-01-11 10:32:51 +00:00
func ( p * Persistence ) GetRequestToJoin ( id [ ] byte ) ( * RequestToJoin , error ) {
request := & RequestToJoin { }
err := p . db . QueryRow ( ` SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE id = ? ` , id ) . Scan ( & request . ID , & request . PublicKey , & request . Clock , & request . ENSName , & request . ChatID , & request . CommunityID , & request . State )
if err != nil {
return nil , err
}
return request , nil
}
2021-08-06 15:40:23 +00:00
2023-02-03 16:33:16 +00:00
func ( p * Persistence ) GetRequestToJoinByPkAndCommunityID ( pk string , communityID [ ] byte ) ( * RequestToJoin , error ) {
request := & RequestToJoin { }
err := p . db . QueryRow ( ` SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE public_key = ? AND community_id = ? ` , pk , communityID ) . Scan ( & request . ID , & request . PublicKey , & request . Clock , & request . ENSName , & request . ChatID , & request . CommunityID , & request . State )
if err != nil {
return nil , err
}
return request , nil
}
2022-10-25 22:06:20 +00:00
func ( p * Persistence ) GetRequestToJoinIDByPkAndCommunityID ( pk string , communityID [ ] byte ) ( [ ] byte , error ) {
var id [ ] byte
err := p . db . QueryRow ( ` SELECT id FROM communities_requests_to_join WHERE community_id = ? AND public_key = ? ` , communityID , pk ) . Scan ( & id )
if err != nil && err != sql . ErrNoRows {
return nil , err
}
return id , nil
}
2022-10-28 08:41:20 +00:00
func ( p * Persistence ) GetRequestToJoinByPk ( pk string , communityID [ ] byte , state RequestToJoinState ) ( * RequestToJoin , error ) {
request := & RequestToJoin { }
err := p . db . QueryRow ( ` SELECT id,public_key,clock,ens_name,chat_id,community_id,state FROM communities_requests_to_join WHERE public_key = ? AND community_id = ? AND state = ? ` , pk , communityID , state ) . Scan ( & request . ID , & request . PublicKey , & request . Clock , & request . ENSName , & request . ChatID , & request . CommunityID , & request . State )
if err != nil {
return nil , err
}
return request , nil
}
2021-08-06 15:40:23 +00:00
func ( p * Persistence ) SetSyncClock ( id [ ] byte , clock uint64 ) error {
_ , err := p . db . Exec ( ` UPDATE communities_communities SET synced_at = ? WHERE id = ? AND synced_at < ? ` , clock , id , clock )
return err
}
func ( p * Persistence ) SetPrivateKey ( id [ ] byte , privKey * ecdsa . PrivateKey ) error {
_ , err := p . db . Exec ( ` UPDATE communities_communities SET private_key = ? WHERE id = ? ` , crypto . FromECDSA ( privKey ) , id )
return err
}
2022-03-08 15:25:00 +00:00
2022-09-28 12:45:34 +00:00
func ( p * Persistence ) SaveWakuMessages ( messages [ ] * types . Message ) ( err error ) {
tx , err := p . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
return
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
query := ` INSERT OR REPLACE INTO waku_messages (sig, timestamp, topic, payload, padding, hash, third_party_id) VALUES (?, ?, ?, ?, ?, ?, ?) `
stmt , err := tx . Prepare ( query )
if err != nil {
return
}
defer stmt . Close ( )
for _ , msg := range messages {
_ , err = stmt . Exec (
msg . Sig ,
msg . Timestamp ,
msg . Topic . String ( ) ,
msg . Payload ,
msg . Padding ,
types . Bytes2Hex ( msg . Hash ) ,
msg . ThirdPartyID ,
)
if err != nil {
return
}
}
return
}
2022-03-09 09:58:05 +00:00
func ( p * Persistence ) SaveWakuMessage ( message * types . Message ) error {
2022-09-28 12:45:34 +00:00
_ , err := p . db . Exec ( ` INSERT OR REPLACE INTO waku_messages (sig, timestamp, topic, payload, padding, hash, third_party_id) VALUES (?, ?, ?, ?, ?, ?, ?) ` ,
2022-03-09 09:58:05 +00:00
message . Sig ,
message . Timestamp ,
message . Topic . String ( ) ,
message . Payload ,
message . Padding ,
types . Bytes2Hex ( message . Hash ) ,
2022-09-28 12:45:34 +00:00
message . ThirdPartyID ,
2022-03-09 09:58:05 +00:00
)
return err
}
2022-03-21 14:18:36 +00:00
func wakuMessageTimestampQuery ( topics [ ] types . TopicType ) string {
query := " FROM waku_messages WHERE "
for i , topic := range topics {
query += ` topic = " ` + topic . String ( ) + ` " `
if i < len ( topics ) - 1 {
query += OR
}
}
return query
}
func ( p * Persistence ) GetOldestWakuMessageTimestamp ( topics [ ] types . TopicType ) ( uint64 , error ) {
var timestamp sql . NullInt64
query := "SELECT MIN(timestamp)"
query += wakuMessageTimestampQuery ( topics )
err := p . db . QueryRow ( query ) . Scan ( & timestamp )
return uint64 ( timestamp . Int64 ) , err
}
func ( p * Persistence ) GetLatestWakuMessageTimestamp ( topics [ ] types . TopicType ) ( uint64 , error ) {
var timestamp sql . NullInt64
query := "SELECT MAX(timestamp)"
query += wakuMessageTimestampQuery ( topics )
err := p . db . QueryRow ( query ) . Scan ( & timestamp )
return uint64 ( timestamp . Int64 ) , err
}
func ( p * Persistence ) GetWakuMessagesByFilterTopic ( topics [ ] types . TopicType , from uint64 , to uint64 ) ( [ ] types . Message , error ) {
2022-09-28 12:45:34 +00:00
query := "SELECT sig, timestamp, topic, payload, padding, hash, third_party_id FROM waku_messages WHERE timestamp >= " + fmt . Sprint ( from ) + " AND timestamp < " + fmt . Sprint ( to ) + " AND ("
2022-03-21 14:18:36 +00:00
for i , topic := range topics {
query += ` topic = " ` + topic . String ( ) + ` " `
if i < len ( topics ) - 1 {
query += OR
}
}
query += ")"
rows , err := p . db . Query ( query )
if err != nil {
return nil , err
}
defer rows . Close ( )
messages := [ ] types . Message { }
for rows . Next ( ) {
msg := types . Message { }
var topicStr string
var hashStr string
2022-09-28 12:45:34 +00:00
err := rows . Scan ( & msg . Sig , & msg . Timestamp , & topicStr , & msg . Payload , & msg . Padding , & hashStr , & msg . ThirdPartyID )
2022-03-21 14:18:36 +00:00
if err != nil {
return nil , err
}
msg . Topic = types . StringToTopic ( topicStr )
msg . Hash = types . Hex2Bytes ( hashStr )
messages = append ( messages , msg )
}
return messages , nil
}
2022-04-22 07:42:22 +00:00
func ( p * Persistence ) HasCommunityArchiveInfo ( communityID types . HexBytes ) ( exists bool , err error ) {
err = p . db . QueryRow ( ` SELECT EXISTS(SELECT 1 FROM communities_archive_info WHERE community_id = ?) ` , communityID . String ( ) ) . Scan ( & exists )
return exists , err
}
2022-12-19 08:34:37 +00:00
func ( p * Persistence ) GetLastSeenMagnetlink ( communityID types . HexBytes ) ( string , error ) {
var magnetlinkURI string
err := p . db . QueryRow ( ` SELECT last_magnetlink_uri FROM communities_archive_info WHERE community_id = ? ` , communityID . String ( ) ) . Scan ( & magnetlinkURI )
if err == sql . ErrNoRows {
return "" , nil
}
return magnetlinkURI , err
}
2022-03-21 14:18:36 +00:00
func ( p * Persistence ) GetMagnetlinkMessageClock ( communityID types . HexBytes ) ( uint64 , error ) {
var magnetlinkClock uint64
err := p . db . QueryRow ( ` SELECT magnetlink_clock FROM communities_archive_info WHERE community_id = ? ` , communityID . String ( ) ) . Scan ( & magnetlinkClock )
if err == sql . ErrNoRows {
return 0 , nil
}
return magnetlinkClock , err
}
2022-04-22 07:42:22 +00:00
func ( p * Persistence ) SaveCommunityArchiveInfo ( communityID types . HexBytes , clock uint64 , lastArchiveEndDate uint64 ) error {
_ , err := p . db . Exec ( ` INSERT INTO communities_archive_info (magnetlink_clock, last_message_archive_end_date, community_id) VALUES (?, ?, ?) ` ,
clock ,
lastArchiveEndDate ,
communityID . String ( ) )
return err
}
2022-03-21 14:18:36 +00:00
func ( p * Persistence ) UpdateMagnetlinkMessageClock ( communityID types . HexBytes , clock uint64 ) error {
_ , err := p . db . Exec ( ` UPDATE communities_archive_info SET
magnetlink_clock = ?
WHERE community_id = ? ` ,
clock ,
communityID . String ( ) )
return err
}
2022-12-19 08:34:37 +00:00
func ( p * Persistence ) UpdateLastSeenMagnetlink ( communityID types . HexBytes , magnetlinkURI string ) error {
_ , err := p . db . Exec ( ` UPDATE communities_archive_info SET
last_magnetlink_uri = ?
WHERE community_id = ? ` ,
magnetlinkURI ,
communityID . String ( ) )
return err
}
2022-03-21 14:18:36 +00:00
func ( p * Persistence ) SaveLastMessageArchiveEndDate ( communityID types . HexBytes , endDate uint64 ) error {
_ , err := p . db . Exec ( ` INSERT INTO communities_archive_info (last_message_archive_end_date, community_id) VALUES (?, ?) ` ,
endDate ,
communityID . String ( ) )
return err
}
func ( p * Persistence ) UpdateLastMessageArchiveEndDate ( communityID types . HexBytes , endDate uint64 ) error {
_ , err := p . db . Exec ( ` UPDATE communities_archive_info SET
last_message_archive_end_date = ?
WHERE community_id = ? ` ,
endDate ,
communityID . String ( ) )
return err
}
func ( p * Persistence ) GetLastMessageArchiveEndDate ( communityID types . HexBytes ) ( uint64 , error ) {
var lastMessageArchiveEndDate uint64
err := p . db . QueryRow ( ` SELECT last_message_archive_end_date FROM communities_archive_info WHERE community_id = ? ` , communityID . String ( ) ) . Scan ( & lastMessageArchiveEndDate )
if err == sql . ErrNoRows {
return 0 , nil
} else if err != nil {
return 0 , err
}
return lastMessageArchiveEndDate , nil
}
2022-12-12 09:22:37 +00:00
func ( p * Persistence ) GetMessageArchiveIDsToImport ( communityID types . HexBytes ) ( [ ] string , error ) {
rows , err := p . db . Query ( "SELECT hash FROM community_message_archive_hashes WHERE community_id = ? AND NOT(imported)" , communityID . String ( ) )
if err != nil {
return nil , err
}
defer rows . Close ( )
ids := [ ] string { }
for rows . Next ( ) {
id := ""
err := rows . Scan ( & id )
if err != nil {
return nil , err
}
ids = append ( ids , id )
}
return ids , err
}
func ( p * Persistence ) GetDownloadedMessageArchiveIDs ( communityID types . HexBytes ) ( [ ] string , error ) {
rows , err := p . db . Query ( "SELECT hash FROM community_message_archive_hashes WHERE community_id = ?" , communityID . String ( ) )
if err != nil {
return nil , err
}
defer rows . Close ( )
ids := [ ] string { }
for rows . Next ( ) {
id := ""
err := rows . Scan ( & id )
if err != nil {
return nil , err
}
ids = append ( ids , id )
}
return ids , err
}
func ( p * Persistence ) SetMessageArchiveIDImported ( communityID types . HexBytes , hash string , imported bool ) error {
_ , err := p . db . Exec ( ` UPDATE community_message_archive_hashes SET imported = ? WHERE hash = ? AND community_id = ? ` , imported , hash , communityID . String ( ) )
return err
}
2022-04-22 07:42:22 +00:00
func ( p * Persistence ) HasMessageArchiveID ( communityID types . HexBytes , hash string ) ( exists bool , err error ) {
err = p . db . QueryRow ( ` SELECT EXISTS (SELECT 1 FROM community_message_archive_hashes WHERE community_id = ? AND hash = ?) ` ,
communityID . String ( ) ,
hash ,
) . Scan ( & exists )
return exists , err
}
func ( p * Persistence ) SaveMessageArchiveID ( communityID types . HexBytes , hash string ) error {
_ , err := p . db . Exec ( ` INSERT INTO community_message_archive_hashes (community_id, hash) VALUES (?, ?) ` ,
communityID . String ( ) ,
hash ,
)
return err
}
2022-03-08 15:25:00 +00:00
func ( p * Persistence ) GetCommunitiesSettings ( ) ( [ ] CommunitySettings , error ) {
2022-06-01 07:55:48 +00:00
rows , err := p . db . Query ( "SELECT community_id, message_archive_seeding_enabled, message_archive_fetching_enabled, clock FROM communities_settings" )
2022-03-08 15:25:00 +00:00
if err != nil {
return nil , err
}
defer rows . Close ( )
communitiesSettings := [ ] CommunitySettings { }
for rows . Next ( ) {
settings := CommunitySettings { }
2022-06-01 07:55:48 +00:00
err := rows . Scan ( & settings . CommunityID , & settings . HistoryArchiveSupportEnabled , & settings . HistoryArchiveSupportEnabled , & settings . Clock )
2022-03-08 15:25:00 +00:00
if err != nil {
return nil , err
}
communitiesSettings = append ( communitiesSettings , settings )
}
return communitiesSettings , err
}
func ( p * Persistence ) CommunitySettingsExist ( communityID types . HexBytes ) ( bool , error ) {
var count int
err := p . db . QueryRow ( ` SELECT count(1) FROM communities_settings WHERE community_id = ? ` , communityID . String ( ) ) . Scan ( & count )
if err != nil {
return false , err
}
return count > 0 , nil
}
func ( p * Persistence ) GetCommunitySettingsByID ( communityID types . HexBytes ) ( * CommunitySettings , error ) {
settings := CommunitySettings { }
2022-06-01 07:55:48 +00:00
err := p . db . QueryRow ( ` SELECT community_id, message_archive_seeding_enabled, message_archive_fetching_enabled, clock FROM communities_settings WHERE community_id = ? ` , communityID . String ( ) ) . Scan ( & settings . CommunityID , & settings . HistoryArchiveSupportEnabled , & settings . HistoryArchiveSupportEnabled , & settings . Clock )
2022-03-08 15:25:00 +00:00
if err == sql . ErrNoRows {
return nil , nil
} else if err != nil {
return nil , err
}
return & settings , nil
}
func ( p * Persistence ) DeleteCommunitySettings ( communityID types . HexBytes ) error {
_ , err := p . db . Exec ( "DELETE FROM communities_settings WHERE community_id = ?" , communityID . String ( ) )
return err
}
func ( p * Persistence ) SaveCommunitySettings ( communitySettings CommunitySettings ) error {
_ , err := p . db . Exec ( ` INSERT INTO communities_settings (
community_id ,
message_archive_seeding_enabled ,
2022-06-01 07:55:48 +00:00
message_archive_fetching_enabled ,
clock
) VALUES ( ? , ? , ? , ? ) ` ,
2022-03-08 15:25:00 +00:00
communitySettings . CommunityID ,
communitySettings . HistoryArchiveSupportEnabled ,
communitySettings . HistoryArchiveSupportEnabled ,
2022-06-01 07:55:48 +00:00
communitySettings . Clock ,
2022-03-08 15:25:00 +00:00
)
return err
}
func ( p * Persistence ) UpdateCommunitySettings ( communitySettings CommunitySettings ) error {
_ , err := p . db . Exec ( ` UPDATE communities_settings SET
message_archive_seeding_enabled = ? ,
2022-06-01 07:55:48 +00:00
message_archive_fetching_enabled = ? ,
clock = ?
2022-03-08 15:25:00 +00:00
WHERE community_id = ? ` ,
communitySettings . HistoryArchiveSupportEnabled ,
communitySettings . HistoryArchiveSupportEnabled ,
2022-06-01 07:55:48 +00:00
communitySettings . Clock ,
2022-10-24 08:45:41 +00:00
communitySettings . CommunityID ,
2022-03-08 15:25:00 +00:00
)
return err
}
2022-03-21 14:18:36 +00:00
func ( p * Persistence ) GetCommunityChatIDs ( communityID types . HexBytes ) ( [ ] string , error ) {
rows , err := p . db . Query ( ` SELECT id FROM chats WHERE community_id = ? ` , communityID . String ( ) )
if err != nil {
return nil , err
}
defer rows . Close ( )
ids := [ ] string { }
for rows . Next ( ) {
id := ""
err := rows . Scan ( & id )
if err != nil {
return nil , err
}
ids = append ( ids , id )
}
return ids , nil
}
2023-01-27 13:27:24 +00:00
2023-04-26 08:48:10 +00:00
func ( p * Persistence ) GetAllCommunityTokens ( ) ( [ ] * CommunityToken , error ) {
2023-06-21 11:20:43 +00:00
rows , err := p . db . Query ( ` SELECT community_id , address , type , name , symbol , description , supply_str ,
2023-06-14 07:47:54 +00:00
infinite_supply , transferable , remote_self_destruct , chain_id , deploy_state , image_base64 , decimals
2023-04-26 08:48:10 +00:00
FROM community_tokens ` )
if err != nil {
return nil , err
}
defer rows . Close ( )
return p . getCommunityTokensInternal ( rows )
}
2023-03-02 17:33:30 +00:00
func ( p * Persistence ) GetCommunityTokens ( communityID string ) ( [ ] * CommunityToken , error ) {
2023-06-21 11:20:43 +00:00
rows , err := p . db . Query ( ` SELECT community_id , address , type , name , symbol , description , supply_str ,
2023-06-14 07:47:54 +00:00
infinite_supply , transferable , remote_self_destruct , chain_id , deploy_state , image_base64 , decimals
2023-03-02 17:33:30 +00:00
FROM community_tokens WHERE community_id = ? ` , communityID )
2023-01-27 13:27:24 +00:00
if err != nil {
return nil , err
}
defer rows . Close ( )
2023-04-26 08:48:10 +00:00
return p . getCommunityTokensInternal ( rows )
}
!refactor: introduce `SaveCommunityToken()` and change `AddCommunityToken()`
**This is a breaking change!**
Prior to this commit we had `AddCommunityToken(token *communities,
croppedImage CroppedImage)` that we used to
1. add a `CommunityToken` to the user's database and
2. to create a `CommunityTokenMetadata` from it which is then added to
the community's `CommunityDescription` and published to its members
However, I've then discovered that we need to separate these two things,
such that we can deploy a community token, then add it to the database
only for tracking purposes, **then** add it to the community description
(and propagate to members) once we know that the deploy tx indeed went
through.
To implement this, this commit introduces a new API
`SaveCommunityToken(token *communities.CommunityToken, croppedImage
CroppedImage)` which adds the token to the database only and doesn't
touch the community description.
The `AddCommunityToken` API is then changed that it's exclusively used
for adding an already saved `CommunityToken` to the community
description so it can be published to members. Hence, the signature is
now `AddCommunityToken(communityID string, chainID int, address
string)`, which makes this a breaking change.
Clients that used `AddCommunityToken()` before now need to ensure that
they first call `SaveCommunityToken()` as `AddCommunityToken()` will
fail otherwise.
2023-07-25 11:35:17 +00:00
func ( p * Persistence ) GetCommunityToken ( communityID string , chainID int , address string ) ( * CommunityToken , error ) {
token := CommunityToken { }
var supplyStr string
err := p . db . QueryRow ( ` SELECT community_id, address, type, name, symbol, description, supply_str, infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals FROM community_tokens WHERE community_id = ? AND chain_id = ? AND address = ? ` , communityID , chainID , address ) . Scan ( & token . CommunityID , & token . Address , & token . TokenType , & token . Name ,
& token . Symbol , & token . Description , & supplyStr , & token . InfiniteSupply , & token . Transferable ,
& token . RemoteSelfDestruct , & token . ChainID , & token . DeployState , & token . Base64Image , & token . Decimals )
if err == sql . ErrNoRows {
return nil , nil
} else if err != nil {
return nil , err
}
supplyBigInt , ok := new ( big . Int ) . SetString ( supplyStr , 10 )
if ok {
token . Supply = & bigint . BigInt { Int : supplyBigInt }
} else {
token . Supply = & bigint . BigInt { Int : big . NewInt ( 0 ) }
}
return & token , nil
}
2023-04-26 08:48:10 +00:00
func ( p * Persistence ) getCommunityTokensInternal ( rows * sql . Rows ) ( [ ] * CommunityToken , error ) {
2023-01-27 13:27:24 +00:00
tokens := [ ] * CommunityToken { }
for rows . Next ( ) {
token := CommunityToken { }
2023-06-21 11:20:43 +00:00
var supplyStr string
2023-01-27 13:27:24 +00:00
err := rows . Scan ( & token . CommunityID , & token . Address , & token . TokenType , & token . Name ,
2023-06-21 11:20:43 +00:00
& token . Symbol , & token . Description , & supplyStr , & token . InfiniteSupply , & token . Transferable ,
2023-06-14 07:47:54 +00:00
& token . RemoteSelfDestruct , & token . ChainID , & token . DeployState , & token . Base64Image , & token . Decimals )
2023-01-27 13:27:24 +00:00
if err != nil {
return nil , err
}
2023-06-21 11:20:43 +00:00
supplyBigInt , ok := new ( big . Int ) . SetString ( supplyStr , 10 )
if ok {
token . Supply = & bigint . BigInt { Int : supplyBigInt }
} else {
token . Supply = & bigint . BigInt { Int : big . NewInt ( 0 ) }
p . logger . Error ( "can't create bigInt from string" )
}
2023-01-27 13:27:24 +00:00
tokens = append ( tokens , & token )
}
return tokens , nil
}
func ( p * Persistence ) AddCommunityToken ( token * CommunityToken ) error {
2023-06-21 11:20:43 +00:00
_ , err := p . db . Exec ( ` INSERT INTO community_tokens ( community_id , address , type , name , symbol , description , supply_str ,
2023-06-14 07:47:54 +00:00
infinite_supply , transferable , remote_self_destruct , chain_id , deploy_state , image_base64 , decimals )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) ` , token . CommunityID , token . Address , token . TokenType , token . Name ,
2023-06-21 11:20:43 +00:00
token . Symbol , token . Description , token . Supply . String ( ) , token . InfiniteSupply , token . Transferable , token . RemoteSelfDestruct ,
2023-06-14 07:47:54 +00:00
token . ChainID , token . DeployState , token . Base64Image , token . Decimals )
2023-01-27 13:27:24 +00:00
return err
}
2023-06-02 08:07:00 +00:00
func ( p * Persistence ) UpdateCommunityTokenState ( chainID int , contractAddress string , deployState DeployState ) error {
_ , err := p . db . Exec ( ` UPDATE community_tokens SET deploy_state = ? WHERE address = ? AND chain_id = ? ` , deployState , contractAddress , chainID )
return err
}
2023-06-21 11:20:43 +00:00
func ( p * Persistence ) UpdateCommunityTokenSupply ( chainID int , contractAddress string , supply * bigint . BigInt ) error {
_ , err := p . db . Exec ( ` UPDATE community_tokens SET supply_str = ? WHERE address = ? AND chain_id = ? ` , supply . String ( ) , contractAddress , chainID )
2023-01-27 13:27:24 +00:00
return err
}
2023-07-18 15:06:12 +00:00
2023-07-24 13:04:11 +00:00
func ( p * Persistence ) RemoveCommunityToken ( chainID int , contractAddress string ) error {
_ , err := p . db . Exec ( ` DELETE FROM community_tokens WHERE chain_id = ? AND address = ? ` , chainID , contractAddress )
return err
}
2023-07-18 15:06:12 +00:00
func decodeCommunityDescription ( descriptionBytes [ ] byte ) ( * protobuf . CommunityDescription , error ) {
metadata := & protobuf . ApplicationMetadataMessage { }
err := proto . Unmarshal ( descriptionBytes , metadata )
if err != nil {
return nil , err
}
description := & protobuf . CommunityDescription { }
err = proto . Unmarshal ( metadata . Payload , description )
if err != nil {
return nil , err
}
return description , nil
}
func decodeEventsData ( eventsBytes [ ] byte , eventsDescriptionBytes [ ] byte ) ( * EventsData , error ) {
if len ( eventsDescriptionBytes ) == 0 {
return nil , nil
}
var events [ ] CommunityEvent
if eventsBytes != nil {
var err error
events , err = communityEventsFromJSONEncodedBytes ( eventsBytes )
if err != nil {
return nil , err
}
}
return & EventsData {
EventsBaseCommunityDescription : eventsDescriptionBytes ,
Events : events ,
} , nil
}