feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

View File

@@ -0,0 +1,8 @@
package ens
// maxRetries is the maximum number of attempts we do before giving up
const maxRetries uint64 = 11
// ENSBackoffTimeSec is the step of the exponential backoff
// we retry roughly for 17 hours after receiving the message 2^11 * 30
const ENSBackoffTimeSec uint64 = 30

View File

@@ -0,0 +1,119 @@
package ens
import (
"context"
"database/sql"
"errors"
)
type Persistence struct {
db *sql.DB
}
func NewPersistence(db *sql.DB) *Persistence {
return &Persistence{db: db}
}
func (p *Persistence) GetENSToBeVerified(now uint64) ([]*VerificationRecord, error) {
rows, err := p.db.Query(`SELECT public_key, name, verified, verified_at, clock, verification_retries, next_retry FROM ens_verification_records WHERE NOT(verified) AND verification_retries < ? AND next_retry <= ?`, maxRetries, now)
if err != nil {
return nil, err
}
var records []*VerificationRecord
for rows.Next() {
var record VerificationRecord
err := rows.Scan(&record.PublicKey, &record.Name, &record.Verified, &record.VerifiedAt, &record.Clock, &record.VerificationRetries, &record.NextRetry)
if err != nil {
return nil, err
}
records = append(records, &record)
}
return records, nil
}
func (p *Persistence) UpdateRecords(records []*VerificationRecord) (err error) {
var tx *sql.Tx
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()
}()
for _, record := range records {
var stmt *sql.Stmt
stmt, err = tx.Prepare(`UPDATE ens_verification_records SET verified = ?, verified_at = ?, verification_retries = ?, next_retry = ? WHERE public_key = ?`)
if err != nil {
return err
}
defer stmt.Close()
_, err = stmt.Exec(record.Verified, record.VerifiedAt, record.VerificationRetries, record.NextRetry, record.PublicKey)
if err != nil {
return err
}
}
return nil
}
// AddRecord adds a record or return the latest available if already in the database and
// hasn't changed
func (p *Persistence) AddRecord(record VerificationRecord) (response *VerificationRecord, err error) {
if !record.Valid() {
err = errors.New("invalid ens record")
return
}
var tx *sql.Tx
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()
}()
dbRecord := &VerificationRecord{PublicKey: record.PublicKey}
err = tx.QueryRow(`SELECT name, clock, verified FROM ens_verification_records WHERE public_key = ?`, record.PublicKey).Scan(&dbRecord.Name, &dbRecord.Clock, &dbRecord.Verified)
if err != nil && err != sql.ErrNoRows {
return
}
if dbRecord.Clock >= record.Clock || dbRecord.Name == record.Name {
response = dbRecord
return
}
_, err = tx.Exec(`INSERT INTO ens_verification_records(public_key, name, clock) VALUES (?,?,?)`, record.PublicKey, record.Name, record.Clock)
return
}
func (p *Persistence) GetVerifiedRecord(publicKey string) (*VerificationRecord, error) {
record := &VerificationRecord{}
err := p.db.QueryRow(`SELECT name, clock FROM ens_verification_records WHERE verified AND public_key = ?`, publicKey).Scan(&record.Name, &record.Clock)
switch err {
case sql.ErrNoRows:
return nil, nil
case nil:
return record, nil
}
return nil, err
}

View File

@@ -0,0 +1,25 @@
package ens
import (
"math"
"strings"
)
type VerificationRecord struct {
PublicKey string
Name string
Clock uint64
Verified bool
VerifiedAt uint64
VerificationRetries uint64
NextRetry uint64
}
// We calculate if it's too early to retry, by exponentially backing off
func (e *VerificationRecord) CalculateNextRetry() {
e.NextRetry = e.VerifiedAt + ENSBackoffTimeSec*uint64(math.Exp2(float64(e.VerificationRetries)))
}
func (e *VerificationRecord) Valid() bool {
return e.Name != "" && strings.HasSuffix(e.Name, ".eth") && e.Clock > 0
}

View File

