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

     1  // Copyright 2017-2018 DERO Project. All rights reserved.
     2  // Use of this source code in any form is governed by RESEARCH license.
     3  // license can be found in the LICENSE file.
     4  // GPG: 0F39 E425 8C65 3947 702A  8234 08B2 0360 A03A 9DE8
     5  //
     6  //
     7  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
     8  // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     9  // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
    10  // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    11  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    12  // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    13  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    14  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
    15  // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    16  
    17  package blockchain
    18  
    19  // NOTE: this is extremely critical code ( as a single error or typo here will lead to invalid transactions )
    20  //
    21  // thhis file implements code which controls output indexes
    22  // rewrites them during chain reorganisation
    23  import "fmt"
    24  
    25  //import "os"
    26  //import "io/ioutil"
    27  //import "sync"
    28  //import "encoding/binary"
    29  
    30  import "github.com/romana/rlog"
    31  import "github.com/vmihailenco/msgpack"
    32  
    33  import "github.com/deroproject/derosuite/config"
    34  import "github.com/deroproject/derosuite/crypto"
    35  import "github.com/deroproject/derosuite/globals"
    36  import "github.com/deroproject/derosuite/storage"
    37  import "github.com/deroproject/derosuite/crypto/ringct"
    38  import "github.com/deroproject/derosuite/transaction"
    39  
    40  //import "github.com/deroproject/derosuite/walletapi"
    41  
    42  type Index_Data struct {
    43  	InKey     ringct.CtKey
    44  	ECDHTuple ringct.ECdhTuple // encrypted Amounts
    45  	// Key crypto.Hash  // stealth address key
    46  	// Commitment crypto.Hash // commitment public key
    47  	Height        uint64 // height to which this belongs
    48  	Unlock_Height uint64 // height at which it will unlock
    49  }
    50  
    51  /*
    52  func (o *Index_Data) Serialize() (result []byte) {
    53  	result = append(o.InKey.Destination[:], o.InKey.Mask[:]...)
    54          result = append(result, o.ECDHTuple.Mask[:]...)
    55          result = append(result, o.ECDHTuple.Amount[:]...)
    56         result = append(result, itob(o.Height)...)
    57  	return
    58  }
    59  
    60  func (o *Index_Data) Deserialize(buf []byte) (err error) {
    61          if len(buf) != ( 32 + 32 + 32+ 32+8){
    62              return fmt.Errorf("Output index needs to be 72 bytes in size but found to be %d bytes", len(buf))
    63          }
    64          copy(o.InKey.Destination[:],buf[:32])
    65          copy(o.InKey.Mask[:],buf[32:64])
    66          copy(o.ECDHTuple.Mask[:],buf[64:96])
    67          copy(o.ECDHTuple.Amount[:],buf[96:128])
    68          o.Height = binary.BigEndian.Uint64(buf[64:])
    69  	return
    70  }
    71  */
    72  
    73  //var account walletapi.Account
    74  
    75  /*
    76  func init() {
    77  
    78      var err error
    79      account , err =  wallet.Generate_Account_From_Recovery_Words("PLACE RECOVERY SEED here to test tx evaluation from within daemon")
    80  
    81      if err != nil {
    82          fmt.Printf("err %s\n",err)
    83          return
    84      }
    85  
    86      fmt.Printf("%+v\n", account)
    87  }
    88  */
    89  
    90  // this function writes or overwrites the data related to outputs
    91  // the following data is collected from each output
    92  // the secret key,
    93  // the commitment  ( for miner tx the commitment is created from scratch
    94  // 8 bytes blockheight to which this output belongs
    95  // this function should always succeed or panic showing something is not correct
    96  // NOTE: this function should only be called after all the tx and the block has been stored to DB
    97  func (chain *Blockchain) write_output_index(dbtx storage.DBTX, block_id crypto.Hash, index_start int64, hard_fork_version_current int64) (result bool) {
    98  
    99  	// load the block
   100  	bl, err := chain.Load_BL_FROM_ID(dbtx, block_id)
   101  	if err != nil {
   102  		logger.Warnf("No such block %s for writing output index", block_id)
   103  		return
   104  	}
   105  
   106  	//index_start := chain.Get_Block_Output_Index(dbtx,block_id) // get index position
   107  	// load topo height
   108  	height := chain.Load_Height_for_BL_ID(dbtx, block_id)
   109  
   110  	// this was for quick tetsing of wallet
   111  	//index_start =  uint64(height) //
   112  
   113  	rlog.Debugf("Writing Output Index for block %s height %d output index %d", block_id, height, index_start)
   114  
   115  	dbtx.StoreUint64(BLOCKCHAIN_UNIVERSE, GALAXY_BLOCK, block_id[:], PLANET_OUTPUT_INDEX, uint64(index_start))
   116  
   117  	// ads miner tx separately as a special case
   118  	var o globals.TX_Output_Data
   119  	var d Index_Data
   120  
   121  	// extract key and commitment mask from for miner tx
   122  	//	d.InKey.Destination = ringct.Key(bl.Miner_TX.Vout[0].Target.(transaction.Txout_to_key).Key)
   123  
   124  	// mask can be calculated for miner tx on the wallet side as below
   125  	//	d.InKey.Mask = ringct.ZeroCommitment_From_Amount(bl.Miner_TX.Vout[0].Amount)
   126  	//	d.Height = uint64(height)
   127  	//	d.Unlock_Height = uint64(height) + config.MINER_TX_AMOUNT_UNLOCK
   128  
   129  	minertx_reward, err := dbtx.LoadUint64(BLOCKCHAIN_UNIVERSE, GALAXY_BLOCK, block_id[:], PLANET_MINERTX_REWARD)
   130  	if err != nil {
   131  		logger.Fatalf("Base Reward is not stored in DB block %s", block_id)
   132  
   133  		return
   134  	}
   135  	o.BLID = block_id // store block id
   136  	o.TXID = bl.Miner_TX.GetHash()
   137  	o.InKey.Destination = crypto.Key(bl.Miner_TX.Vout[0].Target.(transaction.Txout_to_key).Key)
   138  
   139  	// FIXME miner tx amount should be what we calculated
   140  	//	o.InKey.Mask = ringct.ZeroCommitment_From_Amount(bl.Miner_TX.Vout[0].Amount)
   141  	o.InKey.Mask = ringct.ZeroCommitment_From_Amount(minertx_reward)
   142  	o.Height = uint64(height)
   143  	o.Unlock_Height = 0 // miner tx cannot be locked
   144  	o.Index_within_tx = 0
   145  	o.Index_Global = uint64(index_start)
   146  	//	o.Amount = bl.Miner_TX.Vout[0].Amount
   147  	o.Amount = minertx_reward
   148  	o.SigType = 0
   149  	o.Block_Time = bl.Timestamp
   150  	o.TopoHeight = chain.Load_Block_Topological_order(dbtx, block_id)
   151  
   152  	//ECDHTuple & sender pk is not available for miner tx
   153  
   154  	if bl.Miner_TX.Parse_Extra() {
   155  
   156  		// store public key if present
   157  		if _, ok := bl.Miner_TX.Extra_map[transaction.TX_PUBLIC_KEY]; ok {
   158  			o.Tx_Public_Key = bl.Miner_TX.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key)
   159  		}
   160  
   161  		//o.Derivation_Public_Key_From_Vout = bl.Miner_tx.Vout[0].Target.(transaction.Txout_to_key).Key
   162  
   163  	}
   164  
   165  	serialized, err := msgpack.Marshal(&o)
   166  	if err != nil {
   167  		panic(err)
   168  	}
   169  
   170  	//fmt.Printf("index %d  %x\n",index_start,d.InKey.Destination)
   171  
   172  	// store the index and relevant keys together in compact form
   173  	dbtx.StoreObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(uint64(index_start)), serialized)
   174  
   175  	index_start++
   176  
   177  	// now loops through all the transactions, and store there ouutputs also
   178  	// however as per client protocol, only process accepted transactions
   179  	for i := 0; i < len(bl.Tx_hashes); i++ { // load all tx one by one
   180  
   181  		if !chain.IS_TX_Valid(dbtx, block_id, bl.Tx_hashes[i]) { // skip invalid TX
   182  			rlog.Tracef(1, "bl %s tx %s ignored while building outputs index as per client protocol", block_id, bl.Tx_hashes[i])
   183  			continue
   184  		}
   185  		rlog.Tracef(1, "bl %s tx %s is being used while building outputs index as per client protocol", block_id, bl.Tx_hashes[i])
   186  
   187  		tx, err := chain.Load_TX_FROM_ID(dbtx, bl.Tx_hashes[i])
   188  		if err != nil {
   189  			panic(fmt.Errorf("Cannot load  tx for %x err %s", bl.Tx_hashes[i], err))
   190  		}
   191  
   192  		//fmt.Printf("tx %s",bl.Tx_hashes[i])
   193  		index_within_tx := uint64(0)
   194  
   195  		o.BLID = block_id // store block id
   196  		o.TXID = bl.Tx_hashes[i]
   197  		o.Height = uint64(height)
   198  		o.SigType = uint64(tx.RctSignature.Get_Sig_Type())
   199  
   200  		// TODO unlock specific outputs on specific height
   201  		o.Unlock_Height = uint64(height) + config.NORMAL_TX_AMOUNT_UNLOCK
   202  
   203  		// build the key image list and pack it
   204  		for j := 0; j < len(tx.Vin); j++ {
   205  			k_image := crypto.Key(tx.Vin[j].(transaction.Txin_to_key).K_image)
   206  			o.Key_Images = append(o.Key_Images, crypto.Key(k_image))
   207  		}
   208  
   209  		// zero out fields between tx
   210  		o.Tx_Public_Key = crypto.Key(ZERO_HASH)
   211  		o.PaymentID = o.PaymentID[:0]
   212  
   213  		extra_parsed := tx.Parse_Extra()
   214  
   215  		// tx has been loaded, now lets get the vout
   216  		for j := uint64(0); j < uint64(len(tx.Vout)); j++ {
   217  
   218  			//fmt.Printf("Processing vout %d\n", j)
   219  			d.InKey.Destination = crypto.Key(tx.Vout[j].Target.(transaction.Txout_to_key).Key)
   220  			d.InKey.Mask = crypto.Key(tx.RctSignature.OutPk[j].Mask)
   221  
   222  			o.InKey.Destination = crypto.Key(tx.Vout[j].Target.(transaction.Txout_to_key).Key)
   223  			o.InKey.Mask = crypto.Key(tx.RctSignature.OutPk[j].Mask)
   224  
   225  			o.ECDHTuple = tx.RctSignature.ECdhInfo[j]
   226  
   227  			o.Index_within_tx = index_within_tx
   228  			o.Index_Global = uint64(index_start)
   229  			o.Amount = tx.Vout[j].Amount
   230  			o.Unlock_Height = 0
   231  
   232  			if j == 0 && tx.Unlock_Time != 0 { // only first output of a TX can be locked
   233  				o.Unlock_Height = tx.Unlock_Time
   234  			}
   235  
   236  			if hard_fork_version_current >= 3 && o.Unlock_Height  != 0 {
   237  				if o.Unlock_Height < config.CRYPTONOTE_MAX_BLOCK_NUMBER {
   238  					if o.Unlock_Height  < (o.Height + 1000) {
   239  						o.Unlock_Height = o.Height + 1000
   240  					}
   241  				}else{
   242  					if o.Unlock_Height < (o.Block_Time + 12000) {
   243  						 o.Unlock_Height = o.Block_Time + 12000
   244  					}
   245  				}
   246  			}
   247  
   248  			// include the key image list in the first output itself
   249  			// rest all the outputs donot contain the keyimage
   250  			if j != 0 && len(o.Key_Images) > 0 {
   251  				o.Key_Images = o.Key_Images[:0]
   252  			}
   253  
   254  			if extra_parsed {
   255  				// store public key if present
   256  				if _, ok := tx.Extra_map[transaction.TX_PUBLIC_KEY]; ok {
   257  					o.Tx_Public_Key = tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key)
   258  				}
   259  
   260  				// store payment IDs if present
   261  				if _, ok := tx.PaymentID_map[transaction.TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID]; ok {
   262  					o.PaymentID = tx.PaymentID_map[transaction.TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID].([]byte)
   263  				} else if _, ok := tx.PaymentID_map[transaction.TX_EXTRA_NONCE_PAYMENT_ID]; ok {
   264  					o.PaymentID = tx.PaymentID_map[transaction.TX_EXTRA_NONCE_PAYMENT_ID].([]byte)
   265  				}
   266  
   267  				/*   during emergency, for debugging purpose only
   268  				     NOTE: remove this before rekeasing code
   269  
   270  				                        if account.Is_Output_Ours(tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key),index_within_tx, tx.Vout[j].Target.(transaction.Txout_to_key).Key){
   271  				                            logger.Warnf("MG/simple Output is ours in tx %s at index %d height %d  global index %d",bl.Tx_hashes[i],index_within_tx,height, o.Index_Global)
   272  
   273  				                            account.Decode_RingCT_Output(tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key),
   274  				                                                 j,
   275  				                                                 crypto.Key(tx.RctSignature.OutPk[j].Mask),
   276  				                                                 tx.RctSignature.ECdhInfo[j],
   277  				                                                 2)
   278  				                    }
   279  				*/
   280  			}
   281  
   282  			serialized, err := msgpack.Marshal(&o)
   283  			if err != nil {
   284  				panic(err)
   285  			}
   286  
   287  			dbtx.StoreObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(uint64(index_start)), serialized)
   288  
   289  			// fmt.Printf("index %d  %x\n",index_start,d.InKey.Destination)
   290  			index_start++
   291  			index_within_tx++
   292  		}
   293  
   294  	}
   295  
   296  	// store where the look up ends
   297  	dbtx.StoreUint64(BLOCKCHAIN_UNIVERSE, GALAXY_BLOCK, block_id[:], PLANET_OUTPUT_INDEX_END, uint64(index_start))
   298  
   299  	return true
   300  }
   301  
   302  // this will load the index  data for specific index
   303  // this should be done while holding the chain lock,
   304  // since during reorganisation we might  give out wrong keys,
   305  // to avoid that pitfall take the chain lock
   306  // NOTE: this function is now for internal use only by the blockchain itself
   307  //
   308  func (chain *Blockchain) load_output_index(dbtx storage.DBTX, index uint64) (idata globals.TX_Output_Data, success bool) {
   309  	// chain.Lock()
   310  	// defer chain.Unlock()
   311  
   312  	success = false
   313  	data_bytes, err := dbtx.LoadObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(index))
   314  
   315  	if err != nil {
   316  		logger.Warnf("err while loading output index data index = %d err %s", index, err)
   317  		success = false
   318  		return
   319  	}
   320  
   321  	err = msgpack.Unmarshal(data_bytes, &idata)
   322  	if err != nil {
   323  		rlog.Warnf("err while unmarshallin output index data index = %d  data_len %d err %s", index, len(data_bytes), err)
   324  		success = false
   325  		return
   326  	}
   327  
   328  	success = true
   329  	return
   330  }
   331  
   332  // this will read the output index data but will not deserialize it
   333  // this is exposed for rpcserver giving access to wallet
   334  func (chain *Blockchain) Read_output_index(dbtx storage.DBTX, index uint64) (data_bytes []byte, err error) {
   335  
   336  	if dbtx == nil {
   337  		dbtx, err = chain.store.BeginTX(false)
   338  		if err != nil {
   339  			rlog.Warnf("Error obtaining read-only tx. Error opening writable TX, err %s", err)
   340  			return
   341  		}
   342  
   343  		defer dbtx.Rollback()
   344  
   345  	}
   346  
   347  	data_bytes, err = dbtx.LoadObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(index))
   348  
   349  	if err != nil {
   350  		rlog.Warnf("err while loading output index data index = %d err %s", index, err)
   351  		return
   352  	}
   353  	return data_bytes, err
   354  }
   355  
   356  // this function finds output index for the tx
   357  // first find a block index , and get the start offset
   358  // then loop the index till you find the key in the result
   359  // if something is not right, we return 0
   360  
   361  func (chain *Blockchain) Find_TX_Output_Index(tx_hash crypto.Hash) (offset int64) {
   362  	topo_Height := chain.Load_TX_Height(nil, tx_hash) // get height at which it's mined
   363  
   364  	block_id, err := chain.Load_Block_Topological_order_at_index(nil, topo_Height)
   365  
   366  	if err != nil {
   367  		rlog.Warnf("error while finding tx_output_index %s", tx_hash)
   368  		return 0
   369  	}
   370  
   371  	block_index_start, _ := chain.Get_Block_Output_Index(nil, block_id)
   372  
   373  	// output_max_count := chain.Block_Count_Vout(block_id)  // this function will load/serdes all tx contained within block
   374  
   375  	bl, err := chain.Load_BL_FROM_ID(nil, block_id)
   376  	if err != nil {
   377  		rlog.Warnf("Cannot load  block for %s err %s", block_id, err)
   378  		return
   379  	}
   380  
   381  	if tx_hash == bl.Miner_TX.GetHash() { // miner tx is the beginning point
   382  		return block_index_start
   383  	}
   384  
   385  	offset = block_index_start + 1 // shift by 1
   386  
   387  	for i := 0; i < len(bl.Tx_hashes); i++ { // load all tx one by one
   388  
   389  		// follow client protocol and skip some transactions
   390  		if !chain.IS_TX_Valid(nil, block_id, bl.Tx_hashes[i]) { // skip invalid TX
   391  			continue
   392  		}
   393  
   394  		if bl.Tx_hashes[i] == tx_hash {
   395  			return offset
   396  		}
   397  		tx, err := chain.Load_TX_FROM_ID(nil, bl.Tx_hashes[i])
   398  		if err != nil {
   399  			rlog.Warnf("Cannot load  tx for %s err %s", bl.Tx_hashes[i], err)
   400  		}
   401  
   402  		// tx has been loaded, now lets get the vout
   403  		vout_count := int64(len(tx.Vout))
   404  		offset += vout_count
   405  	}
   406  
   407  	// we will reach here only if tx is linked to wrong block
   408  	// this may be possible during reorganisation
   409  	// return 0
   410  	//logger.Warnf("Index Position must never reach here")
   411  	return -1
   412  }