state.go raw
1 package gui
2
3 import (
4 "crypto/cipher"
5 "encoding/json"
6 "io/ioutil"
7 "time"
8
9 "github.com/p9c/p9/pkg/amt"
10 "github.com/p9c/p9/pkg/btcaddr"
11 "github.com/p9c/p9/pkg/chaincfg"
12
13 uberatomic "go.uber.org/atomic"
14
15 l "github.com/p9c/p9/pkg/gel/gio/layout"
16
17 "github.com/p9c/p9/pkg/btcjson"
18 "github.com/p9c/p9/pkg/chainhash"
19 "github.com/p9c/p9/pkg/gcm"
20 "github.com/p9c/p9/pkg/transport"
21 "github.com/p9c/p9/pkg/util/atom"
22 )
23
24 const ZeroAddress = "1111111111111111111114oLvT2"
25
26 // CategoryFilter marks which transactions to omit from the filtered transaction list
27 type CategoryFilter struct {
28 Send bool
29 Generate bool
30 Immature bool
31 Receive bool
32 Unknown bool
33 }
34
35 func (c *CategoryFilter) Filter(s string) (include bool) {
36 include = true
37 if c.Send && s == "send" {
38 include = false
39 }
40 if c.Generate && s == "generate" {
41 include = false
42 }
43 if c.Immature && s == "immature" {
44 include = false
45 }
46 if c.Receive && s == "receive" {
47 include = false
48 }
49 if c.Unknown && s == "unknown" {
50 include = false
51 }
52 return
53 }
54
55 type AddressEntry struct {
56 Address string `json:"address"`
57 Message string `json:"message,omitempty"`
58 Label string `json:"label,omitempty"`
59 Amount amt.Amount `json:"amount"`
60 Created time.Time `json:"created"`
61 Modified time.Time `json:"modified"`
62 TxID string `json:txid,omitempty'`
63 }
64
65 type State struct {
66 lastUpdated *atom.Time
67 bestBlockHeight *atom.Int32
68 bestBlockHash *atom.Hash
69 balance *atom.Float64
70 balanceUnconfirmed *atom.Float64
71 goroutines []l.Widget
72 allTxs *atom.ListTransactionsResult
73 filteredTxs *atom.ListTransactionsResult
74 filter CategoryFilter
75 filterChanged *atom.Bool
76 currentReceivingAddress *atom.Address
77 isAddress *atom.Bool
78 activePage *uberatomic.String
79 sendAddresses []AddressEntry
80 receiveAddresses []AddressEntry
81 }
82
83 func GetNewState(params *chaincfg.Params, activePage *uberatomic.String) *State {
84 fc := &atom.Bool{
85 Bool: uberatomic.NewBool(false),
86 }
87 return &State{
88 lastUpdated: atom.NewTime(time.Now()),
89 bestBlockHeight: &atom.Int32{Int32: uberatomic.NewInt32(0)},
90 bestBlockHash: atom.NewHash(chainhash.Hash{}),
91 balance: &atom.Float64{Float64: uberatomic.NewFloat64(0)},
92 balanceUnconfirmed: &atom.Float64{
93 Float64: uberatomic.NewFloat64(0),
94 },
95 goroutines: nil,
96 allTxs: atom.NewListTransactionsResult(
97 []btcjson.ListTransactionsResult{},
98 ),
99 filteredTxs: atom.NewListTransactionsResult(
100 []btcjson.ListTransactionsResult{},
101 ),
102 filter: CategoryFilter{},
103 filterChanged: fc,
104 currentReceivingAddress: atom.NewAddress(
105 &btcaddr.PubKeyHash{},
106 params,
107 ),
108 isAddress: &atom.Bool{Bool: uberatomic.NewBool(false)},
109 activePage: activePage,
110 }
111 }
112
113 func (s *State) BumpLastUpdated() {
114 s.lastUpdated.Store(time.Now())
115 }
116
117 func (s *State) SetReceivingAddress(addr btcaddr.Address) {
118 s.currentReceivingAddress.Store(addr)
119 }
120
121 func (s *State) IsReceivingAddress() bool {
122 addr := s.currentReceivingAddress.String.Load()
123 if addr == ZeroAddress || addr == "" {
124 s.isAddress.Store(false)
125 } else {
126 s.isAddress.Store(true)
127 }
128 return s.isAddress.Load()
129 }
130
131 // Save the state to the specified file
132 func (s *State) Save(filename string, pass []byte, debug bool) (e error) {
133 D.Ln("saving state...")
134 marshalled := s.Marshal()
135 var j []byte
136 if j, e = json.MarshalIndent(marshalled, "", " "); E.Chk(e) {
137 return
138 }
139 // D.Ln(string(j))
140 var ciph cipher.AEAD
141 if ciph, e = gcm.GetCipher(pass); E.Chk(e) {
142 return
143 }
144 var nonce []byte
145 if nonce, e = transport.GetNonce(ciph); E.Chk(e) {
146 return
147 }
148 crypted := append(nonce, ciph.Seal(nil, nonce, j, nil)...)
149 var b []byte
150 _ = b
151 if b, e = ciph.Open(nil, nonce, crypted[len(nonce):], nil); E.Chk(e) {
152 // since it was just created it should not fail to decrypt
153 panic(e)
154 // interrupt.Request()
155 return
156 }
157 if e = ioutil.WriteFile(filename, crypted, 0600); E.Chk(e) {
158 }
159 if debug {
160 if e = ioutil.WriteFile(filename+".clear", j, 0600); E.Chk(e) {
161 }
162 }
163 return
164 }
165
166 // Load in the configuration from the specified file and decrypt using the given password
167 func (s *State) Load(filename string, pass []byte) (e error) {
168 D.Ln("loading state...")
169 var data []byte
170 var ciph cipher.AEAD
171 if data, e = ioutil.ReadFile(filename); E.Chk(e) {
172 return
173 }
174 D.Ln("cipher:", string(pass))
175 if ciph, e = gcm.GetCipher(pass); E.Chk(e) {
176 return
177 }
178 ns := ciph.NonceSize()
179 D.Ln("nonce size:", ns)
180 nonce := data[:ns]
181 data = data[ns:]
182 var b []byte
183 if b, e = ciph.Open(nil, nonce, data, nil); E.Chk(e) {
184 // interrupt.Request()
185 return
186 }
187 // yay, right password, now unmarshal
188 ss := &Marshalled{}
189 if e = json.Unmarshal(b, ss); E.Chk(e) {
190 return
191 }
192 // D.Ln(string(b))
193 ss.Unmarshal(s)
194 return
195 }
196
197 type Marshalled struct {
198 LastUpdated time.Time
199 BestBlockHeight int32
200 BestBlockHash chainhash.Hash
201 Balance float64
202 BalanceUnconfirmed float64
203 AllTxs []btcjson.ListTransactionsResult
204 Filter CategoryFilter
205 ReceivingAddress string
206 ActivePage string
207 ReceiveAddressBook []AddressEntry
208 SendAddressBook []AddressEntry
209 }
210
211 func (s *State) Marshal() (out *Marshalled) {
212 out = &Marshalled{
213 LastUpdated: s.lastUpdated.Load(),
214 BestBlockHeight: s.bestBlockHeight.Load(),
215 BestBlockHash: s.bestBlockHash.Load(),
216 Balance: s.balance.Load(),
217 BalanceUnconfirmed: s.balanceUnconfirmed.Load(),
218 AllTxs: s.allTxs.Load(),
219 Filter: s.filter,
220 ReceivingAddress: s.currentReceivingAddress.Load().EncodeAddress(),
221 ActivePage: s.activePage.Load(),
222 ReceiveAddressBook: s.receiveAddresses,
223 SendAddressBook: s.sendAddresses,
224 }
225 return
226 }
227
228 func (m *Marshalled) Unmarshal(s *State) {
229 s.lastUpdated.Store(m.LastUpdated)
230 s.bestBlockHeight.Store(m.BestBlockHeight)
231 s.bestBlockHash.Store(m.BestBlockHash)
232 s.balance.Store(m.Balance)
233 s.balanceUnconfirmed.Store(m.BalanceUnconfirmed)
234 if len(s.allTxs.Load()) < len(m.AllTxs) {
235 s.allTxs.Store(m.AllTxs)
236 }
237 s.receiveAddresses = m.ReceiveAddressBook
238 s.sendAddresses = m.SendAddressBook
239 s.filter = m.Filter
240
241 if m.ReceivingAddress != "1111111111111111111114oLvT2" {
242 var e error
243 var ra btcaddr.Address
244 if ra, e = btcaddr.Decode(m.ReceivingAddress, s.currentReceivingAddress.ForNet); E.Chk(e) {
245 }
246 s.currentReceivingAddress.Store(ra)
247 }
248 s.SetActivePage(m.ActivePage)
249 return
250 }
251
252 func (s *State) Goroutines() []l.Widget {
253 return s.goroutines
254 }
255
256 func (s *State) SetGoroutines(gr []l.Widget) {
257 s.goroutines = gr
258 }
259
260 func (s *State) SetAllTxs(atxs []btcjson.ListTransactionsResult) {
261 s.allTxs.Store(atxs)
262 // generate filtered state
263 filteredTxs := make([]btcjson.ListTransactionsResult, 0, len(s.allTxs.Load()))
264 for i := range atxs {
265 if s.filter.Filter(atxs[i].Category) {
266 filteredTxs = append(filteredTxs, atxs[i])
267 }
268 }
269 s.filteredTxs.Store(filteredTxs)
270 }
271
272 func (s *State) LastUpdated() time.Time {
273 return s.lastUpdated.Load()
274 }
275
276 func (s *State) BestBlockHeight() int32 {
277 return s.bestBlockHeight.Load()
278 }
279
280 func (s *State) BestBlockHash() *chainhash.Hash {
281 o := s.bestBlockHash.Load()
282 return &o
283 }
284
285 func (s *State) Balance() float64 {
286 return s.balance.Load()
287 }
288
289 func (s *State) BalanceUnconfirmed() float64 {
290 return s.balanceUnconfirmed.Load()
291 }
292
293 func (s *State) ActivePage() string {
294 return s.activePage.Load()
295 }
296
297 func (s *State) SetActivePage(page string) {
298 s.activePage.Store(page)
299 }
300
301 func (s *State) SetBestBlockHeight(height int32) {
302 s.BumpLastUpdated()
303 s.bestBlockHeight.Store(height)
304 }
305
306 func (s *State) SetBestBlockHash(h *chainhash.Hash) {
307 s.BumpLastUpdated()
308 s.bestBlockHash.Store(*h)
309 }
310
311 func (s *State) SetBalance(total float64) {
312 s.BumpLastUpdated()
313 s.balance.Store(total)
314 }
315
316 func (s *State) SetBalanceUnconfirmed(unconfirmed float64) {
317 s.BumpLastUpdated()
318 s.balanceUnconfirmed.Store(unconfirmed)
319 }
320