2021-12-04 16:42:11 +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
import "fmt"
//import "sort"
//import "math/rand"
//import cryptorand "crypto/rand"
//import "encoding/binary"
import "encoding/hex"
//import "encoding/json"
//import "github.com/vmihailenco/msgpack"
import "github.com/deroproject/derohe/config"
import "github.com/deroproject/derohe/cryptography/crypto"
//import "github.com/deroproject/derohe/crypto/ringct"
import "github.com/deroproject/derohe/transaction"
//import "github.com/deroproject/derohe/globals"
import "github.com/deroproject/derohe/rpc"
//import "github.com/deroproject/derohe/ddn"
//import "github.com/deroproject/derohe/structures"
//import "github.com/deroproject/derohe/blockchain/inputmaturity"
import "github.com/deroproject/derohe/cryptography/bn256"
/ *
func ( w * Wallet_Memory ) Transfer_Simplified ( addr string , value uint64 , data [ ] byte , scdata rpc . Arguments ) ( tx * transaction . Transaction , err error ) {
if sender , err := rpc . NewAddress ( addr ) ; err == nil {
burn_value := uint64 ( 0 )
return w . TransferPayload0 ( * sender , value , burn_value , 0 , 0 , false , data , scdata , false )
}
return
}
* /
// we should reply to an entry
// send amount to specific addresses
2022-01-26 10:05:01 +00:00
func ( w * Wallet_Memory ) TransferPayload0 ( transfers [ ] rpc . Transfer , ringsize uint64 , transfer_all bool , scdata rpc . Arguments , gasstorage uint64 , dry_run bool ) ( tx * transaction . Transaction , err error ) {
2021-12-04 16:42:11 +00:00
// var transfer_details structures.Outgoing_Transfer_Details
w . transfer_mutex . Lock ( )
defer w . transfer_mutex . Unlock ( )
2022-03-03 10:49:59 +00:00
//if len(transfers) == 0 {
// return nil, fmt.Error("transfers is nil, cannot send.")
//}
2021-12-04 16:42:11 +00:00
if ringsize == 0 {
ringsize = uint64 ( w . account . Ringsize ) // use wallet ringsize, if ringsize not provided
} else { // we need to use supplied ringsize
if ringsize & ( ringsize - 1 ) != 0 {
err = fmt . Errorf ( "ringsize should be power of 2. value %d" , ringsize )
return
}
if ! ( ringsize >= config . MIN_RINGSIZE && ringsize <= config . MAX_RINGSIZE ) {
err = fmt . Errorf ( "ringsize out of range value %d" , ringsize )
return
}
}
//ringsize = 2
// if wallet is online,take the fees from the network itself
// otherwise use whatever user has provided
//if w.GetMode() {
fees_per_kb := w . dynamic_fees_per_kb // TODO disabled as protection while lots more testing is going on
//rlog.Infof("Fees per KB %d\n", fees_per_kb)
//}
if fees_per_kb == 0 {
fees_per_kb = config . FEE_PER_KB
}
// user wants to do an SC call, but doesn't want any transfer, so we will transfer 0 to a random account
if len ( scdata ) >= 1 && len ( transfers ) == 0 {
var zeroscid crypto . Hash
for _ , k := range w . Random_ring_members ( zeroscid ) {
if k != w . GetAddress ( ) . String ( ) { /// make sure random member is not equal to ourself
transfers = append ( transfers , rpc . Transfer { Destination : k , Amount : 0 } )
logger . V ( 3 ) . Info ( "Doing 0 transfer to" , "random_address" , k )
break
}
}
}
if len ( transfers ) >= 1 {
has_base := false
for i := range transfers {
if transfers [ i ] . SCID . IsZero ( ) {
has_base = true
}
}
// if we do not have base we can not detect fees. this restriction should be lifted at suitable time
if ! has_base {
var zeroscid crypto . Hash
for _ , k := range w . Random_ring_members ( zeroscid ) {
if k != w . GetAddress ( ) . String ( ) { /// make sure random member is not equal to ourself
transfers = append ( transfers , rpc . Transfer { Destination : k , Amount : 0 } )
logger . V ( 3 ) . Info ( "Doing 0 transfer to" , "random_address" , k )
break
}
}
}
}
for t := range transfers {
var data [ ] byte
if data , err = transfers [ t ] . Payload_RPC . CheckPack ( transaction . PAYLOAD0_LIMIT ) ; err != nil {
return
}
if len ( data ) != transaction . PAYLOAD0_LIMIT {
err = fmt . Errorf ( "Expecting exactly %d bytes data but have %d bytes" , transaction . PAYLOAD0_LIMIT , len ( data ) )
return
}
}
//fees := (ringsize + 1) * fees_per_kb // start with zero fees
// expected_fee := uint64(0)
if transfer_all {
err = fmt . Errorf ( "Transfer all not supported" )
return
//transfers[0].Amount = w.account.Balance_Mature - fees
}
total_amount_required := map [ crypto . Hash ] uint64 { }
for i := range transfers {
total_amount_required [ transfers [ i ] . SCID ] = total_amount_required [ transfers [ i ] . SCID ] + transfers [ i ] . Amount + transfers [ i ] . Burn
}
for i := range transfers {
var current_balance uint64
current_balance , _ , err = w . GetDecryptedBalanceAtTopoHeight ( transfers [ i ] . SCID , - 1 , w . GetAddress ( ) . String ( ) )
if err != nil {
return
}
if total_amount_required [ transfers [ i ] . SCID ] > current_balance {
err = fmt . Errorf ( "Insufficent funds for scid %s Need %s Actual %s" , transfers [ i ] . SCID , FormatMoney ( total_amount_required [ transfers [ i ] . SCID ] ) , FormatMoney ( current_balance ) )
return
}
}
for t := range transfers {
if transfers [ t ] . Destination == "" { // user skipped destination
if transfers [ t ] . SCID . IsZero ( ) {
err = fmt . Errorf ( "Main Destination cannot be empty" )
return
}
// we will try x times, to get a random ring ring member other than us, if ok, we move ahead
ring_count := 0
for i := 0 ; i < 20 ; i ++ {
//fmt.Printf("getting random ring member %d\n", i)
scid := transfers [ t ] . SCID
if i > 17 { // if we cannot obtain ring member is 17 tries, choose ring member from zero
var zeroscid crypto . Hash
scid = zeroscid
}
for _ , k := range w . Random_ring_members ( scid ) {
if k != w . GetAddress ( ) . String ( ) {
transfers [ t ] . Destination = k
i = 1000000 // break outer loop also
ring_count ++
break
}
}
}
}
if transfers [ t ] . Destination == "" {
err = fmt . Errorf ( "could not obtain random ring member for scid %s" , transfers [ t ] . SCID )
return
}
// try to resolve name to address here
if _ , err = rpc . NewAddress ( transfers [ t ] . Destination ) ; err != nil {
if transfers [ t ] . Destination , err = w . NameToAddress ( transfers [ t ] . Destination ) ; err != nil {
2022-03-10 11:15:46 +00:00
err = fmt . Errorf ( "could not decode name or address err '%s' name '%s'\n" , err , transfers [ t ] . Destination )
2021-12-04 16:42:11 +00:00
return
}
}
}
//fmt.Printf("transfers %+v\n", transfers)
var rings [ ] [ ] * bn256 . G1
var rings_balances [ ] [ ] [ ] byte //initialize all maps
var max_bits_array [ ] int
topoheight := int64 ( - 1 )
var block_hash crypto . Hash
2022-01-06 04:11:51 +00:00
var zeroscid crypto . Hash
2022-01-26 10:05:01 +00:00
// noncetopo should be verified for all ring members simultaneously
// this can lead to tx rejection
// we currently bypass this since random members are chosen which have not been used in last 5 block
2022-01-06 04:11:51 +00:00
_ , noncetopo , block_hash , self_e , err := w . GetEncryptedBalanceAtTopoHeight ( zeroscid , - 1 , w . GetAddress ( ) . String ( ) )
if err != nil {
2022-03-10 11:15:46 +00:00
err = fmt . Errorf ( "could not obtain encrypted balance for self err %s\n" , err )
2022-01-06 04:11:51 +00:00
return
}
2022-01-02 15:49:13 +00:00
2022-01-06 04:11:51 +00:00
// TODO, we should check nonce for base token and other tokens at the same time
// right now, we are probably using a bit of luck here
if daemon_topoheight >= int64 ( noncetopo ) + 3 { // if wallet has not been recently used, increase probability of user's tx being successfully mined
topoheight = daemon_topoheight - 3
2021-12-04 16:42:11 +00:00
}
2022-01-06 04:11:51 +00:00
_ , _ , block_hash , self_e , _ = w . GetEncryptedBalanceAtTopoHeight ( transfers [ 0 ] . SCID , topoheight , w . GetAddress ( ) . String ( ) )
2021-12-04 16:42:11 +00:00
if err != nil {
fmt . Printf ( "self unregistered err %s\n" , err )
return
}
er , err := w . GetSelfEncryptedBalanceAtTopoHeight ( transfers [ 0 ] . SCID , topoheight )
if err != nil {
2022-03-10 11:15:46 +00:00
err = fmt . Errorf ( "could not obtain encrypted balance for self err %s\n" , err )
2021-12-04 16:42:11 +00:00
return
}
height := uint64 ( er . Height )
block_hash = er . BlockHash
topoheight = er . Topoheight
treehash := er . Merkle_Balance_TreeHash
treehash_raw , err := hex . DecodeString ( treehash )
if err != nil {
return
}
if len ( treehash_raw ) != 32 {
err = fmt . Errorf ( "roothash is not of 32 bytes, probably daemon corruption '%s'" , treehash )
return
}
for t := range transfers {
var ring [ ] * bn256 . G1
var ring_balances [ ] [ ] byte
/ * if transfers [ t ] . SCID . IsZero ( ) {
ringsize = uint64 ( ringsize )
} else {
ringsize = ringsize // only for easier testing
}
* /
bits_needed := make ( [ ] int , ringsize , ringsize )
bits_needed [ 0 ] , _ , _ , self_e , err = w . GetEncryptedBalanceAtTopoHeight ( transfers [ t ] . SCID , topoheight , w . GetAddress ( ) . String ( ) )
if err != nil {
fmt . Printf ( "self unregistered err %s\n" , err )
return
} else {
ring_balances = append ( ring_balances , self_e . Serialize ( ) )
ring = append ( ring , w . account . Keys . Public . G1 ( ) )
}
var addr * rpc . Address
if addr , err = rpc . NewAddress ( transfers [ t ] . Destination ) ; err != nil {
return
}
2022-03-31 04:30:56 +00:00
if addr . IsIntegratedAddress ( ) && addr . Arguments . Validate_Arguments ( ) != nil {
err = fmt . Errorf ( "Integrated Address arguments could not be validated." )
return
}
if addr . IsIntegratedAddress ( ) && len ( transfers [ t ] . Payload_RPC ) == 0 {
for _ , arg := range addr . Arguments {
if arg . Name == rpc . RPC_DESTINATION_PORT && addr . Arguments . Has ( rpc . RPC_DESTINATION_PORT , rpc . DataUint64 ) {
transfers [ t ] . Payload_RPC = append ( transfers [ t ] . Payload_RPC , rpc . Argument { Name : rpc . RPC_DESTINATION_PORT , DataType : rpc . DataUint64 , Value : addr . Arguments . Value ( rpc . RPC_DESTINATION_PORT , rpc . DataUint64 ) . ( uint64 ) } )
continue
} else {
fmt . Printf ( "integrtated address, but don't know how to process\n" )
err = fmt . Errorf ( "integrated address used, but don't know how to process %+v" , addr . Arguments )
}
}
return
}
2021-12-04 16:42:11 +00:00
var dest_e * crypto . ElGamal
bits_needed [ 1 ] , _ , _ , dest_e , err = w . GetEncryptedBalanceAtTopoHeight ( transfers [ t ] . SCID , topoheight , addr . BaseAddress ( ) . String ( ) )
if err != nil {
fmt . Printf ( " t %d unregistered1 '%s' %s\n" , t , addr , err )
return
} else {
ring_balances = append ( ring_balances , dest_e . Serialize ( ) )
ring = append ( ring , addr . PublicKey . G1 ( ) )
}
/ * if len ( w . account . RingMembers ) < int ( ringsize ) {
err = fmt . Errorf ( "We do not have enough ring members, expecting alteast %d but have only %d" , int ( ringsize ) , len ( w . account . RingMembers ) )
return
} * /
receiver_without_payment_id := addr . BaseAddress ( )
//sending to self is not supported
if w . GetAddress ( ) . String ( ) == receiver_without_payment_id . String ( ) {
err = fmt . Errorf ( "Sending to self is not supported" )
return
}
deduplicator := map [ string ] bool { }
deduplicator [ receiver_without_payment_id . String ( ) ] = true
deduplicator [ w . GetAddress ( ) . String ( ) ] = true
for ringsize != 2 {
probable_members := w . Random_ring_members ( transfers [ t ] . SCID )
if len ( probable_members ) <= 40 { // we do not have enough ring members for sure, extract ring members from base
var zeroscid crypto . Hash
probable_members = w . Random_ring_members ( zeroscid )
}
for _ , k := range probable_members {
if _ , collision := deduplicator [ k ] ; collision {
continue
}
deduplicator [ k ] = true
if len ( ring_balances ) < int ( ringsize ) && k != receiver_without_payment_id . String ( ) && k != w . GetAddress ( ) . String ( ) {
var addr_member * rpc . Address
//fmt.Printf("t:%d len %d %s receiver %s sender %s\n",t,len(ring_balances), k, receiver_without_payment_id.String(), w.GetAddress().String())
var ebal * crypto . ElGamal
bits_needed [ len ( ring_balances ) ] , _ , _ , ebal , err = w . GetEncryptedBalanceAtTopoHeight ( transfers [ t ] . SCID , - 1 , k )
if err != nil {
fmt . Printf ( " unregistered %s\n" , k )
return
}
if addr_member , err = rpc . NewAddress ( k ) ; err != nil {
return
}
ring_balances = append ( ring_balances , ebal . Serialize ( ) )
ring = append ( ring , addr_member . PublicKey . G1 ( ) )
if len ( ring_balances ) == int ( ringsize ) {
goto ring_members_collected
}
}
}
}
ring_members_collected :
rings = append ( rings , ring )
rings_balances = append ( rings_balances , ring_balances )
max_bits := 0
for i := range bits_needed {
if max_bits < bits_needed [ i ] {
max_bits = bits_needed [ i ]
}
}
max_bits_array = append ( max_bits_array , max_bits )
}
max_bits := 0
for i := range max_bits_array {
if max_bits < max_bits_array [ i ] {
max_bits = max_bits_array [ i ]
}
}
max_bits += 6 // extra 6 bits
if ! dry_run {
2022-01-26 10:05:01 +00:00
tx = w . BuildTransaction ( transfers , rings_balances , rings , block_hash , height , scdata , treehash_raw , max_bits , gasstorage )
2021-12-04 16:42:11 +00:00
}
if tx == nil {
err = fmt . Errorf ( "somehow the tx could not be built, please retry" )
}
return
}