320 lines
7.8 KiB
Go
320 lines
7.8 KiB
Go
package gui
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"time"
|
|
|
|
"github.com/p9c/p9/pkg/amt"
|
|
"github.com/p9c/p9/pkg/btcaddr"
|
|
"github.com/p9c/p9/pkg/chaincfg"
|
|
|
|
uberatomic "go.uber.org/atomic"
|
|
|
|
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
|
|
|
"github.com/p9c/p9/pkg/btcjson"
|
|
"github.com/p9c/p9/pkg/chainhash"
|
|
"github.com/p9c/p9/pkg/gcm"
|
|
"github.com/p9c/p9/pkg/transport"
|
|
"github.com/p9c/p9/pkg/util/atom"
|
|
)
|
|
|
|
const ZeroAddress = "1111111111111111111114oLvT2"
|
|
|
|
// CategoryFilter marks which transactions to omit from the filtered transaction list
|
|
type CategoryFilter struct {
|
|
Send bool
|
|
Generate bool
|
|
Immature bool
|
|
Receive bool
|
|
Unknown bool
|
|
}
|
|
|
|
func (c *CategoryFilter) Filter(s string) (include bool) {
|
|
include = true
|
|
if c.Send && s == "send" {
|
|
include = false
|
|
}
|
|
if c.Generate && s == "generate" {
|
|
include = false
|
|
}
|
|
if c.Immature && s == "immature" {
|
|
include = false
|
|
}
|
|
if c.Receive && s == "receive" {
|
|
include = false
|
|
}
|
|
if c.Unknown && s == "unknown" {
|
|
include = false
|
|
}
|
|
return
|
|
}
|
|
|
|
type AddressEntry struct {
|
|
Address string `json:"address"`
|
|
Message string `json:"message,omitempty"`
|
|
Label string `json:"label,omitempty"`
|
|
Amount amt.Amount `json:"amount"`
|
|
Created time.Time `json:"created"`
|
|
Modified time.Time `json:"modified"`
|
|
TxID string `json:txid,omitempty'`
|
|
}
|
|
|
|
type State struct {
|
|
lastUpdated *atom.Time
|
|
bestBlockHeight *atom.Int32
|
|
bestBlockHash *atom.Hash
|
|
balance *atom.Float64
|
|
balanceUnconfirmed *atom.Float64
|
|
goroutines []l.Widget
|
|
allTxs *atom.ListTransactionsResult
|
|
filteredTxs *atom.ListTransactionsResult
|
|
filter CategoryFilter
|
|
filterChanged *atom.Bool
|
|
currentReceivingAddress *atom.Address
|
|
isAddress *atom.Bool
|
|
activePage *uberatomic.String
|
|
sendAddresses []AddressEntry
|
|
receiveAddresses []AddressEntry
|
|
}
|
|
|
|
func GetNewState(params *chaincfg.Params, activePage *uberatomic.String) *State {
|
|
fc := &atom.Bool{
|
|
Bool: uberatomic.NewBool(false),
|
|
}
|
|
return &State{
|
|
lastUpdated: atom.NewTime(time.Now()),
|
|
bestBlockHeight: &atom.Int32{Int32: uberatomic.NewInt32(0)},
|
|
bestBlockHash: atom.NewHash(chainhash.Hash{}),
|
|
balance: &atom.Float64{Float64: uberatomic.NewFloat64(0)},
|
|
balanceUnconfirmed: &atom.Float64{
|
|
Float64: uberatomic.NewFloat64(0),
|
|
},
|
|
goroutines: nil,
|
|
allTxs: atom.NewListTransactionsResult(
|
|
[]btcjson.ListTransactionsResult{},
|
|
),
|
|
filteredTxs: atom.NewListTransactionsResult(
|
|
[]btcjson.ListTransactionsResult{},
|
|
),
|
|
filter: CategoryFilter{},
|
|
filterChanged: fc,
|
|
currentReceivingAddress: atom.NewAddress(
|
|
&btcaddr.PubKeyHash{},
|
|
params,
|
|
),
|
|
isAddress: &atom.Bool{Bool: uberatomic.NewBool(false)},
|
|
activePage: activePage,
|
|
}
|
|
}
|
|
|
|
func (s *State) BumpLastUpdated() {
|
|
s.lastUpdated.Store(time.Now())
|
|
}
|
|
|
|
func (s *State) SetReceivingAddress(addr btcaddr.Address) {
|
|
s.currentReceivingAddress.Store(addr)
|
|
}
|
|
|
|
func (s *State) IsReceivingAddress() bool {
|
|
addr := s.currentReceivingAddress.String.Load()
|
|
if addr == ZeroAddress || addr == "" {
|
|
s.isAddress.Store(false)
|
|
} else {
|
|
s.isAddress.Store(true)
|
|
}
|
|
return s.isAddress.Load()
|
|
}
|
|
|
|
// Save the state to the specified file
|
|
func (s *State) Save(filename string, pass []byte, debug bool) (e error) {
|
|
D.Ln("saving state...")
|
|
marshalled := s.Marshal()
|
|
var j []byte
|
|
if j, e = json.MarshalIndent(marshalled, "", " "); E.Chk(e) {
|
|
return
|
|
}
|
|
// D.Ln(string(j))
|
|
var ciph cipher.AEAD
|
|
if ciph, e = gcm.GetCipher(pass); E.Chk(e) {
|
|
return
|
|
}
|
|
var nonce []byte
|
|
if nonce, e = transport.GetNonce(ciph); E.Chk(e) {
|
|
return
|
|
}
|
|
crypted := append(nonce, ciph.Seal(nil, nonce, j, nil)...)
|
|
var b []byte
|
|
_ = b
|
|
if b, e = ciph.Open(nil, nonce, crypted[len(nonce):], nil); E.Chk(e) {
|
|
// since it was just created it should not fail to decrypt
|
|
panic(e)
|
|
// interrupt.Request()
|
|
return
|
|
}
|
|
if e = ioutil.WriteFile(filename, crypted, 0600); E.Chk(e) {
|
|
}
|
|
if debug {
|
|
if e = ioutil.WriteFile(filename+".clear", j, 0600); E.Chk(e) {
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Load in the configuration from the specified file and decrypt using the given password
|
|
func (s *State) Load(filename string, pass []byte) (e error) {
|
|
D.Ln("loading state...")
|
|
var data []byte
|
|
var ciph cipher.AEAD
|
|
if data, e = ioutil.ReadFile(filename); E.Chk(e) {
|
|
return
|
|
}
|
|
D.Ln("cipher:", string(pass))
|
|
if ciph, e = gcm.GetCipher(pass); E.Chk(e) {
|
|
return
|
|
}
|
|
ns := ciph.NonceSize()
|
|
D.Ln("nonce size:", ns)
|
|
nonce := data[:ns]
|
|
data = data[ns:]
|
|
var b []byte
|
|
if b, e = ciph.Open(nil, nonce, data, nil); E.Chk(e) {
|
|
// interrupt.Request()
|
|
return
|
|
}
|
|
// yay, right password, now unmarshal
|
|
ss := &Marshalled{}
|
|
if e = json.Unmarshal(b, ss); E.Chk(e) {
|
|
return
|
|
}
|
|
// D.Ln(string(b))
|
|
ss.Unmarshal(s)
|
|
return
|
|
}
|
|
|
|
type Marshalled struct {
|
|
LastUpdated time.Time
|
|
BestBlockHeight int32
|
|
BestBlockHash chainhash.Hash
|
|
Balance float64
|
|
BalanceUnconfirmed float64
|
|
AllTxs []btcjson.ListTransactionsResult
|
|
Filter CategoryFilter
|
|
ReceivingAddress string
|
|
ActivePage string
|
|
ReceiveAddressBook []AddressEntry
|
|
SendAddressBook []AddressEntry
|
|
}
|
|
|
|
func (s *State) Marshal() (out *Marshalled) {
|
|
out = &Marshalled{
|
|
LastUpdated: s.lastUpdated.Load(),
|
|
BestBlockHeight: s.bestBlockHeight.Load(),
|
|
BestBlockHash: s.bestBlockHash.Load(),
|
|
Balance: s.balance.Load(),
|
|
BalanceUnconfirmed: s.balanceUnconfirmed.Load(),
|
|
AllTxs: s.allTxs.Load(),
|
|
Filter: s.filter,
|
|
ReceivingAddress: s.currentReceivingAddress.Load().EncodeAddress(),
|
|
ActivePage: s.activePage.Load(),
|
|
ReceiveAddressBook: s.receiveAddresses,
|
|
SendAddressBook: s.sendAddresses,
|
|
}
|
|
return
|
|
}
|
|
|
|
func (m *Marshalled) Unmarshal(s *State) {
|
|
s.lastUpdated.Store(m.LastUpdated)
|
|
s.bestBlockHeight.Store(m.BestBlockHeight)
|
|
s.bestBlockHash.Store(m.BestBlockHash)
|
|
s.balance.Store(m.Balance)
|
|
s.balanceUnconfirmed.Store(m.BalanceUnconfirmed)
|
|
if len(s.allTxs.Load()) < len(m.AllTxs) {
|
|
s.allTxs.Store(m.AllTxs)
|
|
}
|
|
s.receiveAddresses = m.ReceiveAddressBook
|
|
s.sendAddresses = m.SendAddressBook
|
|
s.filter = m.Filter
|
|
|
|
if m.ReceivingAddress != "1111111111111111111114oLvT2" {
|
|
var e error
|
|
var ra btcaddr.Address
|
|
if ra, e = btcaddr.Decode(m.ReceivingAddress, s.currentReceivingAddress.ForNet); E.Chk(e) {
|
|
}
|
|
s.currentReceivingAddress.Store(ra)
|
|
}
|
|
s.SetActivePage(m.ActivePage)
|
|
return
|
|
}
|
|
|
|
func (s *State) Goroutines() []l.Widget {
|
|
return s.goroutines
|
|
}
|
|
|
|
func (s *State) SetGoroutines(gr []l.Widget) {
|
|
s.goroutines = gr
|
|
}
|
|
|
|
func (s *State) SetAllTxs(atxs []btcjson.ListTransactionsResult) {
|
|
s.allTxs.Store(atxs)
|
|
// generate filtered state
|
|
filteredTxs := make([]btcjson.ListTransactionsResult, 0, len(s.allTxs.Load()))
|
|
for i := range atxs {
|
|
if s.filter.Filter(atxs[i].Category) {
|
|
filteredTxs = append(filteredTxs, atxs[i])
|
|
}
|
|
}
|
|
s.filteredTxs.Store(filteredTxs)
|
|
}
|
|
|
|
func (s *State) LastUpdated() time.Time {
|
|
return s.lastUpdated.Load()
|
|
}
|
|
|
|
func (s *State) BestBlockHeight() int32 {
|
|
return s.bestBlockHeight.Load()
|
|
}
|
|
|
|
func (s *State) BestBlockHash() *chainhash.Hash {
|
|
o := s.bestBlockHash.Load()
|
|
return &o
|
|
}
|
|
|
|
func (s *State) Balance() float64 {
|
|
return s.balance.Load()
|
|
}
|
|
|
|
func (s *State) BalanceUnconfirmed() float64 {
|
|
return s.balanceUnconfirmed.Load()
|
|
}
|
|
|
|
func (s *State) ActivePage() string {
|
|
return s.activePage.Load()
|
|
}
|
|
|
|
func (s *State) SetActivePage(page string) {
|
|
s.activePage.Store(page)
|
|
}
|
|
|
|
func (s *State) SetBestBlockHeight(height int32) {
|
|
s.BumpLastUpdated()
|
|
s.bestBlockHeight.Store(height)
|
|
}
|
|
|
|
func (s *State) SetBestBlockHash(h *chainhash.Hash) {
|
|
s.BumpLastUpdated()
|
|
s.bestBlockHash.Store(*h)
|
|
}
|
|
|
|
func (s *State) SetBalance(total float64) {
|
|
s.BumpLastUpdated()
|
|
s.balance.Store(total)
|
|
}
|
|
|
|
func (s *State) SetBalanceUnconfirmed(unconfirmed float64) {
|
|
s.BumpLastUpdated()
|
|
s.balanceUnconfirmed.Store(unconfirmed)
|
|
}
|