829 lines
26 KiB
Go
829 lines
26 KiB
Go
// Copyright 2017-2021 DERO Project. All rights reserved.
|
|
// Use of this source code in any form is governed by RESEARCH license.
|
|
// license can be found in the LICENSE file.
|
|
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
|
|
//
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package walletapi
|
|
|
|
// this file needs serious improvements but have extremely limited time
|
|
/* this file handles communication with the daemon
|
|
* this includes receiving output information
|
|
*
|
|
* *
|
|
*/
|
|
//import "io"
|
|
//import "os"
|
|
import "fmt"
|
|
import "time"
|
|
import "sync"
|
|
import "bytes"
|
|
import "math/big"
|
|
|
|
//import "bufio"
|
|
import "strings"
|
|
import "context"
|
|
|
|
//import "runtime"
|
|
//import "compress/gzip"
|
|
import "encoding/hex"
|
|
import "encoding/binary"
|
|
|
|
import "runtime/debug"
|
|
|
|
import "github.com/romana/rlog"
|
|
|
|
//import "github.com/vmihailenco/msgpack"
|
|
|
|
//import "github.com/gorilla/websocket"
|
|
//import "github.com/mafredri/cdp/rpcc"
|
|
|
|
import "github.com/deroproject/derohe/block"
|
|
import "github.com/deroproject/derohe/address"
|
|
import "github.com/deroproject/derohe/crypto"
|
|
import "github.com/deroproject/derohe/errormsg"
|
|
import "github.com/deroproject/derohe/structures"
|
|
import "github.com/deroproject/derohe/transaction"
|
|
import "github.com/deroproject/derohe/crypto/bn256"
|
|
|
|
import "github.com/creachadair/jrpc2"
|
|
|
|
// this global variable should be within wallet structure
|
|
var Connected bool = false
|
|
|
|
// there should be no global variables, so multiple wallets can run at the same time with different assset
|
|
|
|
var endpoint string
|
|
|
|
var output_lock sync.Mutex
|
|
|
|
var NotifyNewBlock *sync.Cond = sync.NewCond(&sync.Mutex{})
|
|
var NotifyHeightChange *sync.Cond = sync.NewCond(&sync.Mutex{})
|
|
|
|
// this function will wait n goroutines to wait for new block
|
|
func WaitNewBlock() {
|
|
NotifyNewBlock.L.Lock()
|
|
NotifyNewBlock.Wait()
|
|
NotifyNewBlock.L.Unlock()
|
|
}
|
|
|
|
// this function will wait n goroutines to wait till height changes
|
|
func WaitNewHeightBlock() {
|
|
NotifyHeightChange.L.Lock()
|
|
NotifyHeightChange.Wait()
|
|
NotifyHeightChange.L.Unlock()
|
|
}
|
|
|
|
func Notify_broadcaster(req *jrpc2.Request) {
|
|
|
|
timer.Reset(timeout) // connection is alive
|
|
switch req.Method() {
|
|
|
|
case "Repoll":
|
|
NotifyNewBlock.L.Lock()
|
|
NotifyNewBlock.Broadcast()
|
|
NotifyNewBlock.L.Unlock()
|
|
case "HRepoll":
|
|
NotifyHeightChange.L.Lock()
|
|
NotifyHeightChange.Broadcast()
|
|
NotifyHeightChange.L.Unlock()
|
|
default:
|
|
rlog.Debugf("Notification received %s\n", req.Method())
|
|
}
|
|
|
|
}
|
|
|
|
// triggers syncing with wallet every 5 seconds
|
|
func (w *Wallet_Memory) sync_loop() {
|
|
w.Sync_Wallet_Memory_With_Daemon() // sync with the daemon
|
|
}
|
|
|
|
func (cli *Client) Call(method string, params interface{}, result interface{}) error {
|
|
return cli.RPC.CallResult(context.Background(), method, params, result)
|
|
}
|
|
|
|
// returns whether wallet was online some time ago
|
|
func (w *Wallet_Memory) IsDaemonOnlineCached() bool {
|
|
return Connected
|
|
}
|
|
|
|
// currently process url with compatibility for older ip address
|
|
func buildurl(endpoint string) string {
|
|
if strings.IndexAny(endpoint, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") >= 0 { // url is already complete
|
|
return strings.TrimSuffix(endpoint, "/")
|
|
} else {
|
|
return "http://" + endpoint
|
|
}
|
|
}
|
|
|
|
// this is as simple as it gets
|
|
// single threaded communication to get the daemon status and height
|
|
// this will tell whether the wallet can connection successfully to daemon or not
|
|
func IsDaemonOnline() bool {
|
|
if rpc_client.WS == nil || rpc_client.RPC == nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// sync the wallet with daemon, this is instantaneous and can be done with a single call
|
|
// we have now the apis to avoid polling
|
|
func (w *Wallet_Memory) Sync_Wallet_Memory_With_Daemon() {
|
|
first_time := true
|
|
for {
|
|
select {
|
|
case <-w.quit:
|
|
break
|
|
default:
|
|
|
|
}
|
|
|
|
if (first_time && IsDaemonOnline()) || (!first_time && IsDaemonOnline()) {
|
|
first_time = false
|
|
w.random_ring_members()
|
|
rlog.Debugf("wallet topo height %d daemon online topo height %d\n", w.account.TopoHeight, w.Daemon_TopoHeight)
|
|
previous := w.account.Balance_Result.Data
|
|
if _, err := w.GetEncryptedBalance("", w.GetAddress().String()); err == nil {
|
|
if w.account.Balance_Result.Data != previous || (len(w.account.Entries) >= 1 && strings.ToLower(w.account.Balance_Result.Data) != strings.ToLower(w.account.Entries[len(w.account.Entries)-1].EWData)) {
|
|
w.DecodeEncryptedBalance() // try to decode balance
|
|
w.SyncHistory() // also update statement
|
|
}
|
|
} else {
|
|
rlog.Infof("getbalance err %s", err)
|
|
}
|
|
}
|
|
time.Sleep(timeout) // wait 5 seconds
|
|
|
|
}
|
|
return
|
|
}
|
|
|
|
// this is as simple as it gets
|
|
// single threaded communication to relay TX to daemon
|
|
// if this is successful, then daemon is in control
|
|
|
|
func (w *Wallet_Memory) SendTransaction(tx *transaction.Transaction) (err error) {
|
|
|
|
if tx == nil {
|
|
return fmt.Errorf("Can not send nil transaction")
|
|
}
|
|
|
|
if !IsDaemonOnline() {
|
|
return fmt.Errorf("offline or not connected. cannot send transaction.")
|
|
}
|
|
|
|
params := structures.SendRawTransaction_Params{Tx_as_hex: hex.EncodeToString(tx.Serialize())}
|
|
var result structures.SendRawTransaction_Result
|
|
|
|
// Issue a call with a response.
|
|
if err := rpc_client.Call("DERO.SendRawTransaction", params, &result); err != nil {
|
|
return err
|
|
}
|
|
|
|
//fmt.Printf("raw transaction result %+v\n", result)
|
|
|
|
if result.Status == "OK" {
|
|
return nil
|
|
} else {
|
|
err = fmt.Errorf("Err %s", result.Status)
|
|
}
|
|
|
|
//fmt.Printf("err in response %+v", result)
|
|
|
|
return
|
|
}
|
|
|
|
// this is as simple as it gets
|
|
// single threaded communication gets whether the the key image is spent in pool or in blockchain
|
|
// this can leak informtion which keyimage belongs to us
|
|
// TODO in order to stop privacy leaks we must guess this information somehow on client side itself
|
|
// maybe the server can broadcast a bloomfilter or something else from the mempool keyimages
|
|
//
|
|
func (w *Wallet_Memory) GetEncryptedBalance(treehash string, accountaddr string) (e *crypto.ElGamal, err error) {
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
rlog.Warnf("Stack trace \n%s", debug.Stack())
|
|
|
|
}
|
|
}()
|
|
|
|
if !w.GetMode() { // if wallet is in offline mode , we cannot do anything
|
|
err = fmt.Errorf("wallet is in offline mode")
|
|
return
|
|
}
|
|
|
|
if !IsDaemonOnline() {
|
|
err = fmt.Errorf("offline or not connected")
|
|
return
|
|
}
|
|
|
|
//var params structures.GetEncryptedBalance_Params
|
|
var result structures.GetEncryptedBalance_Result
|
|
|
|
// Issue a call with a response.
|
|
if err = rpc_client.Call("DERO.GetEncryptedBalance", structures.GetEncryptedBalance_Params{Address: accountaddr, TopoHeight: -1}, &result); err != nil {
|
|
|
|
rlog.Warnf("GetEncryptedBalance err %s", err)
|
|
|
|
if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(errormsg.ErrAccountUnregistered.Error())) && accountaddr == w.GetAddress().String() {
|
|
w.Error = errormsg.ErrAccountUnregistered
|
|
}
|
|
return
|
|
}
|
|
|
|
//fmt.Printf("result %+v\n", result)
|
|
w.Daemon_Height = uint64(result.DHeight)
|
|
w.Daemon_TopoHeight = result.DTopoheight
|
|
w.Merkle_Balance_TreeHash = result.DMerkle_Balance_TreeHash
|
|
|
|
if accountaddr == w.GetAddress().String() {
|
|
w.account.Balance_Result = result
|
|
w.account.TopoHeight = result.Topoheight
|
|
}
|
|
|
|
//fmt.Printf("status '%s' err '%s' %+v %+v \n", result.Status , w.Error , result.Status == errormsg.ErrAccountUnregistered.Error() , accountaddr == w.account.GetAddress().String())
|
|
|
|
if result.Status != "OK" {
|
|
err = fmt.Errorf("%s", result.Status)
|
|
return
|
|
}
|
|
|
|
hexdecoded, err := hex.DecodeString(result.Data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if accountaddr == w.GetAddress().String() {
|
|
w.Error = nil
|
|
}
|
|
|
|
el := new(crypto.ElGamal).Deserialize(hexdecoded)
|
|
return el, nil
|
|
}
|
|
|
|
func (w *Wallet_Memory) DecodeEncryptedBalance() (err error) {
|
|
|
|
var el crypto.ElGamal
|
|
var balance_point bn256.G1
|
|
|
|
hexdecoded, err := hex.DecodeString(w.account.Balance_Result.Data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
el = *el.Deserialize(hexdecoded)
|
|
if err != nil {
|
|
panic(err)
|
|
return
|
|
}
|
|
|
|
balance_point.Add(el.Left, new(bn256.G1).Neg(new(bn256.G1).ScalarMult(el.Right, w.account.Keys.Secret.BigInt())))
|
|
|
|
w.account.Balance_Mature = Balance_lookup_table.Lookup(&balance_point, w.account.Balance_Mature)
|
|
|
|
return nil
|
|
}
|
|
|
|
// this is as simple as it gets
|
|
// single threaded communication gets whether the the key image is spent in pool or in blockchain
|
|
// this can leak informtion which keyimage belongs to us
|
|
// TODO in order to stop privacy leaks we must guess this information somehow on client side itself
|
|
// maybe the server can broadcast a bloomfilter or something else from the mempool keyimages
|
|
//
|
|
func (w *Wallet_Memory) GetEncryptedBalanceAtTopoHeight(topoheight int64, accountaddr string) (e *crypto.ElGamal, err error) {
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
rlog.Warnf("Stack trace \n%s", debug.Stack())
|
|
|
|
}
|
|
}()
|
|
|
|
if !w.GetMode() { // if wallet is in offline mode , we cannot do anything
|
|
err = fmt.Errorf("wallet is in offline mode")
|
|
return
|
|
}
|
|
|
|
if !IsDaemonOnline() {
|
|
err = fmt.Errorf("offline or not connected")
|
|
return
|
|
}
|
|
|
|
//var params structures.GetEncryptedBalance_Params
|
|
var result structures.GetEncryptedBalance_Result
|
|
|
|
// Issue a call with a response.
|
|
if err = rpc_client.Call("DERO.GetEncryptedBalance", structures.GetEncryptedBalance_Params{Address: accountaddr, TopoHeight: topoheight}, &result); err != nil {
|
|
rlog.Errorf("Call failed: %v", err)
|
|
return
|
|
}
|
|
|
|
// fmt.Printf("encrypted_balance %+v\n", result)
|
|
/*
|
|
|
|
response, err := rpcClient.CallNamed("getencryptedbalance", map[string]interface{}{"address": accountaddr, "treehash":treehash,})
|
|
if err != nil {
|
|
rlog.Errorf("getencryptedbalance call Failed err %s", err)
|
|
return
|
|
}
|
|
|
|
|
|
// parse response
|
|
if response.Error != nil {
|
|
rlog.Errorf("getencryptedbalance Failed err %s", response.Error)
|
|
return
|
|
}
|
|
|
|
err = response.GetObject(&result)
|
|
if err != nil {
|
|
return // err
|
|
}
|
|
*/
|
|
if result.Status == errormsg.ErrAccountUnregistered.Error() && accountaddr == w.GetAddress().String() {
|
|
w.Error = errormsg.ErrAccountUnregistered
|
|
}
|
|
|
|
// fmt.Printf("status '%s' err '%s' %+v %+v \n", result.Status , w.Error , result.Status == errormsg.ErrAccountUnregistered.Error() , accountaddr == w.account.GetAddress().String())
|
|
|
|
if result.Status == errormsg.ErrAccountUnregistered.Error() {
|
|
err = fmt.Errorf("%s", result.Status)
|
|
return
|
|
}
|
|
|
|
if result.Status != "OK" {
|
|
err = fmt.Errorf("%s", result.Status)
|
|
return
|
|
}
|
|
|
|
hexdecoded, err := hex.DecodeString(result.Data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if accountaddr == w.GetAddress().String() {
|
|
w.Error = nil
|
|
}
|
|
|
|
el := new(crypto.ElGamal).Deserialize(hexdecoded)
|
|
return el, nil
|
|
}
|
|
|
|
func (w *Wallet_Memory) DecodeEncryptedBalance_Memory(el *crypto.ElGamal, hint uint64) (balance uint64) {
|
|
|
|
var balance_point bn256.G1
|
|
|
|
balance_point.Add(el.Left, new(bn256.G1).Neg(new(bn256.G1).ScalarMult(el.Right, w.account.Keys.Secret.BigInt())))
|
|
|
|
return Balance_lookup_table.Lookup(&balance_point, hint)
|
|
}
|
|
|
|
func (w *Wallet_Memory) GetDecryptedBalanceAtTopoHeight(topoheight int64, accountaddr string) (balance uint64, err error) {
|
|
encrypted_balance, err := w.GetEncryptedBalanceAtTopoHeight(topoheight, accountaddr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return w.DecodeEncryptedBalance_Memory(encrypted_balance, 0), nil
|
|
}
|
|
|
|
// sync history of wallet from blockchain
|
|
func (w *Wallet_Memory) random_ring_members() {
|
|
|
|
//fmt.Printf("getting random_ring_members\n")
|
|
|
|
if len(w.account.RingMembers) > 300 { // unregistered so skip
|
|
return
|
|
}
|
|
|
|
var result structures.GetRandomAddress_Result
|
|
|
|
// Issue a call with a response.
|
|
if err := rpc_client.Call("DERO.GetRandomAddress", nil, &result); err != nil {
|
|
rlog.Errorf("GetRandomAddress Call failed: %v", err)
|
|
return
|
|
}
|
|
//fmt.Printf("ring members %+v\n", result)
|
|
|
|
// we have found a matching block hash, start syncing from here
|
|
if w.account.RingMembers == nil {
|
|
w.account.RingMembers = map[string]int64{}
|
|
}
|
|
|
|
for _, k := range result.Address {
|
|
if k != w.GetAddress().String() {
|
|
w.account.RingMembers[k] = 1
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// sync history of wallet from blockchain
|
|
func (w *Wallet_Memory) SyncHistory() (balance uint64) {
|
|
if w.account.Balance_Result.Registration < 0 { // unregistered so skip
|
|
return
|
|
}
|
|
|
|
last_topo_height := int64(-1)
|
|
|
|
//fmt.Printf("finding sync point ( Registration point %d)\n", w.account.Balance_Result.Registration)
|
|
|
|
// we need to find a sync point, to minimize traffic
|
|
for i := len(w.account.Entries) - 1; i >= 0; {
|
|
|
|
// below condition will trigger if chain got pruned on server
|
|
if w.account.Balance_Result.Registration >= w.account.Entries[i].TopoHeight { // keep old history if chain got pruned
|
|
break
|
|
}
|
|
if last_topo_height == w.account.Entries[i].TopoHeight {
|
|
i--
|
|
} else {
|
|
|
|
last_topo_height = w.account.Entries[i].TopoHeight
|
|
|
|
var result structures.GetBlockHeaderByHeight_Result
|
|
|
|
// Issue a call with a response.
|
|
if err := rpc_client.Call("DERO.GetBlockHeaderByTopoHeight", structures.GetBlockHeaderByTopoHeight_Params{TopoHeight: uint64(w.account.Entries[i].TopoHeight)}, &result); err != nil {
|
|
rlog.Errorf("GetBlockHeaderByTopoHeight Call failed: %v", err)
|
|
return 0
|
|
}
|
|
|
|
if i >= 1 && last_topo_height == w.account.Entries[i-1].TopoHeight { // skipping any entries withing same block
|
|
for ; i >= 1; i-- {
|
|
if last_topo_height != w.account.Entries[i-1].TopoHeight {
|
|
w.account.Entries = w.account.Entries[:i]
|
|
}
|
|
}
|
|
}
|
|
|
|
if i == 0 {
|
|
w.account.Entries = w.account.Entries[:0] // discard all entries
|
|
break
|
|
}
|
|
|
|
// we have found a matching block hash, start syncing from here
|
|
if result.Status == "OK" && result.Block_Header.Hash == w.account.Entries[i].BlockHash {
|
|
w.synchistory_internal(w.account.Entries[i].TopoHeight+1, w.account.Balance_Result.Topoheight)
|
|
return
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//fmt.Printf("syncing loop using Registration %d\n", w.account.Balance_Result.Registration)
|
|
|
|
// if we reached here, means we should sync from scratch
|
|
w.synchistory_internal(w.account.Balance_Result.Registration, w.account.Balance_Result.Topoheight)
|
|
|
|
//if w.account.Registration >= 0 {
|
|
// err :=
|
|
// err = w.synchistory_internal(w.account.Registration,6)
|
|
|
|
// }
|
|
// fmt.Printf("syncing err %s\n",err)
|
|
// fmt.Printf("entries %+v\n", w.account.Entries)
|
|
|
|
return 0
|
|
}
|
|
|
|
// sync history
|
|
func (w *Wallet_Memory) synchistory_internal(start_topo, end_topo int64) error {
|
|
|
|
var err error
|
|
var start_balance_e *crypto.ElGamal
|
|
if start_topo == w.account.Balance_Result.Registration {
|
|
start_balance_e = crypto.ConstructElGamal(w.account.Keys.Public.G1(), crypto.ElGamal_BASE_G)
|
|
} else {
|
|
start_balance_e, err = w.GetEncryptedBalanceAtTopoHeight(start_topo, w.GetAddress().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
end_balance_e, err := w.GetEncryptedBalanceAtTopoHeight(end_topo, w.GetAddress().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return w.synchistory_internal_binary_search(start_topo, start_balance_e, end_topo, end_balance_e)
|
|
|
|
}
|
|
|
|
func (w *Wallet_Memory) synchistory_internal_binary_search(start_topo int64, start_balance_e *crypto.ElGamal, end_topo int64, end_balance_e *crypto.ElGamal) error {
|
|
|
|
//fmt.Printf("end %d start %d\n", end_topo, start_topo)
|
|
|
|
if end_topo < 0 {
|
|
return fmt.Errorf("done")
|
|
}
|
|
|
|
/* if bytes.Compare(start_balance_e.Serialize(), end_balance_e.Serialize()) == 0 {
|
|
return nil
|
|
}
|
|
*/
|
|
|
|
//for start_topo <= end_topo{
|
|
{
|
|
median := (start_topo + end_topo) / 2
|
|
|
|
//fmt.Printf("low %d high %d median %d\n", start_topo,end_topo,median)
|
|
|
|
if start_topo == median {
|
|
//fmt.Printf("syncing block %d\n", start_topo)
|
|
err := w.synchistory_block(start_topo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if end_topo-start_topo <= 1 {
|
|
return w.synchistory_block(end_topo)
|
|
}
|
|
|
|
median_balance_e, err := w.GetEncryptedBalanceAtTopoHeight(median, w.GetAddress().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if there is a change in lower section, if yes process more
|
|
if start_topo == w.account.Balance_Result.Registration || bytes.Compare(start_balance_e.Serialize(), median_balance_e.Serialize()) != 0 {
|
|
//fmt.Printf("lower\n")
|
|
err = w.synchistory_internal_binary_search(start_topo, start_balance_e, median, median_balance_e)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// check if there is a change in higher section, if yes process more
|
|
if bytes.Compare(median_balance_e.Serialize(), end_balance_e.Serialize()) != 0 {
|
|
//fmt.Printf("higher\n")
|
|
err = w.synchistory_internal_binary_search(median, median_balance_e, end_topo, end_balance_e)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
/*if IsRegisteredAtTopoHeight (addr,median) {
|
|
high = median - 1
|
|
}else{
|
|
low = median + 1
|
|
}*/
|
|
|
|
}
|
|
|
|
/*
|
|
if end_topo - start_topo <= 1 {
|
|
err := w.synchistory_block(start_topo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return w.synchistory_block(end_topo)
|
|
}
|
|
|
|
|
|
// this means the address is either a ring member or a sender or a receiver in atleast one of the blocks
|
|
middle := start_topo + (end_topo-start_topo)/2
|
|
middle_balance_e, err := w.GetEncryptedBalanceAtTopoHeight( middle ,w.account.GetAddress().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if there is a change in lower section, if yes process more
|
|
if bytes.Compare(start_balance_e.Serialize(), middle_balance_e.Serialize()) != 0 {
|
|
fmt.Printf("lower\n")
|
|
err = w.synchistory_internal_binary_search(start_topo,start_balance_e, middle, middle_balance_e )
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// check if there is a change in lower section, if yes process more
|
|
if bytes.Compare(middle_balance_e.Serialize(), end_balance_e.Serialize()) != 0 {
|
|
fmt.Printf("higher\n")
|
|
err = w.synchistory_internal_binary_search(middle, middle_balance_e, end_topo,end_balance_e )
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
*/
|
|
|
|
return nil
|
|
}
|
|
|
|
// extract history from a single block
|
|
// first get a block, then get all the txs
|
|
// Todo we should expose an API to get all txs which have the specific address as ring member
|
|
// for a particular block
|
|
// for the entire chain
|
|
func (w *Wallet_Memory) synchistory_block(topo int64) (err error) {
|
|
|
|
var local_entries []Entry
|
|
|
|
compressed_address := w.account.Keys.Public.EncodeCompressed()
|
|
|
|
var previous_balance_e, current_balance_e *crypto.ElGamal
|
|
var previous_balance, current_balance, total_sent, total_received uint64
|
|
|
|
if topo <= 0 || w.account.Balance_Result.Registration == topo {
|
|
previous_balance_e = crypto.ConstructElGamal(w.account.Keys.Public.G1(), crypto.ElGamal_BASE_G)
|
|
} else {
|
|
previous_balance_e, err = w.GetEncryptedBalanceAtTopoHeight(topo-1, w.GetAddress().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
current_balance_e, err = w.GetEncryptedBalanceAtTopoHeight(topo, w.GetAddress().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
EWData := fmt.Sprintf("%x", current_balance_e.Serialize())
|
|
|
|
previous_balance = w.DecodeEncryptedBalance_Memory(previous_balance_e, 0)
|
|
current_balance = w.DecodeEncryptedBalance_Memory(current_balance_e, 0)
|
|
|
|
// we can skip some check if both balances are equal ( means we are ring members in this block)
|
|
// this check will also fail if we total spend == total receivein the block
|
|
// currently it is not implmented, and we bruteforce everything
|
|
|
|
_ = current_balance
|
|
|
|
var bl block.Block
|
|
var bresult structures.GetBlock_Result
|
|
if err = rpc_client.Call("DERO.GetBlock", structures.GetBlock_Params{Height: uint64(topo)}, &bresult); err != nil {
|
|
return fmt.Errorf("getblock rpc failed")
|
|
}
|
|
|
|
block_bin, _ := hex.DecodeString(bresult.Blob)
|
|
bl.Deserialize(block_bin)
|
|
|
|
if len(bl.Tx_hashes) >= 1 {
|
|
|
|
//fmt.Printf("Requesting tx data %s", txhash);
|
|
|
|
for i := range bl.Tx_hashes {
|
|
var tx transaction.Transaction
|
|
|
|
var tx_params structures.GetTransaction_Params
|
|
var tx_result structures.GetTransaction_Result
|
|
|
|
tx_params.Tx_Hashes = append(tx_params.Tx_Hashes, bl.Tx_hashes[i].String())
|
|
|
|
if err = rpc_client.Call("DERO.GetTransaction", tx_params, &tx_result); err != nil {
|
|
return fmt.Errorf("gettransa rpc failed %s", err)
|
|
}
|
|
|
|
tx_bin, err := hex.DecodeString(tx_result.Txs_as_hex[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tx.DeserializeHeader(tx_bin)
|
|
|
|
for j := range tx.Statement.Publickeylist_compressed { // check whether statement has public key
|
|
|
|
// check whether our address is a ring member if yes, process it as ours
|
|
if bytes.Compare(compressed_address, tx.Statement.Publickeylist_compressed[j][:]) == 0 {
|
|
|
|
// this tx contains us either as a ring member, or sender or receiver, so add all members as ring members for future
|
|
// keep collecting ring members to make things exponentially complex
|
|
for k := range tx.Statement.Publickeylist_compressed {
|
|
if j != k {
|
|
ringmember := address.NewAddressFromKeys((*crypto.Point)(tx.Statement.Publickeylist[k]))
|
|
ringmember.Mainnet = w.GetNetwork()
|
|
w.account.RingMembers[ringmember.String()] = 1
|
|
}
|
|
}
|
|
|
|
changes := crypto.ConstructElGamal(tx.Statement.C[j], tx.Statement.D)
|
|
changed_balance_e := previous_balance_e.Add(changes)
|
|
|
|
changed_balance := w.DecodeEncryptedBalance_Memory(changed_balance_e, previous_balance)
|
|
|
|
entry := Entry{Height: bl.Height, TopoHeight: topo, BlockHash: bl.GetHash().String(), TransactionPos: i, TXID: tx.GetHash(), Time: time.Unix(int64(bl.Timestamp), 0)}
|
|
|
|
entry.EWData = EWData
|
|
ring_member := false
|
|
|
|
switch {
|
|
case previous_balance == changed_balance: //ring member/* handle 0 value tx but fees is deducted */
|
|
//fmt.Printf("Anon Ring Member in TX %s\n", bl.Tx_hashes[i].String())
|
|
ring_member = true
|
|
case previous_balance > changed_balance: // we generated this tx
|
|
entry.Amount = previous_balance - changed_balance - tx.Statement.Fees
|
|
entry.Fees = tx.Statement.Fees
|
|
entry.Status = 1 // mark it as spend
|
|
total_sent += (previous_balance - changed_balance) + tx.Statement.Fees
|
|
|
|
rinputs := append([]byte{}, tx.Statement.Roothash[:]...)
|
|
for l := range tx.Statement.Publickeylist_compressed {
|
|
rinputs = append(rinputs, tx.Statement.Publickeylist_compressed[l][:]...)
|
|
}
|
|
rencrypted := new(bn256.G1).ScalarMult(crypto.HashToPoint(crypto.HashtoNumber(append([]byte(crypto.PROTOCOL_CONSTANT), rinputs...))), w.account.Keys.Secret.BigInt())
|
|
r := crypto.ReducedHash(rencrypted.EncodeCompressed())
|
|
|
|
// fmt.Printf("r calculated %s\n", r.Text(16))
|
|
// lets separate ring members
|
|
|
|
for k := range tx.Statement.C {
|
|
// skip self address, this can be optimized way more
|
|
if tx.Statement.Publickeylist[k].String() != w.account.Keys.Public.G1().String() {
|
|
var x bn256.G1
|
|
x.ScalarMult(crypto.G, new(big.Int).SetInt64(int64(entry.Amount)))
|
|
x.Add(new(bn256.G1).Set(&x), new(bn256.G1).ScalarMult(tx.Statement.Publickeylist[k], r))
|
|
if x.String() == tx.Statement.C[k].String() {
|
|
|
|
// lets encrypt the payment id, it's simple, we XOR the paymentID
|
|
blinder := new(bn256.G1).ScalarMult(tx.Statement.Publickeylist[k], r)
|
|
|
|
// proof is blinder + amount transferred, it will recover the encrypted payment id also
|
|
proof := address.NewAddressFromKeys((*crypto.Point)(blinder))
|
|
proof.PaymentID = make([]byte, 8, 8)
|
|
proof.Proof = true
|
|
binary.LittleEndian.PutUint64(proof.PaymentID, entry.Amount)
|
|
entry.Proof = proof.String()
|
|
|
|
entry.PaymentID = crypto.EncryptDecryptPaymentID(blinder, tx.PaymentID[:])
|
|
//paymentID := binary.BigEndian.Uint64(payment_id_encrypted_bytes[:]) // get decrypted payment id
|
|
|
|
addr := address.NewAddressFromKeys((*crypto.Point)(tx.Statement.Publickeylist[k]))
|
|
addr.Mainnet = w.GetNetwork()
|
|
//fmt.Printf("%d Sent funds to %s paymentid %x \n", tx.Height, addr.String(), entry.PaymentID)
|
|
|
|
entry.Details.TXID = fmt.Sprintf("%x", entry.TXID)
|
|
entry.Details.PaymentID = fmt.Sprintf("%x", entry.PaymentID)
|
|
entry.Details.Fees = tx.Statement.Fees
|
|
entry.Details.Amount = append(entry.Details.Amount, entry.Amount)
|
|
|
|
entry.Details.Daddress = append(entry.Details.Daddress, addr.String())
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
case previous_balance < changed_balance: // someone sentus this amount
|
|
entry.Amount = changed_balance - previous_balance
|
|
entry.Incoming = true
|
|
|
|
// we should decode the payment id
|
|
var x bn256.G1
|
|
x.ScalarMult(crypto.G, new(big.Int).SetInt64(0-int64(entry.Amount))) // increase receiver's balance
|
|
x.Add(new(bn256.G1).Set(&x), tx.Statement.C[j]) // get the blinder
|
|
|
|
entry.PaymentID = crypto.EncryptDecryptPaymentID(&x, tx.PaymentID[:])
|
|
|
|
//fmt.Printf("Received %s amount in TX(%d) %s payment id %x\n", globals.FormatMoney(changed_balance-previous_balance), tx.Height, bl.Tx_hashes[i].String(), entry.PaymentID)
|
|
total_received += (changed_balance - previous_balance)
|
|
}
|
|
|
|
if !ring_member { // do not book keep ring members
|
|
local_entries = append(local_entries, entry)
|
|
}
|
|
|
|
//break // this tx has been processed so skip it
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//fmt.Printf("block %d %+v\n", topo, tx_result)
|
|
}
|
|
|
|
if bytes.Compare(compressed_address, bl.Miner_TX.MinerAddress[:]) == 0 { // wallet user has minted a block
|
|
entry := Entry{Height: bl.Height, TopoHeight: topo, BlockHash: bl.GetHash().String(), TransactionPos: -1, Time: time.Unix(int64(bl.Timestamp), 0)}
|
|
|
|
entry.EWData = EWData
|
|
entry.Amount = current_balance - (previous_balance - total_sent + total_received)
|
|
entry.Coinbase = true
|
|
local_entries = append([]Entry{entry}, local_entries...)
|
|
|
|
//fmt.Printf("Coinbase Reward %s for block %d\n", globals.FormatMoney(current_balance-(previous_balance-total_sent+total_received)), topo)
|
|
}
|
|
|
|
for _, e := range local_entries {
|
|
w.InsertReplace(e)
|
|
}
|
|
|
|
if len(local_entries) >= 1 {
|
|
w.Save_Wallet()
|
|
// w.db.Sync()
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|