-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpubkey.go
239 lines (219 loc) · 7.75 KB
/
pubkey.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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package main
import (
"fmt"
"strconv"
"strings"
"time"
log "github.com/sirupsen/logrus"
"github.com/tyzbit/btcapi"
)
// PubkeyInfo represents information about a given pubkey.
type PubkeyInfo struct {
Pubkey string `gorm:"primaryKey"`
Nickname string
BalanceSat int
PreviousBalanceSat int
Currency string
BalanceCurrency string
PreviousBalanceCurrency string
TXCount int
}
// GetIdentifier returns the pubkey of the PubkeyInfo variable
func (p PubkeyInfo) GetIdentifier() string {
return p.Pubkey
}
// GetNickname returns the nickname of the PubkeyInfo variable
func (p PubkeyInfo) GetNickname() string {
return p.Nickname
}
// Update updates the database with the PubkeyInfo variable
func (p PubkeyInfo) Update(w Watcher) error {
tx := w.DB.Model(&PubkeyInfo{}).
Where(&PubkeyInfo{Pubkey: p.Pubkey, Nickname: p.Nickname}).
Updates(&p)
if tx.RowsAffected != 1 {
return fmt.Errorf("%d rows affected", tx.RowsAffected)
}
return nil
}
const (
pubkeyMessageTemplate = `**Pubkey Balance Changed**
Nickname: {{ .Nickname }}
Address: {{ .Pubkey }}
Previous Balance (satoshis): {{ .PreviousBalanceSat }}
Previous Balance ({{ .Currency }}): {{ .PreviousBalanceCurrency }}
Transactions: {{ .TXCount }}
New Balance (satoshis): {{ .BalanceSat }}
New Balance ({{ .Currency }}): {{ .BalanceCurrency }}
`
)
// WatchPubkey takes a btcapi config and a nickname:pubkey string. It
// checks the database for a previous pubkey summary and compares the
// previous balance to the current balance. If they are different,
// it calls Watcher.SendNotification.
func (w Watcher) WatchPubkey(stop chan bool, pubkey string) {
main:
for {
select {
case <-stop:
break main
default:
nickname := w.GetNickname(pubkey)
var pubKeys []string
pubKeys = append(pubKeys, pubkey)
oldPubkeyInfo := w.GetPubkeyInfo(pubKeys[0])
// Insert a blank PubkeyInfo if none was found
if (oldPubkeyInfo == PubkeyInfo{}) {
var err error
oldPubkeyInfo, err = w.CreateNewPubkeyInfo(pubKeys[0], nickname)
if err != nil {
log.Error(err)
return
}
}
pubkeySummary, err := w.BTCAPI.ExtendedPublicKeyDetails(pubKeys[0])
if err != nil {
log.Errorf("error calling btcapi: %v", err)
continue
}
if w.CheckAllPubkeyTypes {
for _, pubkeyType := range pubkeySummary.RelatedKeys {
pubKeys = append(pubKeys, pubkeyType.Key)
}
}
// totalBalance is the balance of all pubkeys, similar for totalTxCount.
totalBalance, totalTxCount := 0, 0
for _, pubkey := range pubKeys {
// totalPubkeyBalance is the balance for this pubkey, similar
// for totalPubkeyTxCount. NoTXCount is incremented when
// a consecutive address check yields no new transactions.
totalPubkeyBalance, totalPubkeyTxCount, NoTXCount := 0, 0, 0
pubkey:
for offset := 0; 0 == 0; offset = offset + w.PageSize {
pubKeyPage, err := w.BTCAPI.ExtendedPublicKeyDetailsPage(pubkey, w.PageSize, offset)
if err != nil {
log.Errorf("error calling btcapi: %v", err)
}
// pubkeyTxCount is used to keep track of how many addresses
// we find that don't have new transactions.
pubkeyTxCount := 0
addresses := []string{}
// Zipper join addresses.
// ChangeAddresses and ReceiveAddresses should be the same length.
for i := 0; i < len(pubKeyPage.ChangeAddresses); i++ {
addresses = append(addresses, pubKeyPage.ReceiveAddresses[i], pubKeyPage.ChangeAddresses[i])
}
for _, address := range addresses {
// Check if we've received a stop message
select {
case <-stop:
break main
default:
log.Debug("checking address: " + address)
addressSummary, err := w.UpdatePubkeysTotal(address, &totalPubkeyBalance, &totalPubkeyTxCount)
if err != nil {
log.Errorf("error updating pubkey total: %v", err)
continue
}
if pubkeyTxCount == addressSummary.TXHistory.TXCount {
if NoTXCount > w.Lookahead*2 {
// Stop paging, we haven't had an address with
// transactions in w.Lookahead * 2 addresses.
// (we multiply by 2 because we're checking
// both receive and change addresses)
break pubkey
}
NoTXCount++
}
// Set the pubkeyTxCount so we can compare it next run to
// monitor if we're seeing activity on the addresses
// we're scanning.
pubkeyTxCount = addressSummary.TXHistory.TXCount
}
}
}
// We're done checking this pubkey, add the balance to
// the totals. If w.CheckAllPubkeyTypes is on, we
// might check other pubkeys after this.
totalBalance = totalBalance + totalPubkeyBalance
totalTxCount = totalTxCount + totalPubkeyTxCount
}
currencyBalance, err := w.ConvertBalance(totalBalance)
if err != nil {
log.Errorf("unable to convert balance of %d to %s, err: %v", totalBalance, w.Currency, err)
}
pubkeyInfo := PubkeyInfo{
Pubkey: pubKeys[0],
Nickname: nickname,
BalanceSat: totalBalance,
PreviousBalanceSat: oldPubkeyInfo.BalanceSat,
Currency: strings.ToUpper(w.Currency),
BalanceCurrency: strconv.FormatFloat(currencyBalance, 'f', 2, 64),
PreviousBalanceCurrency: oldPubkeyInfo.BalanceCurrency,
TXCount: totalTxCount,
}
if pubkeyInfo.BalanceSat != oldPubkeyInfo.BalanceSat {
log.Debugf("\"%s\" (%s) balance updated from %d to %d sats", nickname, pubKeys[0], oldPubkeyInfo.BalanceSat, pubkeyInfo.BalanceSat)
w.UpdateInfo(pubkeyInfo)
w.SendNotification(pubkeyInfo, pubkeyMessageTemplate)
}
// Check every second for a stop signal
for i := 0; i < w.SleepInterval; i++ {
select {
case <-stop:
break main
default:
time.Sleep(time.Second)
}
}
}
}
}
// CreateNewPubkeyInfo reates an PubkeyInfo database entry for a new
// pubkey & nickname combination.
func (w Watcher) CreateNewPubkeyInfo(pubkey string, nickname string) (PubkeyInfo, error) {
log.Warnf("previous pubkey information for \"%s\" (%s) was not found, database will be updated", nickname, pubkey)
pubkeyInfo := PubkeyInfo{
Pubkey: pubkey,
Nickname: nickname,
BalanceSat: 0,
BalanceCurrency: "0.0",
Currency: w.Currency,
PreviousBalanceSat: 0,
PreviousBalanceCurrency: "0.0",
TXCount: 0,
}
tx := w.DB.Model(&PubkeyInfo{}).Create(&pubkeyInfo)
if tx.RowsAffected != 1 {
return pubkeyInfo, fmt.Errorf("%d rows affected creating pubkey info for \"%s\" (%s), err: %w", tx.RowsAffected, nickname, pubkey, tx.Error)
}
return pubkeyInfo, nil
}
// UpdatePubkeysTotal takes an address and updates the totals of the pointers provided
// and returns the addressSummary.
func (w Watcher) UpdatePubkeysTotal(address string, totalPubkeyBalance *int, totalPubkeyTxCount *int) (btcapi.AddressSummary, error) {
addressSummary, err := w.BTCAPI.AddressSummary(address)
if err != nil {
return addressSummary, err
}
*totalPubkeyBalance = *totalPubkeyBalance + addressSummary.TXHistory.BalanceSat
*totalPubkeyTxCount = *totalPubkeyTxCount + addressSummary.TXHistory.TXCount
return addressSummary, nil
}
// GetPubkeyInfo gets a PubkeyInfo object from the database with a pubkey
func (w Watcher) GetPubkeyInfo(pubkey string) (pubkeyInfo PubkeyInfo) {
w.DB.Model(&PubkeyInfo{}).
Where(&PubkeyInfo{Pubkey: pubkey}).
Scan(&pubkeyInfo)
return pubkeyInfo
}
// DeletePubkeyInfo deletes a PubkeyInfo object from the database with a pubkey
func (w Watcher) DeletePubkeyInfo(pubkey string) bool {
tx := w.DB.Model(&PubkeyInfo{}).
Where(&PubkeyInfo{Pubkey: pubkey}).
Delete(&PubkeyInfo{Pubkey: pubkey})
if tx.RowsAffected != 1 {
return false
}
return true
}