@@ -0,0 +1,204 @@
package ens
import (
"database/sql"
"time"
"go.uber.org/zap"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/eth-node/types"
enstypes "github.com/status-im/status-go/eth-node/types/ens"
"github.com/status-im/status-go/protocol/common"
)
type Verifier struct {
node types.Node
online bool
persistence *Persistence
logger *zap.Logger
timesource common.TimeSource
subscriptions []chan []*VerificationRecord
rpcEndpoint string
contractAddress string
quit chan struct{}
}
func New(node types.Node, logger *zap.Logger, timesource common.TimeSource, db *sql.DB, rpcEndpoint, contractAddress string) *Verifier {
persistence := NewPersistence(db)
return &Verifier{
node: node,
logger: logger,
persistence: persistence,
timesource: timesource,
rpcEndpoint: rpcEndpoint,
contractAddress: contractAddress,
quit: make(chan struct{}),
}
}
func (v *Verifier) Start() error {
go v.verifyLoop()
return nil
}
func (v *Verifier) Stop() error {
close(v.quit)
return nil
}
// ENSVerified adds an already verified entry to the ens table
func (v *Verifier) ENSVerified(pk, ensName string, clock uint64) error {
// Add returns nil if no record was available
oldRecord, err := v.Add(pk, ensName, clock)
if err != nil {
return err
}
var record *VerificationRecord
if oldRecord != nil {
record = oldRecord
} else {
record = &VerificationRecord{PublicKey: pk, Name: ensName, Clock: clock}
}
record.VerifiedAt = clock
record.Verified = true
records := []*VerificationRecord{record}
err = v.persistence.UpdateRecords(records)
if err != nil {
return err
}
v.publish(records)
return nil
}
func (v *Verifier) GetVerifiedRecord(pk string) (*VerificationRecord, error) {
return v.persistence.GetVerifiedRecord(pk)
}
func (v *Verifier) Add(pk, ensName string, clock uint64) (*VerificationRecord, error) {
record := VerificationRecord{PublicKey: pk, Name: ensName, Clock: clock}
return v.persistence.AddRecord(record)
}
func (v *Verifier) SetOnline(online bool) {
v.online = online
}
func (v *Verifier) verifyLoop() {
ticker := time.NewTicker(30 * time.Second)
for {
select {
case <-v.quit:
ticker.Stop()
return
case <-ticker.C:
if !v.online || v.rpcEndpoint == "" || v.contractAddress == "" {
continue
}
err := v.verify(v.rpcEndpoint, v.contractAddress)
if err != nil {
v.logger.Error("verify loop failed", zap.Error(err))
}
}
}
}
func (v *Verifier) Subscribe() chan []*VerificationRecord {
c := make(chan []*VerificationRecord)
v.subscriptions = append(v.subscriptions, c)
return c
}
func (v *Verifier) publish(records []*VerificationRecord) {
v.logger.Info("publishing records", zap.Any("records", records))
// Publish on channels, drop if buffer is full
for _, s := range v.subscriptions {
select {
case s <- records:
default:
v.logger.Warn("ens subscription channel full, dropping message")
}
}
}
func (v *Verifier) ReverseResolve(address gethcommon.Address) (string, error) {
verifier := v.node.NewENSVerifier(v.logger)
return verifier.ReverseResolve(address, v.rpcEndpoint)
}
// Verify verifies that a registered ENS name matches the expected public key
func (v *Verifier) verify(rpcEndpoint, contractAddress string) error {
v.logger.Debug("verifying ENS Names", zap.String("endpoint", rpcEndpoint))
verifier := v.node.NewENSVerifier(v.logger)
var ensDetails []enstypes.ENSDetails
// Now in seconds
now := v.timesource.GetCurrentTime() / 1000
ensToBeVerified, err := v.persistence.GetENSToBeVerified(now)
if err != nil {
return err
}
recordsMap := make(map[string]*VerificationRecord)
for _, r := range ensToBeVerified {
recordsMap[r.PublicKey] = r
ensDetails = append(ensDetails, enstypes.ENSDetails{
PublicKeyString: r.PublicKey[2:],
Name: r.Name,
})
v.logger.Debug("verifying ens name", zap.Any("record", r))
}
ensResponse, err := verifier.CheckBatch(ensDetails, rpcEndpoint, contractAddress)
if err != nil {
v.logger.Error("failed to check batch", zap.Error(err))
return err
}
var records []*VerificationRecord
for _, details := range ensResponse {
pk := "0x" + details.PublicKeyString
record := recordsMap[pk]
if details.Error == nil {
record.Verified = details.Verified
if !record.Verified {
record.VerificationRetries++
}
} else {
v.logger.Warn("Failed to resolve ens name",
zap.String("name", details.Name),
zap.String("publicKey", details.PublicKeyString),
zap.Error(details.Error),
)
record.VerificationRetries++
}
record.VerifiedAt = now
record.CalculateNextRetry()
records = append(records, record)
}
err = v.persistence.UpdateRecords(records)
if err != nil {
v.logger.Error("failed to update records", zap.Error(err))
return err
}
v.publish(records)
return nil
}