2021-11-08 16:39:17 +00:00
// 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 "runtime/debug"
//import "github.com/vmihailenco/msgpack"
//import "github.com/gorilla/websocket"
//import "github.com/mafredri/cdp/rpcc"
import "github.com/deroproject/derohe/rpc"
import "github.com/deroproject/derohe/block"
import "github.com/deroproject/derohe/config"
import "github.com/deroproject/derohe/globals"
import "github.com/deroproject/derohe/cryptography/crypto"
import "github.com/deroproject/derohe/errormsg"
import "github.com/deroproject/derohe/transaction"
import "github.com/deroproject/derohe/cryptography/bn256"
import "github.com/creachadair/jrpc2"
// this global variable should be within wallet structure
var Connected bool = false
2021-11-12 18:18:42 +00:00
var simulator bool // turns on simulator, which has 0 fees
2021-11-08 16:39:17 +00:00
// 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 "Block" :
NotifyNewBlock . L . Lock ( )
NotifyNewBlock . Broadcast ( )
NotifyNewBlock . L . Unlock ( )
case "Height" :
NotifyHeightChange . L . Lock ( )
NotifyHeightChange . Broadcast ( )
NotifyHeightChange . L . Unlock ( )
case "MiniBlock" : // we can skip this
default :
logger . V ( 1 ) . Info ( "Notification received" , "method" , req . Method ( ) )
}
}
var Daemon_Endpoint string
var Daemon_Endpoint_Active string
func get_daemon_address ( ) string {
if globals . Arguments [ "--remote" ] == true && globals . IsMainnet ( ) {
Daemon_Endpoint_Active = config . REMOTE_DAEMON
}
// if user provided endpoint has error, use default
if Daemon_Endpoint_Active == "" {
Daemon_Endpoint_Active = "127.0.0.1:" + fmt . Sprintf ( "%d" , config . Mainnet . RPC_Default_Port )
if ! globals . IsMainnet ( ) {
Daemon_Endpoint_Active = "127.0.0.1:" + fmt . Sprintf ( "%d" , config . Testnet . RPC_Default_Port )
}
}
if globals . Arguments [ "--daemon-address" ] != nil {
Daemon_Endpoint_Active = globals . Arguments [ "--daemon-address" ] . ( string )
}
return Daemon_Endpoint_Active
}
// tests connectivity when connectivity to daemon
func test_connectivity ( ) ( err error ) {
var result string
// Issue a call with a response.
if err = rpc_client . Call ( "DERO.Echo" , [ ] string { "hello" , "world" } , & result ) ; err != nil {
logger . V ( 1 ) . Error ( err , "DERO.Echo Call failed:" )
Connected = false
return
}
//fmt.Println(result)
var info rpc . GetInfo_Result
// Issue a call with a response.
if err = rpc_client . Call ( "DERO.GetInfo" , nil , & info ) ; err != nil {
logger . V ( 1 ) . Error ( err , "DERO.GetInfo Call failed:" )
Connected = false
return
}
// detect whether both are in different modes
// daemon is in testnet and wallet in mainnet or
// daemon
if info . Testnet != ! globals . IsMainnet ( ) {
err = fmt . Errorf ( "Mainnet/TestNet is different between wallet/daemon.Please run daemon/wallet without --testnet" )
logger . Error ( err , "Mainnet/Testnet mismatch" )
return
}
2021-11-12 18:18:42 +00:00
if strings . ToLower ( info . Network ) == "simulator" {
simulator = true
}
2021-11-08 16:39:17 +00:00
logger . Info ( "successfully connected to daemon" )
return nil
}
// triggers syncing with wallet every 5 seconds
func ( w * Wallet_Memory ) sync_loop ( ) {
for {
select {
case <- w . Quit :
break
default :
}
err := w . Sync_Wallet_Memory_With_Daemon ( ) // sync with the daemon
logger . V ( 1 ) . Error ( err , "wallet syncing err" , err )
time . Sleep ( timeout ) // wait 5 seconds
}
}
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_internal ( scid crypto . Hash ) ( err error ) {
if ! IsDaemonOnline ( ) {
w . Daemon_Height = 0
w . Daemon_TopoHeight = 0
return fmt . Errorf ( "Daemon is offline" )
} else {
//w.random_ring_members()
//rlog.Debugf("wallet topo height %d daemon online topo height %d\n", w.account.TopoHeight, w.Daemon_TopoHeight)
previous := w . getEncryptedBalanceresult ( scid ) . Data
2021-11-12 18:18:42 +00:00
if _ , _ , _ , e , err := w . GetEncryptedBalanceAtTopoHeight ( scid , - 1 , w . GetAddress ( ) . String ( ) ) ; err == nil {
2021-11-08 16:39:17 +00:00
//fmt.Printf("data '%s' previous '%s' scid %s\n",w.account.Balance_Result[scid].Data , previous,scid)
if w . getEncryptedBalanceresult ( scid ) . Data != previous {
b := w . DecodeEncryptedBalanceNow ( e ) // try to decode balance
if scid . IsZero ( ) {
w . account . Balance_Mature = b
}
w . account . Balance [ scid ] = b
w . SyncHistory ( scid ) // also update statement
} else {
}
w . save_if_disk ( ) // save wallet
} else {
return err
}
}
return
}
func ( w * Wallet_Memory ) Sync_Wallet_Memory_With_Daemon ( ) ( err error ) {
var scid crypto . Hash
return w . Sync_Wallet_Memory_With_Daemon_internal ( scid )
}
// 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 := rpc . SendRawTransaction_Params { Tx_as_hex : hex . EncodeToString ( tx . Serialize ( ) ) }
var result rpc . SendRawTransaction_Result
if err := rpc_client . Call ( "DERO.SendRawTransaction" , params , & result ) ; err != nil {
return err
}
if result . Status == "OK" {
return nil
} else {
err = fmt . Errorf ( "Err %s" , result . Status )
}
return
}
// decode encrypted balance now
// it may take a long time, its currently sing threaded, need to parallelize
func ( w * Wallet_Memory ) DecodeEncryptedBalanceNow ( el * crypto . ElGamal ) uint64 {
balance_point := new ( bn256 . G1 ) . 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 , w . account . Balance_Mature )
}
func ( w * Wallet_Memory ) GetSelfEncryptedBalanceAtTopoHeight ( scid crypto . Hash , topoheight int64 ) ( r rpc . GetEncryptedBalance_Result , err error ) {
defer func ( ) {
if r := recover ( ) ; r != nil {
2021-11-12 18:18:42 +00:00
logger . V ( 1 ) . Error ( nil , "Recovered while GetSelfEncryptedBalanceAtTopoHeight" , "r" , r , "stack" , debug . Stack ( ) )
err = fmt . Errorf ( "Recovered while GetSelfEncryptedBalanceAtTopoHeight r %s stack %s" , r , string ( debug . Stack ( ) ) )
2021-11-08 16:39:17 +00:00
}
} ( )
err = rpc_client . Call ( "DERO.GetEncryptedBalance" , rpc . GetEncryptedBalance_Params { SCID : scid , Address : w . GetAddress ( ) . String ( ) , TopoHeight : topoheight } , & r )
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
//
2021-11-12 18:18:42 +00:00
func ( w * Wallet_Memory ) GetEncryptedBalanceAtTopoHeight ( scid crypto . Hash , topoheight int64 , accountaddr string ) ( bits int , lastused uint64 , blid crypto . Hash , e * crypto . ElGamal , err error ) {
2021-11-08 16:39:17 +00:00
defer func ( ) {
if r := recover ( ) ; r != nil {
2021-11-12 18:18:42 +00:00
logger . V ( 1 ) . Error ( nil , "Recovered while GetEncryptedBalanceAtTopoHeight" , "r" , r , "stack" , debug . Stack ( ) )
err = fmt . Errorf ( "Recovered while GetEncryptedBalanceAtTopoHeight r %s stack %s" , r , debug . Stack ( ) )
2021-11-08 16:39:17 +00:00
}
} ( )
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 rpc.GetEncryptedBalance_Params
var result rpc . GetEncryptedBalance_Result
// Issue a call with a response.
if err = rpc_client . Call ( "DERO.GetEncryptedBalance" , rpc . GetEncryptedBalance_Params { SCID : scid , Address : accountaddr , TopoHeight : topoheight } , & result ) ; err != nil {
logger . Error ( err , "DERO.GetEncryptedBalance Call failed:" )
if strings . Contains ( strings . ToLower ( err . Error ( ) ) , strings . ToLower ( errormsg . ErrAccountUnregistered . Error ( ) ) ) && accountaddr == w . GetAddress ( ) . String ( ) && scid . IsZero ( ) {
w . Error = errormsg . ErrAccountUnregistered
//fmt.Printf("setting unregisterd111 err %s scid %s topoheight %d\n",err,scid, topoheight)
//fmt.Printf("debug stack %s\n",debug.Stack())
return
}
// all SCID users are considered registered and their balance is assumed zero
if ! scid . IsZero ( ) {
if strings . Contains ( strings . ToLower ( err . Error ( ) ) , strings . ToLower ( errormsg . ErrAccountUnregistered . Error ( ) ) ) {
if addr , err1 := rpc . NewAddress ( accountaddr ) ; err1 != nil {
err = err1
return
} else {
e = crypto . ConstructElGamal ( addr . PublicKey . G1 ( ) , crypto . ElGamal_BASE_G ) // init zero balance
bits = 0 // since this is an SC we can
err = nil
return
}
}
}
return
}
// fmt.Printf("GetEncryptedBalance result %+v\n", result)
if scid . IsZero ( ) && accountaddr == w . GetAddress ( ) . String ( ) {
if result . Status == errormsg . ErrAccountUnregistered . Error ( ) {
w . Error = errormsg . ErrAccountUnregistered
w . account . Registered = false
} else {
w . account . Registered = true
}
}
// fmt.Printf("status '%s' err '%s' %+v %+v \n", result.Status , w.Error , result.Status == errormsg.ErrAccountUnregistered.Error() , accountaddr == w.account.GetAddress().String())
if scid . IsZero ( ) && result . Status == errormsg . ErrAccountUnregistered . Error ( ) {
err = fmt . Errorf ( "%s" , result . Status )
return
}
if topoheight == - 1 {
w . Daemon_Height = uint64 ( result . DHeight )
w . Daemon_TopoHeight = result . DTopoheight
w . Merkle_Balance_TreeHash = result . DMerkle_Balance_TreeHash
}
if topoheight == - 1 && accountaddr == w . GetAddress ( ) . String ( ) {
//fmt.Printf("topoheight %d accountaddr '%s' waddress '%s'\n ",topoheight,accountaddr,w.GetAddress().String())
w . setEncryptedBalanceresult ( scid , result )
w . account . TopoHeight = result . Topoheight
}
if scid . IsZero ( ) && 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 ( ) && scid . IsZero ( ) {
w . Error = nil
}
var nb crypto . NonceBalance
nb . Unmarshal ( hexdecoded )
2021-11-12 18:18:42 +00:00
var block_hash crypto . Hash
if err = block_hash . UnmarshalText ( [ ] byte ( result . BlockHash ) ) ; err != nil {
return
}
return result . Bits , nb . NonceHeight , block_hash , nb . Balance , nil
2021-11-08 16:39:17 +00:00
}
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 ( scid crypto . Hash , topoheight int64 , accountaddr string ) ( balance uint64 , noncetopo uint64 , err error ) {
2021-11-12 18:18:42 +00:00
_ , noncetopo , _ , encrypted_balance , err := w . GetEncryptedBalanceAtTopoHeight ( scid , topoheight , accountaddr )
2021-11-08 16:39:17 +00:00
if err != nil {
return 0 , 0 , err
}
return w . DecodeEncryptedBalance_Memory ( encrypted_balance , 0 ) , noncetopo , nil
}
// sync history of wallet from blockchain
func ( w * Wallet_Memory ) Random_ring_members ( scid crypto . Hash ) ( alist [ ] string ) {
var result rpc . GetRandomAddress_Result
//fmt.Printf("getting ring members %s %s\n",scid.String(), debug.Stack())
// Issue a call with a response.
if err := rpc_client . Call ( "DERO.GetRandomAddress" , rpc . GetRandomAddress_Params { SCID : scid } , & result ) ; err != nil {
logger . V ( 1 ) . Error ( err , "DERO.GetRandomAddress Call failed:" )
return
}
//fmt.Printf("getting ring members %d\n",len(result.Address))
for _ , k := range result . Address {
if k != w . GetAddress ( ) . String ( ) {
alist = append ( alist , k )
}
}
//fmt.Printf("got ring members %d\n",len(result.Address))
return
}
// sync history of wallet from blockchain
func ( w * Wallet_Memory ) SyncHistory ( scid crypto . Hash ) ( balance uint64 ) {
2021-11-10 11:04:22 +00:00
defer func ( ) {
if r := recover ( ) ; r != nil {
logger . V ( 1 ) . Error ( nil , "Recovered while syncing connecting" , "r" , r , "stack" , debug . Stack ( ) )
}
} ( )
2021-11-08 16:39:17 +00:00
if w . getEncryptedBalanceresult ( scid ) . 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)
entries := w . account . EntriesNative [ scid ]
// we need to find a sync point, to minimize traffic
for i := len ( entries ) - 1 ; i >= 0 ; {
// below condition will trigger if chain got pruned on server
if w . getEncryptedBalanceresult ( scid ) . Registration >= entries [ i ] . TopoHeight { // keep old history if chain got pruned
break
}
if last_topo_height == entries [ i ] . TopoHeight {
i --
} else {
last_topo_height = entries [ i ] . TopoHeight
var result rpc . GetBlockHeaderByHeight_Result
// Issue a call with a response.
if err := rpc_client . Call ( "DERO.GetBlockHeaderByTopoHeight" , rpc . GetBlockHeaderByTopoHeight_Params { TopoHeight : uint64 ( entries [ i ] . TopoHeight ) } , & result ) ; err != nil {
logger . V ( 1 ) . Error ( err , "DERO.GetBlockHeaderByTopoHeight Call failed:" )
return 0
}
if i >= 1 && last_topo_height == entries [ i - 1 ] . TopoHeight { // skipping any entries withing same block
for ; i >= 1 ; i -- {
if last_topo_height != entries [ i - 1 ] . TopoHeight {
entries = entries [ : i ]
w . account . EntriesNative [ scid ] = entries
}
}
}
if i == 0 {
w . account . EntriesNative [ scid ] = 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 == entries [ i ] . BlockHash {
w . synchistory_internal ( scid , entries [ i ] . TopoHeight + 1 , w . getEncryptedBalanceresult ( scid ) . Topoheight )
return
}
}
}
//fmt.Printf("syncing loop using Registration %+v\n", w.account.Balance_Result[scid])
// if we reached here, means we should sync from scratch
w . synchistory_internal ( scid , w . getEncryptedBalanceresult ( scid ) . Registration , w . getEncryptedBalanceresult ( scid ) . 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 ( scid crypto . Hash , start_topo , end_topo int64 ) error {
var err error
var start_balance_e * crypto . ElGamal
if start_topo == w . getEncryptedBalanceresult ( scid ) . Registration {
start_balance_e = crypto . ConstructElGamal ( w . account . Keys . Public . G1 ( ) , crypto . ElGamal_BASE_G )
} else {
2021-11-12 18:18:42 +00:00
_ , _ , _ , start_balance_e , err = w . GetEncryptedBalanceAtTopoHeight ( scid , start_topo , w . GetAddress ( ) . String ( ) )
2021-11-08 16:39:17 +00:00
if err != nil {
return err
}
}
2021-11-12 18:18:42 +00:00
_ , _ , _ , end_balance_e , err := w . GetEncryptedBalanceAtTopoHeight ( scid , end_topo , w . GetAddress ( ) . String ( ) )
2021-11-08 16:39:17 +00:00
if err != nil {
return err
}
return w . synchistory_internal_binary_search ( scid , start_topo , start_balance_e , end_topo , end_balance_e )
}
func ( w * Wallet_Memory ) synchistory_internal_binary_search ( scid crypto . Hash , 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 ( scid , start_topo )
if err != nil {
return err
}
}
if end_topo - start_topo <= 1 {
return w . synchistory_block ( scid , end_topo )
}
2021-11-12 18:18:42 +00:00
_ , _ , _ , median_balance_e , err := w . GetEncryptedBalanceAtTopoHeight ( scid , median , w . GetAddress ( ) . String ( ) )
2021-11-08 16:39:17 +00:00
if err != nil {
return err
}
// check if there is a change in lower section, if yes process more
if start_topo == w . getEncryptedBalanceresult ( scid ) . Registration || bytes . Compare ( start_balance_e . Serialize ( ) , median_balance_e . Serialize ( ) ) != 0 {
//fmt.Printf("lower\n")
err = w . synchistory_internal_binary_search ( scid , 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 ( scid , 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 ( scid crypto . Hash , topo int64 ) ( err error ) {
var local_entries [ ] rpc . 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 . getEncryptedBalanceresult ( scid ) . Registration == topo {
previous_balance_e = crypto . ConstructElGamal ( w . account . Keys . Public . G1 ( ) , crypto . ElGamal_BASE_G )
} else {
2021-11-12 18:18:42 +00:00
_ , _ , _ , previous_balance_e , err = w . GetEncryptedBalanceAtTopoHeight ( scid , topo - 1 , w . GetAddress ( ) . String ( ) )
2021-11-08 16:39:17 +00:00
if err != nil {
return err
}
}
2021-11-12 18:18:42 +00:00
_ , _ , _ , current_balance_e , err = w . GetEncryptedBalanceAtTopoHeight ( scid , topo , w . GetAddress ( ) . String ( ) )
2021-11-08 16:39:17 +00:00
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 rpc . GetBlock_Result
if err = rpc_client . Call ( "DERO.GetBlock" , rpc . 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 rpc . GetTransaction_Params
var tx_result rpc . 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
}
if err = tx . Deserialize ( tx_bin ) ; err != nil {
logger . V ( 1 ) . Error ( err , "Error deserialing tx" , "txid" , bl . Tx_hashes [ i ] . String ( ) , "incoming bytes" , tx_result . Txs_as_hex [ 0 ] )
continue
}
if tx . TransactionType == transaction . REGISTRATION {
continue
}
2021-11-10 11:04:22 +00:00
// if daemon was syncing/or disk corrupption, it may not give data, so skip
if len ( tx_result . Txs ) == 0 {
return fmt . Errorf ( "Daemon did not expandd tx %s" , bl . Tx_hashes [ i ] . String ( ) )
}
2021-11-08 16:39:17 +00:00
// since balance might change with tx, we track within tx using this
previous_balance_e_tx := new ( crypto . ElGamal ) . Deserialize ( previous_balance_e . Serialize ( ) )
for t := range tx . Payloads {
if int ( tx . Payloads [ t ] . Statement . RingSize ) != len ( tx_result . Txs [ 0 ] . Ring [ t ] ) {
logger . V ( 1 ) . Error ( fmt . Errorf ( "missing ring members" ) , "missing ring members" , "txid" , bl . Tx_hashes [ i ] . String ( ) , "expected" , int ( tx . Payloads [ t ] . Statement . RingSize ) , "got" , len ( tx_result . Txs [ t ] . Ring ) )
continue
}
if tx . Payloads [ t ] . SCID != scid { // skip tokens in which we are not interested
continue
}
previous_balance = w . DecodeEncryptedBalanceNow ( previous_balance_e_tx )
for j := 0 ; j < int ( tx . Payloads [ t ] . Statement . RingSize ) ; j ++ { // first fill in all the ring members
var addr * rpc . Address
if addr , err = rpc . NewAddress ( tx_result . Txs [ 0 ] . Ring [ t ] [ j ] ) ; err != nil {
panic ( err )
}
var buf [ 33 ] byte
copy ( buf [ : ] , addr . PublicKey . EncodeCompressed ( ) )
tx . Payloads [ t ] . Statement . Publickeylist_compressed = append ( tx . Payloads [ t ] . Statement . Publickeylist_compressed , buf )
}
for j := 0 ; j < int ( tx . Payloads [ t ] . Statement . RingSize ) ; j ++ { // 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 . Payloads [ t ] . 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 . Payloads [ t ] . Statement . Publickeylist_compressed {
var p bn256 . G1
if err = p . DecodeCompressed ( tx . Payloads [ t ] . Statement . Publickeylist_compressed [ k ] [ : ] ) ; err != nil {
fmt . Printf ( "key could not be decompressed" )
} else {
tx . Payloads [ t ] . Statement . Publickeylist = append ( tx . Payloads [ t ] . Statement . Publickeylist , & p )
}
}
/ * 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 . Payloads [ t ] . Statement . C [ j ] , tx . Payloads [ t ] . Statement . D )
changed_balance_e := previous_balance_e_tx . Add ( changes )
previous_balance_e_tx = new ( crypto . ElGamal ) . Deserialize ( changed_balance_e . Serialize ( ) )
changed_balance := w . DecodeEncryptedBalance_Memory ( changed_balance_e , previous_balance )
//fmt.Printf("%d changed_balance %d previous_balance %d len payload %d\n", t, changed_balance, previous_balance, len(tx.Payloads[t].RPCPayload))
entry := rpc . Entry { Height : bl . Height , Pos : t , TopoHeight : topo , BlockHash : bl . GetHash ( ) . String ( ) , TransactionPos : i , TXID : tx . GetHash ( ) . String ( ) , Time : time . UnixMicro ( int64 ( bl . Timestamp ) ) , Fees : tx . Fees ( ) }
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 . Burn = tx . Payloads [ t ] . BurnValue
entry . Amount = previous_balance - changed_balance - ( tx . Payloads [ t ] . Statement . Fees )
entry . Fees = tx . Payloads [ t ] . Statement . Fees
entry . Status = 1 // mark it as spend
total_sent += entry . Amount + entry . Fees // burn is in amount
rinputs := append ( [ ] byte { } , tx . Payloads [ t ] . Statement . Roothash [ : ] ... )
for l := range tx . Payloads [ t ] . Statement . Publickeylist_compressed {
rinputs = append ( rinputs , tx . Payloads [ t ] . 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("t %d r calculated %s value amount %d burn %d\n", t, r.Text(16), entry.Amount,entry.Burn)
parity := tx . Payloads [ t ] . Proof . Parity ( )
// lets separate ring members
for k := range tx . Payloads [ t ] . Statement . C {
if ( k % 2 == 0 ) == parity { // ignore senders self,this condition is well thought out and works good enough
continue
}
// we need to brute force receiver in this case, if amount sent is zero
if entry . Amount == entry . Burn && tx . Payloads [ t ] . RPCType == transaction . ENCRYPTED_DEFAULT_PAYLOAD_CBOR {
shared_key := crypto . GenerateSharedSecret ( r , tx . Payloads [ t ] . Statement . Publickeylist [ k ] )
var data_copy [ ] byte
data_copy = append ( data_copy , tx . Payloads [ t ] . RPCPayload ... )
crypto . EncryptDecryptUserData ( crypto . Keccak256 ( shared_key [ : ] , tx . Payloads [ t ] . Statement . Publickeylist [ k ] . EncodeCompressed ( ) ) , data_copy )
var args rpc . Arguments
if err = args . UnmarshalBinary ( data_copy [ 1 : ] ) ; err != nil {
//fmt.Printf("k %d len(data_copy) %d err %s data_copy %x\n",k, len(data_copy),err, data_copy)
continue
}
// we have found one which could be decoded, fall through
}
var x bn256 . G1
x . ScalarMult ( crypto . G , new ( big . Int ) . SetInt64 ( int64 ( entry . Amount - entry . Burn ) ) )
x . Add ( new ( bn256 . G1 ) . Set ( & x ) , new ( bn256 . G1 ) . ScalarMult ( tx . Payloads [ t ] . Statement . Publickeylist [ k ] , r ) )
if x . String ( ) == tx . Payloads [ t ] . Statement . C [ k ] . String ( ) {
var x bn256 . G1
x . ScalarMult ( crypto . G , new ( big . Int ) . SetInt64 ( int64 ( 0 - entry . Amount ) ) )
x . Add ( new ( bn256 . G1 ) . Set ( & x ) , tx . Payloads [ t ] . Statement . C [ k ] ) // get the blinder
blinder := & x
shared_key := crypto . GenerateSharedSecret ( r , tx . Payloads [ t ] . Statement . Publickeylist [ k ] )
// proof is blinder + amount transferred, it will recover the encrypted rpc payload also
// enable sender side proofs
proof := rpc . NewAddressFromKeys ( ( * crypto . Point ) ( blinder ) )
proof . Proof = true
proof . Arguments = rpc . Arguments { { "H" , rpc . DataHash , crypto . Hash ( shared_key ) } , { rpc . RPC_VALUE_TRANSFER , rpc . DataUint64 , uint64 ( entry . Amount - entry . Burn ) } }
entry . Proof = proof . String ( )
entry . PayloadType = tx . Payloads [ t ] . RPCType
switch tx . Payloads [ t ] . RPCType {
case transaction . ENCRYPTED_DEFAULT_PAYLOAD_CBOR :
crypto . EncryptDecryptUserData ( crypto . Keccak256 ( shared_key [ : ] , tx . Payloads [ t ] . Statement . Publickeylist [ k ] . EncodeCompressed ( ) ) , tx . Payloads [ t ] . RPCPayload )
//fmt.Printf("decoded plaintext payload t %d %x\n",t,tx.Payloads[t].RPCPayload)
//sender_idx := uint(tx.Payloads[t].RPCPayload[0])
addr := rpc . NewAddressFromKeys ( ( * crypto . Point ) ( w . account . Keys . Public . G1 ( ) ) )
addr . Mainnet = w . GetNetwork ( )
entry . Sender = addr . String ( )
entry . Payload = append ( entry . Payload , tx . Payloads [ t ] . RPCPayload [ 1 : ] ... )
entry . Data = append ( entry . Data , tx . Payloads [ t ] . RPCPayload [ : ] ... )
args , _ := entry . ProcessPayload ( )
_ = args
// fmt.Printf("data received %s idx %d arguments %s\n", string(entry.Payload), sender_idx, args)
default :
entry . PayloadError = fmt . Sprintf ( "unknown payload type %d" , tx . Payloads [ t ] . RPCType )
entry . Payload = tx . Payloads [ t ] . RPCPayload
}
//paymentID := binary.BigEndian.Uint64(payment_id_encrypted_bytes[:]) // get decrypted payment id
addr := rpc . NewAddressFromKeys ( ( * crypto . Point ) ( tx . Payloads [ t ] . Statement . Publickeylist [ k ] ) )
addr . Mainnet = w . GetNetwork ( )
entry . Destination = addr . String ( )
//fmt.Printf("t %d height %d Sent funds to %s entry %+v\n",t, tx.Height, addr.String(), entry)
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 ) ) ) // decrease amounts
x . Add ( new ( bn256 . G1 ) . Set ( & x ) , tx . Payloads [ t ] . Statement . C [ j ] ) // get the blinder
blinder := & x
shared_key := crypto . GenerateSharedSecret ( w . account . Keys . Secret . BigInt ( ) , tx . Payloads [ t ] . Statement . D )
// enable receiver side proofs
proof := rpc . NewAddressFromKeys ( ( * crypto . Point ) ( blinder ) )
proof . Proof = true
proof . Arguments = rpc . Arguments { { "H" , rpc . DataHash , crypto . Hash ( shared_key ) } , { rpc . RPC_VALUE_TRANSFER , rpc . DataUint64 , uint64 ( entry . Amount ) } }
entry . Proof = proof . String ( )
entry . PayloadType = tx . Payloads [ t ] . RPCType
switch tx . Payloads [ t ] . RPCType {
case 0 :
//fmt.Printf("decoding encrypted payload %x\n",tx.Payloads[t].RPCPayload)
crypto . EncryptDecryptUserData ( crypto . Keccak256 ( shared_key [ : ] , w . GetAddress ( ) . PublicKey . EncodeCompressed ( ) ) , tx . Payloads [ t ] . RPCPayload )
//fmt.Printf("decoded plaintext payload %x\n",tx.Payloads[t].RPCPayload)
sender_idx := uint ( tx . Payloads [ t ] . RPCPayload [ 0 ] )
if sender_idx <= uint ( tx . Payloads [ t ] . Statement . RingSize ) {
addr := rpc . NewAddressFromKeys ( ( * crypto . Point ) ( tx . Payloads [ t ] . Statement . Publickeylist [ sender_idx ] ) )
addr . Mainnet = w . GetNetwork ( )
entry . Sender = addr . String ( )
}
entry . Payload = append ( entry . Payload , tx . Payloads [ t ] . RPCPayload [ 1 : ] ... )
entry . Data = append ( entry . Data , tx . Payloads [ t ] . RPCPayload [ : ] ... )
args , _ := entry . ProcessPayload ( )
_ = args
// fmt.Printf("data received %s idx %d arguments %s\n", string(entry.Payload), sender_idx, args)
default :
entry . PayloadError = fmt . Sprintf ( "unknown payload type %d" , tx . Payloads [ t ] . RPCType )
entry . Payload = tx . Payloads [ t ] . RPCPayload
}
//fmt.Printf("Received %s amount in TX(%d) %s payment id %x Src_ID %s data %s\n", globals.FormatMoney(changed_balance-previous_balance), tx.Height, bl.Tx_hashes[i].String(), entry.PaymentID, tx.Src_ID, tx.Data)
//fmt.Printf("Received amount in TX(%d) %s payment id %x Src_ID %s data %s\n", tx.Height, bl.Tx_hashes[i].String(), entry.PaymentID, tx.SrcID, tx.Data)
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)
}
}
previous_balance = w . DecodeEncryptedBalance_Memory ( previous_balance_e , 0 )
coinbase_reward := current_balance - ( previous_balance - total_sent + total_received )
//fmt.Printf("ht %d coinbase_reward %d curent balance %d previous_balance %d sent %d received %d\n", bl.Height, coinbase_reward, current_balance, previous_balance, total_sent, total_received)
if bytes . Compare ( compressed_address , bl . Miner_TX . MinerAddress [ : ] ) == 0 || coinbase_reward > 0 { // wallet user has minted a block
entry := rpc . Entry { Height : bl . Height , TopoHeight : topo , BlockHash : bl . GetHash ( ) . String ( ) , TransactionPos : - 1 , Time : time . UnixMicro ( int64 ( bl . Timestamp ) ) }
entry . EWData = EWData
entry . Amount = current_balance - ( previous_balance - total_sent + total_received )
entry . Coinbase = true
local_entries = append ( [ ] rpc . 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 ( scid , e )
}
if len ( local_entries ) >= 1 {
w . save_if_disk ( ) // save wallet()
// w.db.Sync()
}
return nil
}