github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/walletapi/wallet.go (about)

     1  // Copyright 2017-2018 DERO Project. All rights reserved.
     2  // Use of this source code in any form is governed by RESEARCH license.
     3  // license can be found in the LICENSE file.
     4  // GPG: 0F39 E425 8C65 3947 702A  8234 08B2 0360 A03A 9DE8
     5  //
     6  //
     7  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
     8  // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     9  // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
    10  // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    11  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    12  // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    13  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    14  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
    15  // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    16  
    17  package walletapi
    18  
    19  import "fmt"
    20  import "net"
    21  import "sort"
    22  import "sync"
    23  import "time"
    24  import "bytes"
    25  import "strings"
    26  import "crypto/rand"
    27  import "encoding/json"
    28  import "encoding/binary"
    29  
    30  import "github.com/romana/rlog"
    31  import "github.com/vmihailenco/msgpack"
    32  
    33  import "github.com/deroproject/derosuite/config"
    34  import "github.com/deroproject/derosuite/structures"
    35  import "github.com/deroproject/derosuite/crypto"
    36  import "github.com/deroproject/derosuite/crypto/ringct"
    37  import "github.com/deroproject/derosuite/globals"
    38  import "github.com/deroproject/derosuite/walletapi/mnemonics"
    39  import "github.com/deroproject/derosuite/address"
    40  import "github.com/deroproject/derosuite/blockchain/inputmaturity"
    41  
    42  // used to encrypt payment id
    43  const ENCRYPTED_PAYMENT_ID_TAIL = 0x8d
    44  
    45  type _Keys struct {
    46  	Spendkey_Secret crypto.Key `json:"spendkey_secret"`
    47  	Spendkey_Public crypto.Key `json:"spendkey_public"`
    48  	Viewkey_Secret  crypto.Key `json:"viewkey_secret"`
    49  	Viewkey_Public  crypto.Key `json:"viewkey_public"`
    50  }
    51  
    52  // all random outputs are stored within wallet in this form
    53  // to be used as ring members
    54  type Ring_Member struct { // structure size is around 74 bytes
    55  	InKey         ringct.CtKey `msgpack:"K"`
    56  	Index_Global  uint64       `msgpack:"I"`
    57  	Height        uint64       `msgpack:"H"`
    58  	Unlock_Height uint64       `msgpack:"U,omitempty"` // this is mostly empty
    59  	Sigtype       uint64       `msgpack:"S,omitempty"` // this is empty for miner tx
    60  }
    61  
    62  type Account struct {
    63  	Keys           _Keys   `json:"keys"`
    64  	SeedLanguage   string  `json:"seedlanguage"`
    65  	FeesMultiplier float32 `json:"feesmultiplier"` // fees multiplier accurate to 2 decimals
    66  	Mixin          int     `json:"mixin"`          // default mixn to use for txs
    67  
    68  	ViewOnly bool `json:"viewonly"` // is this viewonly wallet
    69  
    70  	Index_Global uint64 `json:"index_global"` // till where the indexes have been processed, it must only increase and never decrease
    71  	Height       uint64 `json:"height"`       // block height till where blockchain has been scanned
    72  	TopoHeight   int64  `json:"topoheight"`   // block height till where blockchain has been scanned
    73  
    74  	//Wallet_Height    uint64    `json:"wallet_height"`// used to track height till which we have scanned the inputs
    75  
    76  	balance_stale  bool   // whether the balance  is stale
    77  	Balance_Mature uint64 `json:"balance_mature"` // total balance of account
    78  	Balance_Locked uint64 `json:"balance_locked"` // balance locked
    79  
    80  	random_percent uint64 // number of outputs to store within the db, for mixing, default is 10%
    81  
    82  	key_image_checklist map[crypto.Key]bool // key images which need to be monitored, this is updated when new funds arrive
    83  
    84  	//Outputs_Array []TX_Wallet_Data // all outputs found in the chain belonging to us, as found in chain
    85  
    86  	// uint64 si the Index_Global which is the unique number
    87  	//Outputs_Index    map[uint64]bool               // all outputs which  are ours for deduplication
    88  	//Outputs_Ready    map[uint64]TX_Wallet_Data     // these outputs are ready for consumption ( maturity needs to be checked)
    89  	//Keyimages_Ready  map[crypto.Key]bool           // keyimages which are ready to get consumed, // we monitor them to find which
    90  	//Outputs_Consumed map[crypto.Key]TX_Wallet_Data // the key is the keyimage
    91  
    92  	//Random_Outputs  map[uint64]TX_Wallet_Data  // random ring members
    93  	//Random_Outputs_Recent map[uint64]TX_Wallet_Data  // random ring members from recent blocks
    94  	//Ring_Members    map[uint64]bool // ring members
    95  
    96  	sync.Mutex // syncronise modifications to this structure
    97  }
    98  
    99  // this structure is kept by wallet
   100  type TX_Wallet_Data struct {
   101  	TXdata globals.TX_Output_Data `msgpack:"txdata"` // all the fields of output data
   102  
   103  	WAmount      uint64       `msgpack:"wamount"` // actual amount, in case of miner it is verbatim, for other cases it decrypted
   104  	WKey         ringct.CtKey `msgpack:"wkey"`    // key which is used to later send this specific output
   105  	WKimage      crypto.Key   `msgpack:"wkimage"` // key image which gets consumed when this output is spent
   106  	WSpent       bool         `msgpack:"wspent"`  // whether this output has been spent
   107  	WSpentPool   bool         //`msgpack:""`// we built and send out a tx , but it has not been mined
   108  	WPaymentID   []byte       `msgpack:"wpaymentid"`   // payment if if present and decrypted if required
   109  	WSecretTXkey crypto.Key   `msgpack:"wsecrettxkey"` // tx secret which can be be used to prove that the funds have been spent
   110  }
   111  
   112  // generate keys from using random numbers
   113  func Generate_Keys_From_Random() (user *Account, err error) {
   114  	user = &Account{Mixin: 5, FeesMultiplier: 1.5}
   115  	seed := crypto.RandomScalar()
   116  	user.Keys = Generate_Keys_From_Seed(*seed)
   117  
   118  	return
   119  }
   120  
   121  // generate keys from seed which is from the recovery words
   122  // or we feed in direct
   123  func Generate_Keys_From_Seed(Seed crypto.Key) (keys _Keys) {
   124  
   125  	// setup main keys
   126  	keys.Spendkey_Secret = Seed
   127  	keys.Spendkey_Public = *(Seed.PublicKey())
   128  
   129  	// view keys are generated from secret ( so as single recovery seed is enough )
   130  	hash := crypto.Key(crypto.Keccak256(Seed[:]))
   131  	crypto.ScReduce32(&hash)
   132  	keys.Viewkey_Secret = hash
   133  	keys.Viewkey_Public = *(keys.Viewkey_Secret.PublicKey())
   134  
   135  	return
   136  }
   137  
   138  // generate user account using recovery seeds
   139  func Generate_Account_From_Recovery_Words(words string) (user *Account, err error) {
   140  	user = &Account{Mixin: 5, FeesMultiplier: 1.5}
   141  	language, seed, err := mnemonics.Words_To_Key(words)
   142  	if err != nil {
   143  		return
   144  	}
   145  
   146  	user.SeedLanguage = language
   147  	user.Keys = Generate_Keys_From_Seed(seed)
   148  
   149  	return
   150  }
   151  
   152  func Generate_Account_From_Seed(Seed crypto.Key) (user *Account, err error) {
   153  	user = &Account{Mixin: 5, FeesMultiplier: 1.5}
   154  
   155  	// TODO check whether the seed is invalid
   156  	user.Keys = Generate_Keys_From_Seed(Seed)
   157  
   158  	return
   159  }
   160  
   161  // generate keys for view only wallet
   162  func Generate_Account_View_Only(Publicspend crypto.Key, ViewSecret crypto.Key) (user *Account, err error) {
   163  
   164  	user = &Account{Mixin: 5, FeesMultiplier: 1.5}
   165  
   166  	//  TODO check whether seed is valid secret
   167  	user.Keys.Spendkey_Public = Publicspend
   168  	user.Keys.Viewkey_Secret = ViewSecret
   169  	user.Keys.Viewkey_Public = *(ViewSecret.PublicKey())
   170  	user.ViewOnly = true
   171  
   172  	return
   173  }
   174  
   175  // generate keys for view only wallet
   176  func Generate_Account_NONDeterministic_Only(Secretspend crypto.Key, ViewSecret crypto.Key) (user *Account, err error) {
   177  
   178  	user = &Account{Mixin: 5, FeesMultiplier: 1.5}
   179  
   180  	//  TODO check whether seed is valid secret
   181  	user.Keys.Spendkey_Secret = Secretspend
   182  	user.Keys.Spendkey_Public = *(Secretspend.PublicKey())
   183  	user.Keys.Viewkey_Secret = ViewSecret
   184  	user.Keys.Viewkey_Public = *(ViewSecret.PublicKey())
   185  	user.ViewOnly = true
   186  
   187  	return
   188  }
   189  
   190  // convert key to seed using language
   191  func (w *Wallet) GetSeed() (str string) {
   192  	return mnemonics.Key_To_Words(w.account.Keys.Spendkey_Secret, w.account.SeedLanguage)
   193  }
   194  
   195  // convert key to seed using language
   196  func (w *Wallet) GetSeedinLanguage(lang string) (str string) {
   197  	return mnemonics.Key_To_Words(w.account.Keys.Spendkey_Secret, lang)
   198  }
   199  
   200  // view wallet key consists of public spendkey and private view key
   201  func (w *Wallet) GetViewWalletKey() (str string) {
   202  	return fmt.Sprintf("%s%s", w.account.Keys.Spendkey_Public, w.account.Keys.Viewkey_Secret)
   203  }
   204  
   205  func (account *Account) GetAddress() (addr address.Address) {
   206  	switch globals.Config.Name {
   207  	case "testnet":
   208  		addr.Network = config.Testnet.Public_Address_Prefix //choose dETo
   209  
   210  	default:
   211  		fallthrough // assume mainnet
   212  	case "mainnet":
   213  		addr.Network = config.Mainnet.Public_Address_Prefix //choose dERo
   214  
   215  		//panic(fmt.Sprintf("Unknown Network \"%s\"", globals.Config.Name))
   216  	}
   217  
   218  	addr.SpendKey = account.Keys.Spendkey_Public
   219  	addr.ViewKey = account.Keys.Viewkey_Public
   220  
   221  	return
   222  }
   223  
   224  // convert a user account to address
   225  func (w *Wallet) GetAddress() (addr address.Address) {
   226  	return w.account.GetAddress()
   227  }
   228  
   229  // get a random integrated address
   230  func (w *Wallet) GetRandomIAddress8() (addr address.Address) {
   231  	addr = w.account.GetAddress()
   232  
   233  	if addr.Network == config.Mainnet.Public_Address_Prefix {
   234  		addr.Network = config.Mainnet.Public_Address_Prefix_Integrated
   235  	} else { // it's a testnet address
   236  		addr.Network = config.Testnet.Public_Address_Prefix_Integrated
   237  	}
   238  
   239  	// setup random 8 bytes of payment ID, it must be from non-deterministic RNG namely crypto random
   240  	addr.PaymentID = make([]byte, 8, 8)
   241  	rand.Read(addr.PaymentID[:])
   242  
   243  	return
   244  }
   245  
   246  // get a random integrated address
   247  func (w *Wallet) GetRandomIAddress32() (addr address.Address) {
   248  	addr = w.account.GetAddress()
   249  
   250  	if addr.Network == config.Mainnet.Public_Address_Prefix {
   251  		addr.Network = config.Mainnet.Public_Address_Prefix_Integrated
   252  	} else { // it's a testnet address
   253  		addr.Network = config.Testnet.Public_Address_Prefix_Integrated
   254  	}
   255  
   256  	// setup random 32 bytes of payment ID, it must be from non-deterministic RNG namely crypto random
   257  	addr.PaymentID = make([]byte, 32, 32)
   258  	rand.Read(addr.PaymentID[:])
   259  
   260  	return
   261  }
   262  
   263  // this function is used to encrypt/decrypt payment id
   264  // as the operation is symmetric XOR, is the same in both direction
   265  func EncryptDecryptPaymentID(derivation crypto.Key, tx_public crypto.Key, input []byte) (output []byte) {
   266  	// input must be exactly 8 bytes long
   267  	if len(input) != 8 {
   268  		panic("Encrypted payment ID must be exactly 8 bytes long")
   269  	}
   270  
   271  	var tmp_buf [33]byte
   272  	copy(tmp_buf[:], derivation[:]) // copy derivation key to buffer
   273  	tmp_buf[32] = ENCRYPTED_PAYMENT_ID_TAIL
   274  
   275  	// take hash
   276  	hash := crypto.Keccak256(tmp_buf[:]) // take hash of entire 33 bytes, 32 bytes derivation key, 1 byte tail
   277  
   278  	output = make([]byte, 8, 8)
   279  	for i := range input {
   280  		output[i] = input[i] ^ hash[i] // xor the bytes with the hash
   281  	}
   282  
   283  	return
   284  }
   285  
   286  // one simple function which does all the crypto to find out whether output belongs to this account
   287  // NOTE: this function only uses view key secret and Spendkey_Public
   288  // output index is the position of vout within the tx list itself
   289  func (w *Wallet) Is_Output_Ours(tx_public crypto.Key, output_index uint64, vout_key crypto.Key) bool {
   290  	derivation := crypto.KeyDerivation(&tx_public, &w.account.Keys.Viewkey_Secret)
   291  	derivation_public_key := derivation.KeyDerivation_To_PublicKey(output_index, w.account.Keys.Spendkey_Public)
   292  
   293  	return derivation_public_key == vout_key
   294  }
   295  
   296  // only for testing purposes
   297  func (acc *Account) Is_Output_Ours(tx_public crypto.Key, output_index uint64, vout_key crypto.Key) bool {
   298  	derivation := crypto.KeyDerivation(&tx_public, &acc.Keys.Viewkey_Secret)
   299  	derivation_public_key := derivation.KeyDerivation_To_PublicKey(output_index, acc.Keys.Spendkey_Public)
   300  	return derivation_public_key == vout_key
   301  }
   302  
   303  // this function does all the keyderivation required for decrypting ringct outputs, generate keyimage etc
   304  // also used when we build up a transaction for mining or sending amount
   305  func (w *Wallet) Generate_Helper_Key_Image(tx_public crypto.Key, output_index uint64) (ephermal_secret, ephermal_public, keyimage crypto.Key) {
   306  	derivation := crypto.KeyDerivation(&tx_public, &w.account.Keys.Viewkey_Secret)
   307  	ephermal_secret = derivation.KeyDerivation_To_PrivateKey(output_index, w.account.Keys.Spendkey_Secret)
   308  	ephermal_public = derivation.KeyDerivation_To_PublicKey(output_index, w.account.Keys.Spendkey_Public)
   309  	keyimage = crypto.GenerateKeyImage(ephermal_public, ephermal_secret)
   310  
   311  	return
   312  }
   313  
   314  // this function decodes ringCT encoded output amounts
   315  // this is only possible if signature is full or simple
   316  func (w *Wallet) Decode_RingCT_Output(tx_public crypto.Key, output_index uint64, pkkey crypto.Key, tuple ringct.ECdhTuple, sigtype uint64) (amount uint64, mask crypto.Key, result bool) {
   317  
   318  	derivation := crypto.KeyDerivation(&tx_public, &w.account.Keys.Viewkey_Secret)
   319  	scalar_key := derivation.KeyDerivationToScalar(output_index)
   320  
   321  	switch sigtype {
   322  	case 0: // NOT possible , miner tx outputs are not hidden
   323  		return
   324  	case 1: // ringct MG  // Both ringct outputs can be decoded using the same methods
   325  		// however, original implementation has different methods, maybe need to evaluate more
   326  		fallthrough
   327  	case 2, 4: // ringct sample
   328  
   329  		amount, mask, result = ringct.Decode_Amount(tuple, *scalar_key, pkkey)
   330  
   331  	default:
   332  		return
   333  	}
   334  
   335  	return
   336  }
   337  
   338  // add the transaction to our wallet record, so as funds can be later on tracked
   339  // due to enhanced features, we have to wait and watch for all funds
   340  // this will extract secret keys from newly arrived funds to consume them later on
   341  func (w *Wallet) Add_Transaction_Record_Funds(txdata *globals.TX_Output_Data) (amount uint64, result bool) {
   342  
   343  	// check for input maturity at every height change
   344  	w.Lock()
   345  	if w.account.Height < txdata.Height {
   346  		w.account.Height = txdata.Height
   347  		w.account.TopoHeight = txdata.TopoHeight
   348  		w.account.balance_stale = true // balance needs recalculation
   349  	}
   350  	w.Unlock()
   351  
   352  	w.Store_Height_Mapping(txdata)     // update height to block mapping, also sets wallet height
   353  	w.Add_Possible_Ring_Member(txdata) // add as ringmember for future
   354  
   355  	// if our funds have been consumed, remove it from our available list
   356  	for i := range txdata.Key_Images {
   357  		w.Is_Our_Fund_Consumed(txdata.Key_Images[i], txdata)
   358  	}
   359  
   360  	// confirm that that data belongs to this user
   361  	if !w.Is_Output_Ours(txdata.Tx_Public_Key, txdata.Index_within_tx, crypto.Key(txdata.InKey.Destination)) {
   362  		return // output is not ours
   363  	}
   364  
   365  	// since input is ours, take a lock for processing
   366  	defer w.Save_Wallet()
   367  	w.Lock()
   368  	defer w.Unlock()
   369  
   370  	var tx_wallet TX_Wallet_Data
   371  
   372  	/*
   373  		// check whether we are deduplicating, is the transaction already in our records, skip it
   374  		// transaction is already in our wallet, skip it for being duplicate
   375  		if w.check_key_exists(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), itob(txdata.Index_Global)) {
   376  			return
   377  		}
   378  	*/
   379  
   380  	// setup Amount
   381  	switch txdata.SigType {
   382  	case 0: // miner tx
   383  		tx_wallet.WAmount = txdata.Amount
   384  		tx_wallet.WKey.Mask = ringct.Identity // secret mask for miner tx is Identity
   385  
   386  	case 1, 2, 4: // ringct full/simple, simplebulletproof
   387  		tx_wallet.WAmount, tx_wallet.WKey.Mask, result = w.Decode_RingCT_Output(txdata.Tx_Public_Key, txdata.Index_within_tx, crypto.Key(txdata.InKey.Mask), txdata.ECDHTuple,
   388  			txdata.SigType)
   389  
   390  		if result == false { // It's an internal error most probably, log more details
   391  			return
   392  		}
   393  	case 3: //fullbulletproof are not supported
   394  
   395  	}
   396  
   397  	amount = tx_wallet.WAmount // setup amount so it can be properly returned
   398  	tx_wallet.TXdata = *txdata
   399  
   400  	// use payment ID if availble
   401  	// due to design we never know for which output the payment id was
   402  	// C daemon places relates it with any outputs
   403  	// so we attach the payment with all outputs
   404  	// tracking it would make wallet slow
   405  	// we cannot send to ourselves with a payment ID
   406  	// if the TX was sent by this wallet, do NOT process payment IDs,to avoid triggerring a critical bug
   407  	// we need a better FIX for this
   408  	// NOTE: this fix needs reqork before adding support for SHADOW addresses
   409  	if w.GetTXKey(txdata.TXID) == "" { // make sure TX was not sent by this wallet
   410  		if !w.check_key_exists(BLOCKCHAIN_UNIVERSE, []byte(OUR_TX_BUCKET), txdata.TXID[:]) { // if wallet is recreated, we track our TX via key-images
   411  			if txdata.Index_within_tx >= 0 {
   412  				switch len(txdata.PaymentID) {
   413  				case 8: // this needs to be decoded
   414  					derivation := crypto.KeyDerivation(&txdata.Tx_Public_Key, &w.account.Keys.Viewkey_Secret)
   415  					tx_wallet.WPaymentID = EncryptDecryptPaymentID(derivation, txdata.Tx_Public_Key, txdata.PaymentID)
   416  				case 32:
   417  					tx_wallet.WPaymentID = txdata.PaymentID
   418  				}
   419  			}
   420  		}
   421  	}
   422  
   423  	// if wallet is viewonly, we cannot track when the funds were spent
   424  	// so lets skip the part, since we do not have the keys
   425  	if !w.account.ViewOnly { // it's a full wallet, track spendable and get ready to spend
   426  		secret_key, _, kimage := w.Generate_Helper_Key_Image(txdata.Tx_Public_Key, txdata.Index_within_tx)
   427  		tx_wallet.WKimage = kimage
   428  		tx_wallet.WKey.Destination = secret_key
   429  
   430  		if w.check_key_exists(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), kimage[:]) {
   431  			// find the output index to which this key image belong
   432  			value_bytes, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), kimage[:])
   433  			if err == nil && len(value_bytes) == 8 {
   434  				index := binary.BigEndian.Uint64(value_bytes)
   435  
   436  				// now lets load the suitable index data
   437  				value_bytes, err = w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), itob(index))
   438  				if err == nil {
   439  					var tx_wallet_temp TX_Wallet_Data
   440  					err = msgpack.Unmarshal(value_bytes, &tx_wallet_temp)
   441  					if err == nil {
   442  						if tx_wallet_temp.TXdata.TXID != txdata.TXID { // transaction mismatch
   443  							rlog.Warnf("KICD  %s,%s,  \n%+v  \n%+v",txdata.TXID, tx_wallet_temp.TXdata.TXID, txdata,tx_wallet_temp);
   444  							return 0, false
   445  						}
   446  
   447  						if tx_wallet_temp.TXdata.Index_within_tx != txdata.Index_within_tx { // index within tx mismatch
   448  							rlog.Warnf("KICD2  %s,%s,  \n%+v  \n%+v",txdata.TXID, tx_wallet_temp.TXdata.TXID, txdata,tx_wallet_temp);
   449  							return 0, false
   450  						}
   451  					}
   452  				}
   453  			}
   454  
   455  		}
   456  
   457  		// store the key image so as later on we can find when it is spent
   458  		w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), kimage[:], itob(txdata.Index_Global))
   459  	}
   460  
   461  	// serialize and store the tx, make it available for funds
   462  	serialized, err := msgpack.Marshal(&tx_wallet)
   463  	if err != nil {
   464  		panic(err)
   465  	}
   466  	// store all data about the transfer
   467  	w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), itob(txdata.Index_Global), serialized)
   468  
   469  	// store TX to global output index link
   470  	w.store_key_value(BLOCKCHAIN_UNIVERSE, append([]byte(TXID), txdata.TXID[:]...), itob(txdata.Index_Global), itob(txdata.Index_Global))
   471  
   472  	// store payment ID to global output index link, but only if we have payment ID
   473  	if len(tx_wallet.WPaymentID) == 8 || len(tx_wallet.WPaymentID) == 32 {
   474  		w.store_key_value(BLOCKCHAIN_UNIVERSE, append([]byte(PAYID), tx_wallet.WPaymentID[:]...), itob(txdata.Index_Global), itob(txdata.Index_Global))
   475  	}
   476  
   477  	// mark the funds as available
   478  	w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE), itob(txdata.Index_Global), itob(txdata.Index_Global))
   479  
   480  	// TODO we must also sort transactions by payment id, so as they are searchable by payment id
   481  	w.account.balance_stale = true // balance needs recalculation, as new funds have arrived
   482  
   483  	result = true
   484  	return
   485  }
   486  
   487  // check whether our fund is consumed
   488  // this is done by finding the keyimages floating in blockchain, to what keyimages belong to this account
   489  // if match is found, we have consumed our funds
   490  // NOTE: Funds spent check should always be earlier than TX output check, so as we can handle payment IDs properly
   491  
   492  func (w *Wallet) Is_Our_Fund_Consumed(key_image crypto.Key, txdata *globals.TX_Output_Data) (amount uint64, result bool) {
   493  
   494  	// if not ours, go back
   495  	if !w.check_key_exists(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), key_image[:]) {
   496  		return 0, false
   497  	}
   498  	defer w.Save_Wallet()
   499  	w.Lock()
   500  	defer w.Unlock()
   501  
   502  	spent_index := txdata.Index_Global
   503  
   504  	// these are stored in a different bucket so we never collide with incoming funds
   505  	{
   506  		var tx_wallet TX_Wallet_Data
   507  		tx_wallet.TXdata = *txdata
   508  
   509  		// yes it is our fund, store relevant info to FUNDS bucket
   510  		serialized, err := msgpack.Marshal(&tx_wallet)
   511  		if err != nil {
   512  			panic(err)
   513  		}
   514  		// store all data
   515  		w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET_OUTGOING), itob(txdata.Index_Global), serialized)
   516  	}
   517  
   518  	// mark that this TX was sent by us
   519  	w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(OUR_TX_BUCKET), txdata.TXID[:], txdata.TXID[:])
   520  
   521  	// find the output index to which this key image belong
   522  	value_bytes, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), key_image[:])
   523  	if err != nil {
   524  		panic(fmt.Sprintf("Error while reading spent keyimage data key_image %s, err %s", key_image, err))
   525  	}
   526  	index := binary.BigEndian.Uint64(value_bytes)
   527  
   528  	// now lets load the suitable index data
   529  	value_bytes, err = w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), itob(index))
   530  	if err != nil {
   531  		panic(fmt.Sprintf("Error while reading spent funds data key_image %s, index %d err %s", key_image, index, err))
   532  	}
   533  
   534  	var tx_wallet TX_Wallet_Data
   535  	err = msgpack.Unmarshal(value_bytes, &tx_wallet)
   536  	if err != nil {
   537  		panic(fmt.Sprintf("Error while decoding spent funds data key_image %s, index %d err %s", key_image, index, err))
   538  	}
   539  
   540  	// this case should never be possible, until logical or db corruption has already occured
   541  	if key_image != tx_wallet.WKimage {
   542  		fmt.Printf("%+v\n", tx_wallet)
   543  		panic(fmt.Sprintf("Stored key_image %s and loaded key image mismatch  %s index %d err %s", key_image, tx_wallet.WKimage, index, err))
   544  	}
   545  
   546  	// move the funds from availble to spent bucket
   547  	w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE), itob(index))
   548  	w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT), itob(index), itob(index))
   549  
   550  	w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT_WHERE), itob(index), itob(spent_index))
   551  
   552  	w.account.balance_stale = true // balance needs recalculation
   553  
   554  	return tx_wallet.WAmount, true
   555  }
   556  
   557  // add the transaction to record,
   558  // this will mark the funds as consumed on the basis of  keyimages
   559  // locate the transaction  and get the amount , this is O(n), so we can tell how much funds were spent
   560  // cryptnote only allows to spend complete funds, change comes back
   561  func (w *Wallet) Consume_Transaction_Record_Funds(txdata *globals.TX_Output_Data, key_image crypto.Key) bool {
   562  	return false
   563  
   564  }
   565  
   566  // get the unlocked balance ( amounts which are mature and can be spent at this time )
   567  // offline wallets may get this wrong, since they may not have latest data
   568  // TODO: for offline wallets, we must make all balance as mature
   569  // full resync costly
   570  // TODO URGENT we are still not cleaning up, spent funds,do that asap to recover funds which were spent on alt-xhain
   571  func (w *Wallet) Get_Balance_Rescan() (mature_balance uint64, locked_balance uint64) {
   572  	w.RLock()
   573  	defer w.RUnlock()
   574  
   575  	index_list := w.load_all_values_from_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE))
   576  
   577  	//fmt.Printf("found %d elements in bucket \n", len(index_list))
   578  	for i := range index_list { // load index
   579  		index := binary.BigEndian.Uint64(index_list[i])
   580  		value_bytes, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), index_list[i])
   581  		if err != nil {
   582  			rlog.Debugf("Error while reading available funds index index %d err %s", index, err)
   583  			continue
   584  		}
   585  
   586  		var tx_wallet TX_Wallet_Data
   587  		err = msgpack.Unmarshal(value_bytes, &tx_wallet)
   588  		if err != nil {
   589  			rlog.Debugf("Error while decoding availble funds data index %d err %s", index, err)
   590  			continue
   591  		}
   592  
   593  		// check whether the height and block matches with what is stored at this point in time
   594  		local_hash, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(HEIGHT_TO_BLOCK_BUCKET), itob(uint64(tx_wallet.TXdata.TopoHeight)))
   595  		if err != nil {
   596  			continue
   597  		}
   598  
   599  		// input disappeared due to chain soft fork
   600  		if len(local_hash) == 32 && !bytes.Equal(tx_wallet.TXdata.BLID[:], local_hash) {
   601  			// stop tracking the funds everywhere
   602  			w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), index_list[i])           // delete index
   603  			w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), tx_wallet.WKimage[:]) // delete key_image for this
   604  			w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE), itob(index))
   605  			w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT), index_list[i])
   606  
   607  			w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT), index_list[i])
   608  
   609  			// delete TXID to index mapping
   610  			w.delete_key(BLOCKCHAIN_UNIVERSE, append([]byte(TXID), tx_wallet.TXdata.TXID[:]...), index_list[i])
   611  			// delete payment ID to index mapping
   612  			w.delete_key(BLOCKCHAIN_UNIVERSE, append([]byte(PAYID), tx_wallet.WPaymentID[:]...), index_list[i])
   613  
   614  			continue // skip this input
   615  		}
   616  
   617  		if inputmaturity.Is_Input_Mature(w.account.Height,
   618  			tx_wallet.TXdata.Height,
   619  			tx_wallet.TXdata.Unlock_Height,
   620  			tx_wallet.TXdata.SigType) {
   621  			mature_balance += tx_wallet.WAmount
   622  		} else {
   623  			locked_balance += tx_wallet.WAmount
   624  		}
   625  
   626  	}
   627  
   628  	w.account.Balance_Mature = mature_balance
   629  	w.account.Balance_Locked = locked_balance
   630  	w.account.balance_stale = false // balance is updated
   631  
   632  	return
   633  }
   634  
   635  // get the unlocked balance ( amounts which are mature and can be spent at this time )
   636  // offline wallets may get this wrong, since they may not have latest data
   637  
   638  //
   639  func (w *Wallet) Get_Balance() (mature_balance uint64, locked_balance uint64) {
   640  	w.RLock()
   641  	if !w.Is_Balance_Modified() {
   642  		w.RUnlock()
   643  		return w.account.Balance_Mature, w.account.Balance_Locked
   644  	}
   645  
   646  	w.RUnlock()
   647  	return w.Get_Balance_Rescan() // call heavy function
   648  }
   649  
   650  var old_block_cache crypto.Hash // used as a cache to skip decryptions is possible
   651  
   652  func (w *Wallet) Store_Height_Mapping(txdata *globals.TX_Output_Data) {
   653  
   654  	w.Lock()
   655  	defer w.Unlock()
   656  
   657  	// store height to block hash mapping
   658  	// store all data
   659  	// save block height only if required
   660  
   661  	w.account.Height = txdata.Height         // increase wallet height
   662  	w.account.TopoHeight = txdata.TopoHeight // increase wallet topo height
   663  	if old_block_cache != txdata.BLID {      // make wallet scanning as fast as possible
   664  		//fmt.Printf("gStoring height to block %d %s t %s\n", txdata.Height, txdata.BLID, txdata.TXID)
   665  		old_block, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(HEIGHT_TO_BLOCK_BUCKET), itob(uint64(txdata.TopoHeight)))
   666  		if err != nil || !bytes.Equal(old_block, txdata.BLID[:]) {
   667  			w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(HEIGHT_TO_BLOCK_BUCKET), itob(uint64(txdata.TopoHeight)), txdata.BLID[:])
   668  			old_block_cache = txdata.BLID
   669  
   670  			// fmt.Printf("Storing height to block %d %s\n", txdata.Height, txdata.BLID)
   671  		}
   672  	}
   673  
   674  }
   675  
   676  // add all random outputs which will be used while creating transactions
   677  // currently we store all ringmembers
   678  func (w *Wallet) Add_Possible_Ring_Member(txdata *globals.TX_Output_Data) {
   679  
   680  	if txdata == nil {
   681  		return
   682  	}
   683  
   684  	if w.Is_View_Only() { // view only wallets can never construct a transaction
   685  		return
   686  	}
   687  	w.Lock()
   688  	defer w.Unlock()
   689  
   690  	w.account.Index_Global = txdata.Index_Global // increment out pointer
   691  
   692  	var r Ring_Member
   693  	r.InKey = txdata.InKey
   694  	r.Height = txdata.Height
   695  	r.Index_Global = txdata.Index_Global
   696  	r.Unlock_Height = txdata.Unlock_Height // required for maturity checking
   697  	r.Sigtype = txdata.SigType             // required for maturity checking
   698  
   699  	// lets serialize and store the data encrypted, so as no one track any info
   700  	serialized, err := msgpack.Marshal(&r)
   701  	if err != nil {
   702  		panic(err)
   703  	}
   704  	// store all data
   705  	w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(RING_BUCKET), itob(txdata.Index_Global), serialized)
   706  
   707  	return
   708  }
   709  
   710  type Entry struct {
   711  	Index_Global uint64 `json:"index_global"`
   712  	Height       uint64 `json:"height"` 
   713  	TopoHeight   int64  `json:"topoheight"` 
   714  	TXID         crypto.Hash `json:"txid"` 
   715  	Amount       uint64  `json:"amount"`
   716  	PaymentID    []byte `json:"payment_id"`
   717  	Status       byte  `json:"status"`
   718   	Unlock_Time  uint64 `json:"unlock_time"`
   719  	Time         time.Time `json:"time"`
   720  	Secret_TX_Key string `json:"secret_tx_key"`  // can be used to prove if available
   721  	Details structures.Outgoing_Transfer_Details `json:"details"`  // actual details if available
   722  }
   723  
   724  	
   725  // finds all inputs which have been received/spent etc
   726  // TODO this code can be easily parallelised and need to be parallelised
   727  // if only the availble is requested, then the wallet is very fast
   728  // the spent tracking may make it slow ( in case of large probably million  txs )
   729  //TODO currently we do not track POOL at all any where ( except while building tx)
   730  // if payment_id is true, only entries with payment ids are returned
   731  func (w *Wallet) Show_Transfers(available bool, in bool, out bool, pool bool, failed bool, payment_id bool, min_height, max_height uint64) (entries []Entry) {
   732  
   733  	dero_first_block_time := time.Unix(1512432000, 0) //Tuesday, December 5, 2017 12:00:00 AM
   734  
   735  	if max_height == 0 {
   736  		max_height = 5000000000
   737  	}
   738  	if available || in {
   739  		index_list := w.load_all_values_from_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE))
   740  
   741  		for i := range index_list { // load index
   742  			current_index := binary.BigEndian.Uint64(index_list[i])
   743  
   744  			tx, err := w.load_funds_data(current_index, FUNDS_BUCKET)
   745  			if err != nil {
   746  				rlog.Warnf("Error while reading available funds index index %d err %s", current_index, err)
   747  				continue
   748  			}
   749  
   750  			if tx.TXdata.Height >= min_height && tx.TXdata.Height <= max_height { // height filter
   751  				var entry Entry
   752  				entry.Index_Global = current_index
   753  				entry.Height = tx.TXdata.Height
   754  				entry.TopoHeight = tx.TXdata.TopoHeight
   755  				entry.TXID = tx.TXdata.TXID
   756  				entry.Amount = tx.WAmount
   757  				entry.PaymentID = tx.WPaymentID
   758  				entry.Status = 0
   759  				entry.Time = time.Unix(int64(tx.TXdata.Block_Time), 0)
   760  
   761  				if entry.Height < 95600 { // make up time for pre-atlantis blocks
   762  					duration, _ := time.ParseDuration(fmt.Sprintf("%ds", int64(180*entry.Height)))
   763  					entry.Time = dero_first_block_time.Add(duration)
   764  				}
   765  
   766  				if payment_id {
   767  
   768  					if len(entry.PaymentID) >= 8 {
   769  						entries = append(entries, entry)
   770  					}
   771  				} else {
   772  					entries = append(entries, entry)
   773  				}
   774  
   775  			}
   776  
   777  		}
   778  	}
   779  
   780  	if in || out {
   781  		// all spent funds will have 2 entries, one for receive/other for spent
   782  		index_list := w.load_all_values_from_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT))
   783  
   784  		for i := range index_list { // load index
   785  			current_index := binary.BigEndian.Uint64(index_list[i])
   786  
   787  			tx, err := w.load_funds_data(current_index, FUNDS_BUCKET)
   788  			if err != nil {
   789  				rlog.Warnf("Error while reading available funds index index %d err %s", current_index, err)
   790  				continue
   791  			}
   792  
   793  			/// receipt entry
   794  			var entry Entry
   795  			entry.Index_Global = current_index
   796  			entry.Height = tx.TXdata.Height
   797  			entry.TopoHeight = tx.TXdata.TopoHeight
   798  			entry.TXID = tx.TXdata.TXID
   799  			entry.Amount = tx.WAmount
   800  			entry.PaymentID = tx.WPaymentID
   801  			entry.Status = 0
   802  			entry.Time = time.Unix(int64(tx.TXdata.Block_Time), 0)
   803  
   804  			if entry.Height < 95600 { // make up time for pre-atlantis blocks
   805  				duration, _ := time.ParseDuration(fmt.Sprintf("%ds", int64(180*entry.Height)))
   806  				entry.Time = dero_first_block_time.Add(duration)
   807  			}
   808  
   809  			if in {
   810  				if tx.TXdata.Height >= min_height && tx.TXdata.Height <= max_height { // height filter
   811  					if payment_id {
   812  
   813  						if len(entry.PaymentID) >= 8 {
   814  							entries = append(entries, entry)
   815  						}
   816  					} else {
   817  						entries = append(entries, entry)
   818  					}
   819  				}
   820  
   821  			}
   822  
   823  			if out {
   824  				// spendy entry
   825  				value_bytes, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT_WHERE), index_list[i])
   826  				if err != nil {
   827  					fmt.Printf("Error while reading FUNDS_SPENT_WHERE index %d err %s", current_index, err)
   828  					continue
   829  				}
   830  				spent_index := binary.BigEndian.Uint64(value_bytes)
   831  				tx, err = w.load_funds_data(spent_index, FUNDS_BUCKET_OUTGOING)
   832  				if err != nil {
   833  					rlog.Warnf("Error while reading available funds index index %d err %s", current_index, err)
   834  					continue
   835  				}
   836  				if tx.TXdata.Height >= min_height && tx.TXdata.Height <= max_height { // height filter
   837  					entry.Index_Global = tx.TXdata.Index_Global
   838  					entry.Height = tx.TXdata.Height
   839  					entry.TXID = tx.TXdata.TXID
   840  					entry.TopoHeight = tx.TXdata.TopoHeight
   841  					entry.PaymentID = entry.PaymentID[:0] // payment id needs to be zero or tracked from some where else
   842  					entry.Time = time.Unix(int64(tx.TXdata.Block_Time), 0)
   843  
   844  					if entry.Height < 95600 { // make up time for pre-atlantis blocks
   845  						duration, _ := time.ParseDuration(fmt.Sprintf("%ds", int64(180*entry.Height)))
   846  						entry.Time = dero_first_block_time.Add(duration)
   847  					}
   848  					
   849  					// fill tx secret_key 
   850  					entry.Secret_TX_Key =  w.GetTXKey(tx.TXdata.TXID)
   851                                          entry.Details = w.GetTXOutDetails(tx.TXdata.TXID)
   852  
   853  					entry.Status = 1
   854  					entries = append(entries, entry) // spend entry
   855  				}
   856  			}
   857  
   858  		}
   859  	}
   860  
   861  	sort.SliceStable(entries, func(i, j int) bool { return entries[i].Index_Global > entries[j].Index_Global })
   862  
   863  	return
   864  
   865  }
   866  
   867  // w.delete_key(BLOCKCHAIN_UNIVERSE, append([]byte(TXID),tx_wallet.TXdata.TXID[:]...) , index_list[i])
   868  
   869  /* gets all the payments  done to specific payment ID and filtered by specific block height */
   870  func (w *Wallet) Get_Payments_Payment_ID(payid []byte, min_height uint64) (entries []Entry) {
   871  	index_list := w.load_all_values_from_bucket(BLOCKCHAIN_UNIVERSE, append([]byte(PAYID), payid[:]...))
   872  
   873  	for i := range index_list {
   874  		current_index := binary.BigEndian.Uint64(index_list[i])
   875  
   876  		tx, err := w.load_funds_data(current_index, FUNDS_BUCKET)
   877  		if err != nil {
   878  			rlog.Warnf("Error while reading available funds index index %d err %s", current_index, err)
   879  			continue
   880  		}
   881  
   882  		if tx.TXdata.Height > min_height { // height filter
   883  			var entry Entry
   884  			entry.Index_Global = current_index
   885  			entry.TopoHeight = tx.TXdata.TopoHeight
   886  			entry.Height = tx.TXdata.Height
   887  			entry.TXID = tx.TXdata.TXID
   888  			entry.Amount = tx.WAmount
   889  			entry.PaymentID = tx.WPaymentID
   890  			entry.Status = 0
   891  			entry.Unlock_Time = tx.TXdata.Unlock_Height
   892  			
   893  			// fill tx secret_key 
   894                          entry.Secret_TX_Key =  w.GetTXKey(tx.TXdata.TXID)
   895                          entry.Details = w.GetTXOutDetails(tx.TXdata.TXID)
   896  			entries = append(entries, entry)
   897  		}
   898  	}
   899  
   900  	sort.SliceStable(entries, func(i, j int) bool { return entries[i].Index_Global > entries[j].Index_Global })
   901  
   902  	return
   903  
   904  }
   905  
   906  // return all payments within a tx there can be more than 1 entry, if yes then they will be merged
   907  // NOTE:
   908  func (w *Wallet) Get_Payments_TXID(txid []byte) (entry Entry) {
   909  	index_list := w.load_all_values_from_bucket(BLOCKCHAIN_UNIVERSE, append([]byte(TXID), txid[:]...))
   910  
   911  	for i := range index_list {
   912  		current_index := binary.BigEndian.Uint64(index_list[i])
   913  
   914  		tx, err := w.load_funds_data(current_index, FUNDS_BUCKET)
   915  		if err != nil {
   916  			rlog.Warnf("Error while reading available funds index index %d err %s", current_index, err)
   917  			continue
   918  		}
   919  		if bytes.Compare(txid, tx.TXdata.TXID[:]) == 0 {
   920  
   921  			entry.Index_Global = current_index
   922  			entry.Height = tx.TXdata.Height
   923  			entry.TopoHeight = tx.TXdata.TopoHeight
   924  			entry.TXID = tx.TXdata.TXID
   925  			entry.Amount += tx.WAmount // merge all amounts ( if it was provided in different outputs)
   926  			entry.PaymentID = tx.WPaymentID
   927  			entry.Status = 0
   928  			entry.Unlock_Time = tx.TXdata.Unlock_Height
   929  			
   930  			// fill tx secret_key 
   931                          entry.Secret_TX_Key =  w.GetTXKey(tx.TXdata.TXID)
   932                          entry.Details = w.GetTXOutDetails(tx.TXdata.TXID)
   933  		}
   934  	}
   935  
   936  	return
   937  
   938  }
   939  
   940  // get the unlocked balance ( amounts which are mature and can be spent at this time )
   941  // offline wallets may get this wrong, since they may not have latest data
   942  // TODO: for offline wallets, we must make all balance as mature
   943  //
   944  func (w *Wallet) Start_RPC_Server(address string) (err error) {
   945  	w.Lock()
   946  	defer w.Unlock()
   947  
   948  	tcpAddr, err := net.ResolveTCPAddr("tcp", address)
   949  	if err != nil {
   950  		return
   951  	}
   952  
   953  	w.rpcserver, err = RPCServer_Start(w, tcpAddr.String())
   954  	if err != nil {
   955  		w.rpcserver = nil
   956  	}
   957  
   958  	return
   959  }
   960  
   961  func (w *Wallet) Stop_RPC_Server() {
   962  	w.Lock()
   963  	defer w.Unlock()
   964  
   965  	if w.rpcserver != nil {
   966  		w.rpcserver.RPCServer_Stop()
   967  		w.rpcserver = nil // remover reference
   968  	}
   969  
   970  	return
   971  }
   972  
   973  // delete most of the data and prepare for rescan
   974  func (w *Wallet) Clean() {
   975  	w.Lock()
   976  	defer w.Get_Balance_Rescan()
   977  	defer w.Unlock()
   978  
   979  	w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET))
   980  	w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE))
   981  	w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT))
   982  	w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT_WHERE))
   983  	w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET_OUTGOING))
   984  
   985  	w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(RING_BUCKET)) //Improves wallet rescan performance.
   986  	w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET))
   987  	w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(HEIGHT_TO_BLOCK_BUCKET))
   988  
   989  }
   990  
   991  // whether account is view only
   992  func (w *Wallet) Is_View_Only() bool {
   993  	// w.RLock()
   994  	//defer w.RUnlock()
   995  	return w.account.ViewOnly
   996  }
   997  
   998  // informs thats balance information may be stale and needs recalculation
   999  func (w *Wallet) Is_Balance_Modified() bool {
  1000  	// w.RLock()
  1001  	// defer w.RUnlock()
  1002  	return w.account.balance_stale
  1003  }
  1004  
  1005  // return height of wallet
  1006  func (w *Wallet) Get_Height() uint64 {
  1007  	w.RLock()
  1008  	defer w.RUnlock()
  1009  	return w.account.Height
  1010  }
  1011  
  1012  // return topoheight of wallet
  1013  func (w *Wallet) Get_TopoHeight() int64 {
  1014  	w.RLock()
  1015  	defer w.RUnlock()
  1016  	return w.account.TopoHeight
  1017  }
  1018  
  1019  func (w *Wallet) Get_Daemon_Height() uint64 {
  1020  	w.Lock()
  1021  	defer w.Unlock()
  1022  
  1023  	return w.Daemon_Height
  1024  }
  1025  
  1026  // return index position
  1027  func (w *Wallet) Get_Index_Global() uint64 {
  1028  	w.RLock()
  1029  	defer w.RUnlock()
  1030  	return w.account.Index_Global
  1031  }
  1032  
  1033  func (w *Wallet) Get_Keys() _Keys {
  1034  	return w.account.Keys
  1035  }
  1036  
  1037  // by default a wallet opens in Offline Mode
  1038  // however, if the wallet is in online mode, it can be made offline instantly using this
  1039  func (w *Wallet) SetOfflineMode() bool {
  1040  	w.Lock()
  1041  	defer w.Unlock()
  1042  
  1043  	current_mode := w.wallet_online_mode
  1044  	w.wallet_online_mode = false
  1045  	return current_mode
  1046  }
  1047  
  1048  // return current mode
  1049  func (w *Wallet) GetMode() bool {
  1050  	w.RLock()
  1051  	defer w.RUnlock()
  1052  
  1053  	return w.wallet_online_mode
  1054  }
  1055  
  1056  // use the endpoint set  by the program
  1057  func (w *Wallet) SetDaemonAddress(endpoint string) string {
  1058  	w.Lock()
  1059  	defer w.Unlock()
  1060  
  1061  	w.Daemon_Endpoint = endpoint
  1062  	return w.Daemon_Endpoint
  1063  }
  1064  
  1065  // by default a wallet opens in Offline Mode
  1066  // however, It can be made online by calling this
  1067  func (w *Wallet) SetOnlineMode() bool {
  1068  	w.Lock()
  1069  	defer w.Unlock()
  1070  
  1071  	current_mode := w.wallet_online_mode
  1072  	w.wallet_online_mode = true
  1073  
  1074  	if current_mode != true { // trigger subroutine if previous mode was offline
  1075  		go w.sync_loop() // start sync subroutine
  1076  	}
  1077  	return current_mode
  1078  }
  1079  
  1080  // by default a wallet opens in Offline Mode
  1081  // however, It can be made online by calling this
  1082  func (w *Wallet) SetMixin(Mixin int) int {
  1083  	defer w.Save_Wallet() // save wallet
  1084  	w.Lock()
  1085  	defer w.Unlock()
  1086  
  1087  	if Mixin >= 5 && Mixin < 14 { //reasonable limits for mixin, atleastt for now, network should bump it to 13 on next HF
  1088  		w.account.Mixin = Mixin
  1089  	}
  1090  	return w.account.Mixin
  1091  }
  1092  
  1093  // by default a wallet opens in Offline Mode
  1094  // however, It can be made online by calling this
  1095  func (w *Wallet) GetMixin() int {
  1096  	w.Lock()
  1097  	defer w.Unlock()
  1098  	if w.account.Mixin < 5 {
  1099  		return 5
  1100  	}
  1101  	return w.account.Mixin
  1102  }
  1103  
  1104  // sets a fee multiplier
  1105  func (w *Wallet) SetFeeMultiplier(x float32) float32 {
  1106  	defer w.Save_Wallet() // save wallet
  1107  	w.Lock()
  1108  	defer w.Unlock()
  1109  	if x < 1.0 { // fee cannot be less than 1.0, base fees
  1110  		w.account.FeesMultiplier = 2.0
  1111  	} else {
  1112  		w.account.FeesMultiplier = x
  1113  	}
  1114  	return w.account.FeesMultiplier
  1115  }
  1116  
  1117  // gets current fee multiplier
  1118  func (w *Wallet) GetFeeMultiplier() float32 {
  1119  	w.Lock()
  1120  	defer w.Unlock()
  1121  	if w.account.FeesMultiplier < 1.0 {
  1122  		return 1.0
  1123  	}
  1124  	return w.account.FeesMultiplier
  1125  }
  1126  
  1127  // get fees multiplied by multiplier
  1128  func (w *Wallet) getfees(txfee uint64) uint64 {
  1129  	multiplier := w.account.FeesMultiplier
  1130  	if multiplier < 1.0 {
  1131  		multiplier = 2.0
  1132  	}
  1133  	return txfee * uint64(multiplier*100.0) / 100
  1134  }
  1135  
  1136  // Ability to change seed lanaguage
  1137  func (w *Wallet) SetSeedLanguage(language string) string {
  1138  	defer w.Save_Wallet() // save wallet
  1139  	w.Lock()
  1140  	defer w.Unlock()
  1141  
  1142  	language_list := mnemonics.Language_List()
  1143  	for i := range language_list {
  1144  		if strings.ToLower(language) == strings.ToLower(language_list[i]) {
  1145  			w.account.SeedLanguage = language_list[i]
  1146  		}
  1147  	}
  1148  	return w.account.SeedLanguage
  1149  }
  1150  
  1151  // retrieve current seed language
  1152  func (w *Wallet) GetSeedLanguage() string {
  1153  	w.Lock()
  1154  	defer w.Unlock()
  1155  	if w.account.SeedLanguage == "" { // default is English
  1156  		return "English"
  1157  	}
  1158  	return w.account.SeedLanguage
  1159  }
  1160  
  1161  // retrieve  secret key for any tx we may have created
  1162  func (w *Wallet) GetTXKey(txhash crypto.Hash) string {
  1163  
  1164  	key, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(SECRET_KEY_BUCKET), txhash[:])
  1165  	if err != nil {
  1166  		return ""
  1167  	}
  1168  
  1169  	return fmt.Sprintf("%x", key)
  1170  }
  1171  
  1172  // we need better names for functions
  1173  func (w *Wallet) GetTXOutDetails(txhash crypto.Hash) (details structures.Outgoing_Transfer_Details) {
  1174  
  1175  	data_bytes, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(TX_OUT_DETAILS_BUCKET), txhash[:])
  1176  	if err != nil {
  1177  		return
  1178  	}
  1179  
  1180  	if len(data_bytes) > 10 {
  1181  		json.Unmarshal(data_bytes, &details)
  1182  	}
  1183  
  1184  	return
  1185  }