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 blockchain
import "fmt"
import "time"
/ * import "bytes"
import "encoding/binary"
import "github.com/romana/rlog"
* /
import "sync"
import "runtime/debug"
import "golang.org/x/xerrors"
import "github.com/deroproject/graviton"
import "github.com/deroproject/derohe/config"
import "github.com/deroproject/derohe/block"
import "github.com/deroproject/derohe/cryptography/crypto"
import "github.com/deroproject/derohe/transaction"
import "github.com/deroproject/derohe/cryptography/bn256"
//import "github.com/deroproject/derosuite/emission"
// caches x of transactions validity
// it is always atomic
// the cache is not txhash -> validity mapping
// instead it is txhash+expanded ringmembers
// if the entry exist, the tx is valid
// it stores special hash and first seen time
// this can only be used on expanded transactions
var transaction_valid_cache sync . Map
// this go routine continuously scans and cleans up the cache for expired entries
func clean_up_valid_cache ( ) {
for {
time . Sleep ( 3600 * time . Second )
current_time := time . Now ( )
// track propagation upto 10 minutes
transaction_valid_cache . Range ( func ( k , value interface { } ) bool {
first_seen := value . ( time . Time )
if current_time . Sub ( first_seen ) . Round ( time . Second ) . Seconds ( ) > 3600 {
transaction_valid_cache . Delete ( k )
}
return true
} )
}
}
/ * Coinbase transactions need to verify registration
* * /
func ( chain * Blockchain ) Verify_Transaction_Coinbase ( cbl * block . Complete_Block , minertx * transaction . Transaction ) ( err error ) {
if ! minertx . IsCoinbase ( ) { // transaction is not coinbase, return failed
return fmt . Errorf ( "tx is not coinbase" )
}
// make sure miner address is registered
_ , topos := chain . Store . Topo_store . binarySearchHeight ( int64 ( cbl . Bl . Height - 1 ) )
// load all db versions one by one and check whether the root hash matches the one mentioned in the tx
if len ( topos ) < 1 {
return fmt . Errorf ( "could not find previous height blocks %d" , cbl . Bl . Height - 1 )
}
var balance_tree * graviton . Tree
for i := range topos {
toporecord , err := chain . Store . Topo_store . Read ( topos [ i ] )
if err != nil {
return fmt . Errorf ( "could not read block at height %d due to error while obtaining toporecord topos %+v processing %d err:%s\n" , cbl . Bl . Height - 1 , topos , i , err )
}
ss , err := chain . Store . Balance_store . LoadSnapshot ( toporecord . State_Version )
if err != nil {
return err
}
if balance_tree , err = ss . GetTree ( config . BALANCE_TREE ) ; err != nil {
return err
}
if _ , err := balance_tree . Get ( minertx . MinerAddress [ : ] ) ; err != nil {
return fmt . Errorf ( "balance not obtained err %s\n" , err )
//return false
}
}
return nil // success comes last
}
// this checks the nonces of a tx agains the current chain state, this basically does a comparision of state trees in limited form
2021-11-12 18:18:42 +00:00
func ( chain * Blockchain ) Verify_Transaction_NonCoinbase_CheckNonce_Tips ( hf_version int64 , tx * transaction . Transaction , tips [ ] crypto . Hash ) ( err error ) {
2021-11-08 16:39:17 +00:00
var tx_hash crypto . Hash
defer func ( ) { // safety so if anything wrong happens, verification fails
if r := recover ( ) ; r != nil {
2021-11-10 11:04:22 +00:00
logger . V ( 1 ) . Error ( nil , "Recovered while verifying tx" , "txid" , tx_hash , "r" , r , "stack" , debug . Stack ( ) )
2021-11-08 16:39:17 +00:00
err = fmt . Errorf ( "Stack Trace %s" , debug . Stack ( ) )
}
} ( )
tx_hash = tx . GetHash ( )
if tx . TransactionType == transaction . REGISTRATION { // all other tx must be checked
return nil
}
if len ( tips ) < 1 {
return fmt . Errorf ( "no tips provided, cannot verify" )
}
// transaction needs to be expanded. this expansion needs balance state
2021-11-12 18:18:42 +00:00
version , err := chain . ReadBlockSnapshotVersion ( tx . BLID )
2021-11-08 16:39:17 +00:00
if err != nil {
return err
}
2021-11-12 18:18:42 +00:00
ss_tx , err := chain . Store . Balance_store . LoadSnapshot ( version )
2021-11-08 16:39:17 +00:00
if err != nil {
return err
}
2021-11-12 18:18:42 +00:00
var tx_balance_tree * graviton . Tree
2021-11-08 16:39:17 +00:00
if tx_balance_tree , err = ss_tx . GetTree ( config . BALANCE_TREE ) ; err != nil {
return err
}
if tx_balance_tree == nil {
return fmt . Errorf ( "mentioned balance tree not found, cannot verify TX" )
}
// now we must solve the tips, against which the nonces will be verified
for _ , tip := range tips {
var tip_balance_tree * graviton . Tree
2021-11-12 18:18:42 +00:00
version , err := chain . ReadBlockSnapshotVersion ( tip )
2021-11-08 16:39:17 +00:00
if err != nil {
return err
}
2021-11-12 18:18:42 +00:00
ss_tip , err := chain . Store . Balance_store . LoadSnapshot ( version )
2021-11-08 16:39:17 +00:00
if err != nil {
return err
}
if tip_balance_tree , err = ss_tip . GetTree ( config . BALANCE_TREE ) ; err != nil {
return err
}
if tip_balance_tree == nil {
return fmt . Errorf ( "mentioned tip balance tree not found, cannot verify TX" )
}
for t := range tx . Payloads {
parity := tx . Payloads [ t ] . Proof . Parity ( )
var tip_tree , tx_tree * graviton . Tree
if tx . Payloads [ t ] . SCID . IsZero ( ) { // choose whether we use main tree or sc tree
tip_tree = tip_balance_tree
tx_tree = tx_balance_tree
} else {
if tip_tree , err = ss_tip . GetTree ( string ( tx . Payloads [ t ] . SCID [ : ] ) ) ; err != nil {
return err
}
if tx_tree , err = ss_tx . GetTree ( string ( tx . Payloads [ t ] . SCID [ : ] ) ) ; err != nil {
return err
}
}
for i := 0 ; i < int ( tx . Payloads [ t ] . Statement . RingSize ) ; i ++ {
if ( i % 2 == 0 ) != parity { // this condition is well thought out and works good enough
continue
}
key_pointer := tx . Payloads [ t ] . Statement . Publickeylist_pointers [ i * int ( tx . Payloads [ t ] . Statement . Bytes_per_publickey ) : ( i + 1 ) * int ( tx . Payloads [ t ] . Statement . Bytes_per_publickey ) ]
_ , key_compressed , tx_balance_serialized , err := tx_tree . GetKeyValueFromHash ( key_pointer )
if err != nil && tx . Payloads [ t ] . SCID . IsZero ( ) {
return err
}
if err != nil && xerrors . Is ( err , graviton . ErrNotFound ) && ! tx . Payloads [ t ] . SCID . IsZero ( ) { // SC used a ring member not yet part
continue
}
var tx_nb , tip_nb crypto . NonceBalance
tx_nb . UnmarshalNonce ( tx_balance_serialized )
_ , _ , tip_balance_serialized , err := tip_tree . GetKeyValueFromKey ( key_compressed )
if err != nil && xerrors . Is ( err , graviton . ErrNotFound ) {
continue
}
if err != nil {
return err
}
tip_nb . UnmarshalNonce ( tip_balance_serialized )
//fmt.Printf("tx nonce %d tip nonce %d\n", tx_nb.NonceHeight, tip_nb.NonceHeight)
if tip_nb . NonceHeight > tx_nb . NonceHeight {
return fmt . Errorf ( "Invalid Nonce, not usable, expected %d actual %d" , tip_nb . NonceHeight , tx_nb . NonceHeight )
}
}
}
}
return nil
}
2021-11-12 18:18:42 +00:00
func ( chain * Blockchain ) Verify_Transaction_NonCoinbase ( tx * transaction . Transaction ) ( err error ) {
return chain . verify_Transaction_NonCoinbase_internal ( false , tx )
}
func ( chain * Blockchain ) Expand_Transaction_NonCoinbase ( tx * transaction . Transaction ) ( err error ) {
return chain . verify_Transaction_NonCoinbase_internal ( true , tx )
}
2021-11-08 16:39:17 +00:00
// all non miner tx must be non-coinbase tx
// each check is placed in a separate block of code, to avoid ambigous code or faulty checks
// all check are placed and not within individual functions ( so as we cannot skip a check )
// This function verifies tx fully, means all checks,
// if the transaction has passed the check it can be added to mempool, relayed or added to blockchain
// the transaction has already been deserialized thats it
// It also expands the transactions, using the repective state trie
2021-11-12 18:18:42 +00:00
func ( chain * Blockchain ) verify_Transaction_NonCoinbase_internal ( skip_proof bool , tx * transaction . Transaction ) ( err error ) {
2021-11-08 16:39:17 +00:00
var tx_hash crypto . Hash
defer func ( ) { // safety so if anything wrong happens, verification fails
if r := recover ( ) ; r != nil {
2021-11-10 11:04:22 +00:00
logger . V ( 1 ) . Error ( nil , "Recovered while verifying tx" , "txid" , tx_hash , "r" , r , "stack" , debug . Stack ( ) )
2021-11-08 16:39:17 +00:00
err = fmt . Errorf ( "Stack Trace %s" , debug . Stack ( ) )
}
} ( )
if tx . Version != 1 {
return fmt . Errorf ( "TX should be version 1" )
}
tx_hash = tx . GetHash ( )
if tx . TransactionType == transaction . REGISTRATION {
if _ , ok := transaction_valid_cache . Load ( tx_hash ) ; ok {
return nil //logger.Infof("Found in cache %s ",tx_hash)
} else {
//logger.Infof("TX not found in cache %s len %d ",tx_hash, len(tmp_buffer))
}
if tx . IsRegistrationValid ( ) {
transaction_valid_cache . Store ( tx_hash , time . Now ( ) ) // signature got verified, cache it
return nil
}
return fmt . Errorf ( "Registration has invalid signature" )
}
// currently we allow following types of transaction
if ! ( tx . TransactionType == transaction . NORMAL || tx . TransactionType == transaction . SC_TX || tx . TransactionType == transaction . BURN_TX ) {
return fmt . Errorf ( "Unknown transaction type" )
}
if tx . TransactionType == transaction . BURN_TX {
if tx . Value == 0 {
return fmt . Errorf ( "Burn Value cannot be zero" )
}
}
// avoid some bugs lurking elsewhere
if tx . Height != uint64 ( int64 ( tx . Height ) ) {
return fmt . Errorf ( "invalid tx height" )
}
if len ( tx . Payloads ) < 1 {
return fmt . Errorf ( "tx must have at least one payload" )
}
{ // we can not deduct fees, if no base, so make sure base is there
// this restriction should be lifted under suitable conditions
has_base := false
for i := range tx . Payloads {
if tx . Payloads [ i ] . SCID . IsZero ( ) {
has_base = true
}
}
if ! has_base {
return fmt . Errorf ( "tx does not contains base" )
}
}
2021-11-12 18:18:42 +00:00
for t := range tx . Payloads {
if tx . Payloads [ t ] . Statement . Roothash != tx . Payloads [ 0 ] . Statement . Roothash {
return fmt . Errorf ( "Roothash corrupted" )
}
}
2021-11-08 16:39:17 +00:00
for t := range tx . Payloads {
// check sanity
if tx . Payloads [ t ] . Statement . RingSize != uint64 ( len ( tx . Payloads [ t ] . Statement . Publickeylist_pointers ) / int ( tx . Payloads [ t ] . Statement . Bytes_per_publickey ) ) {
return fmt . Errorf ( "corrupted key pointers ringsize" )
}
if tx . Payloads [ t ] . Statement . RingSize < 2 { // ring size minimum 2
return fmt . Errorf ( "RingSize for %d statement cannot be less than 2 actual %d" , t , tx . Payloads [ t ] . Statement . RingSize )
}
if tx . Payloads [ t ] . Statement . RingSize > 128 { // ring size current limited to 128
return fmt . Errorf ( "RingSize for %d statement cannot be more than 128.Actual %d" , t , tx . Payloads [ t ] . Statement . RingSize )
}
if ! crypto . IsPowerOf2 ( len ( tx . Payloads [ t ] . Statement . Publickeylist_pointers ) / int ( tx . Payloads [ t ] . Statement . Bytes_per_publickey ) ) {
return fmt . Errorf ( "corrupted key pointers" )
}
// check duplicate ring members within the tx
{
key_map := map [ string ] bool { }
for i := 0 ; i < int ( tx . Payloads [ t ] . Statement . RingSize ) ; i ++ {
key_map [ string ( tx . Payloads [ t ] . Statement . Publickeylist_pointers [ i * int ( tx . Payloads [ t ] . Statement . Bytes_per_publickey ) : ( i + 1 ) * int ( tx . Payloads [ t ] . Statement . Bytes_per_publickey ) ] ) ] = true
}
if len ( key_map ) != int ( tx . Payloads [ t ] . Statement . RingSize ) {
return fmt . Errorf ( "key_map does not contain ringsize members, ringsize %d , bytesperkey %d data %x" , tx . Payloads [ t ] . Statement . RingSize , tx . Payloads [ t ] . Statement . Bytes_per_publickey , tx . Payloads [ t ] . Statement . Publickeylist_pointers [ : ] )
}
}
tx . Payloads [ t ] . Statement . CLn = tx . Payloads [ t ] . Statement . CLn [ : 0 ]
tx . Payloads [ t ] . Statement . CRn = tx . Payloads [ t ] . Statement . CRn [ : 0 ]
}
// transaction needs to be expanded. this expansion needs balance state
2021-11-12 18:18:42 +00:00
version , err := chain . ReadBlockSnapshotVersion ( tx . BLID )
if err != nil {
return err
2021-11-08 16:39:17 +00:00
}
2021-11-12 18:18:42 +00:00
hash , err := chain . Load_Merkle_Hash ( version )
2021-11-08 16:39:17 +00:00
if err != nil {
return err
}
2021-11-12 18:18:42 +00:00
if hash != tx . Payloads [ 0 ] . Statement . Roothash {
return fmt . Errorf ( "Tx statement roothash mismatch" )
}
// we have found the balance tree with which it was built now lets verify
ss , err := chain . Store . Balance_store . LoadSnapshot ( version )
2021-11-08 16:39:17 +00:00
if err != nil {
return err
}
2021-11-12 18:18:42 +00:00
var balance_tree * graviton . Tree
2021-11-08 16:39:17 +00:00
if balance_tree , err = ss . GetTree ( config . BALANCE_TREE ) ; err != nil {
return err
}
if balance_tree == nil {
return fmt . Errorf ( "mentioned balance tree not found, cannot verify TX" )
}
//logger.Infof("dTX state tree has been found")
trees := map [ crypto . Hash ] * graviton . Tree { }
var zerohash crypto . Hash
trees [ zerohash ] = balance_tree // initialize main tree by default
for t := range tx . Payloads {
tx . Payloads [ t ] . Statement . Publickeylist_compressed = tx . Payloads [ t ] . Statement . Publickeylist_compressed [ : 0 ]
tx . Payloads [ t ] . Statement . Publickeylist = tx . Payloads [ t ] . Statement . Publickeylist [ : 0 ]
var tree * graviton . Tree
if _ , ok := trees [ tx . Payloads [ t ] . SCID ] ; ok {
tree = trees [ tx . Payloads [ t ] . SCID ]
} else {
// fmt.Printf("SCID loading %s tree\n", tx.Payloads[t].SCID)
tree , _ = ss . GetTree ( string ( tx . Payloads [ t ] . SCID [ : ] ) )
trees [ tx . Payloads [ t ] . SCID ] = tree
}
// now lets calculate CLn and CRn
for i := 0 ; i < int ( tx . Payloads [ t ] . Statement . RingSize ) ; i ++ {
key_pointer := tx . Payloads [ t ] . Statement . Publickeylist_pointers [ i * int ( tx . Payloads [ t ] . Statement . Bytes_per_publickey ) : ( i + 1 ) * int ( tx . Payloads [ t ] . Statement . Bytes_per_publickey ) ]
_ , key_compressed , balance_serialized , err := tree . GetKeyValueFromHash ( key_pointer )
// if destination address could be found be found in sc balance tree, assume its zero balance
needs_init := false
if err != nil && ! tx . Payloads [ t ] . SCID . IsZero ( ) {
if xerrors . Is ( err , graviton . ErrNotFound ) { // if the address is not found, lookup in main tree
_ , key_compressed , _ , err = balance_tree . GetKeyValueFromHash ( key_pointer )
if err != nil {
return fmt . Errorf ( "balance not obtained err %s\n" , err )
}
needs_init = true
}
}
if err != nil {
return fmt . Errorf ( "balance not obtained err %s\n" , err )
}
// decode public key and expand
{
var p bn256 . G1
var pcopy [ 33 ] byte
copy ( pcopy [ : ] , key_compressed )
if err = p . DecodeCompressed ( key_compressed [ : ] ) ; err != nil {
return fmt . Errorf ( "key %d could not be decompressed" , i )
}
tx . Payloads [ t ] . Statement . Publickeylist_compressed = append ( tx . Payloads [ t ] . Statement . Publickeylist_compressed , pcopy )
tx . Payloads [ t ] . Statement . Publickeylist = append ( tx . Payloads [ t ] . Statement . Publickeylist , & p )
if needs_init {
var nb crypto . NonceBalance
nb . Balance = crypto . ConstructElGamal ( & p , crypto . ElGamal_BASE_G ) // init zero balance
balance_serialized = nb . Serialize ( )
}
}
var ll , rr bn256 . G1
nb := new ( crypto . NonceBalance ) . Deserialize ( balance_serialized )
ebalance := nb . Balance
ll . Add ( ebalance . Left , tx . Payloads [ t ] . Statement . C [ i ] )
tx . Payloads [ t ] . Statement . CLn = append ( tx . Payloads [ t ] . Statement . CLn , & ll )
rr . Add ( ebalance . Right , tx . Payloads [ t ] . Statement . D )
tx . Payloads [ t ] . Statement . CRn = append ( tx . Payloads [ t ] . Statement . CRn , & rr )
// prepare for another sub transaction
echanges := crypto . ConstructElGamal ( tx . Payloads [ t ] . Statement . C [ i ] , tx . Payloads [ t ] . Statement . D )
nb = new ( crypto . NonceBalance ) . Deserialize ( balance_serialized )
nb . Balance = nb . Balance . Add ( echanges ) // homomorphic addition of changes
tree . Put ( key_compressed , nb . Serialize ( ) ) // reserialize and store temporarily, tree will be discarded after verification
}
}
2021-11-12 18:18:42 +00:00
if _ , ok := transaction_valid_cache . Load ( tx_hash ) ; ok {
logger . V ( 2 ) . Info ( "Found in cache, skipping verification" , "txid" , tx_hash )
return nil
} else {
//logger.Infof("TX not found in cache %s len %d ",tx_hash, len(tmp_buffer))
}
if skip_proof {
return nil
}
2021-11-08 16:39:17 +00:00
// at this point TX has been completely expanded, verify the tx statement
scid_map := map [ crypto . Hash ] int { }
for t := range tx . Payloads {
index := scid_map [ tx . Payloads [ t ] . SCID ]
if ! tx . Payloads [ t ] . Proof . Verify ( tx . Payloads [ t ] . SCID , index , & tx . Payloads [ t ] . Statement , tx . GetHash ( ) , tx . Payloads [ t ] . BurnValue ) {
2021-11-12 18:18:42 +00:00
// fmt.Printf("Statement %+v\n", tx.Payloads[t].Statement)
// fmt.Printf("Proof %+v\n", tx.Payloads[t].Proof)
2021-11-08 16:39:17 +00:00
return fmt . Errorf ( "transaction statement %d verification failed" , t )
}
scid_map [ tx . Payloads [ t ] . SCID ] = scid_map [ tx . Payloads [ t ] . SCID ] + 1 // increment scid counter
}
// these transactions are done
if tx . TransactionType == transaction . NORMAL || tx . TransactionType == transaction . BURN_TX {
transaction_valid_cache . Store ( tx_hash , time . Now ( ) ) // signature got verified, cache it
return nil
}
// we reach here if tx proofs are valid
if tx . TransactionType != transaction . SC_TX {
return fmt . Errorf ( "non sc transaction should never reach here" )
}
//if !tx.IsRegistrationValid() {
// return fmt.Errorf("SC has invalid signature")
//}
return nil
}