-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdonation.go
168 lines (135 loc) · 4.32 KB
/
donation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package main
import (
"context"
"encoding/hex"
"errors"
"fmt"
"gonovon/json"
"regexp"
"strconv"
"time"
"github.com/golang/protobuf/proto"
"github.com/nknorg/nkn-sdk-go"
"github.com/nknorg/nkn/common"
"github.com/nknorg/nkn/pb"
)
var donationRegex = regexp.MustCompile(`donate[0-9]+`)
var donationMap map[string]string = make(map[string]string)
func ValidateDonation(message *ChatMessage, allowMempool bool) (err error) {
//Check if message text contains donation, if so validate, otherwise early true nothing to check.
var donationSum int
donateMatches := donationRegex.FindAllString(message.Text, -1)
//No donations always valid
if len(donateMatches) == 0 {
return nil
}
for _, match := range donateMatches {
amount, err := strconv.Atoi(match[6:]) // Remove "donate" from the start
if err != nil {
fmt.Println("Error parsing amount:", err)
continue // Skip to the next match if there's an error
}
donationSum += amount
}
//probably donation0 was included in the message, this is not an actual donation
if donationSum == 0 {
return nil
}
//donation but no hash always invalid
if donationSum > 0 && len(message.Hash) != 64 {
return errors.New("no tx hash")
}
//get the src wallet address
srcPk, _ := nkn.ClientAddrToPubKey(message.Src)
srcAddr, _ := nkn.PubKeyToWalletAddr(srcPk)
transaction := &json.Transaction{}
if allowMempool {
transaction, err = getTransactionFromMempool(message.Hash, srcAddr)
if err != nil {
return err
}
}
if transaction != nil && transaction.Hash == "" {
err = getTransactionWithRetry(context.Background(), message.Hash, transaction)
if err != nil {
return err
}
}
//donation is has to be known
hash, exists := donationMap[transaction.Attributes]
if !exists {
return errors.New("this donation id does not exist")
}
if len(hash) > 0 {
return errors.New("this donation was already received")
}
donationMap[transaction.Attributes] = transaction.Hash
//incorrect txtype always invalid
if transaction.TxType != "TRANSFER_ASSET_TYPE" {
return errors.New("incorrect txtype")
}
payloadBytes, err := hex.DecodeString(transaction.PayloadData)
if err != nil {
fmt.Println(err)
}
fmt.Println(payloadBytes)
transferAsset := new(pb.TransferAsset)
err = proto.Unmarshal(payloadBytes, transferAsset)
if err != nil {
fmt.Println(err)
}
//verify donation amount with transfer amount
if int64(donationSum)*int64(nkn.AmountUnit) != transferAsset.Amount {
return errors.New("transfer amount mismatch")
}
programHashSender, _ := common.Uint160ParseFromBytes(transferAsset.Sender)
programHashRecipient, _ := common.Uint160ParseFromBytes(transferAsset.Recipient)
senderAddr, _ := programHashSender.ToAddress()
recipientAddr, _ := programHashRecipient.ToAddress()
//validate transfer sender is the message sender
if senderAddr != srcAddr {
return errors.New("transfer sender is not message src")
}
//validate recipient is this stream host
if recipientAddr != client.Account().WalletAddress() {
return errors.New("transfer recipient is not host address")
}
return nil
}
func getTransactionWithRetry(ctx context.Context, hash string, transaction *json.Transaction) (err error) {
for i := 0; i < 10; i++ {
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
err = nkn.RPCCall(timeoutCtx, "gettransaction", map[string]interface{}{"hash": hash}, transaction, nkn.GetDefaultRPCConfig())
if err == nil {
return nil // Success!
}
fmt.Printf("Transaction not found yet, retrying in %v...\n", 5*time.Second)
time.Sleep(5 * time.Second)
}
return errors.New("transaction not found in time")
}
func getTransactionFromMempool(hash string, sender string) (*json.Transaction, error) {
var transactions []json.Transaction
requestBody := map[string]interface{}{"action": "txnlist", "address": sender}
for i := 0; i < 5; i++ {
err := nkn.RPCCall(context.Background(), "getrawmempool", requestBody, &transactions, nkn.GetDefaultRPCConfig())
if err != nil {
return nil, err
}
for _, tx := range transactions {
if tx.Hash == hash {
return &tx, nil
}
}
fmt.Printf("Transaction not in mempool, retrying in %v...\n", time.Second)
time.Sleep(time.Second)
}
return nil, nil
}
func generateDonationEntry() string {
rngBytes, _ := nkn.RandomBytes(32)
hex := hex.EncodeToString(rngBytes)
donationMap[hex] = ""
return hex
}