// Copyright 2017-2018 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 // NOTE: this is extremely critical code ( as a single error or typo here will lead to invalid transactions ) // // thhis file implements code which controls output indexes // rewrites them during chain reorganisation import "fmt" //import "os" //import "io/ioutil" //import "sync" //import "encoding/binary" import "github.com/vmihailenco/msgpack" import "github.com/arnaucode/derosuite/config" import "github.com/arnaucode/derosuite/crypto" import "github.com/arnaucode/derosuite/globals" import "github.com/arnaucode/derosuite/crypto/ringct" import "github.com/arnaucode/derosuite/transaction" import "github.com/arnaucode/derosuite/walletapi" type Index_Data struct { InKey ringct.CtKey ECDHTuple ringct.ECdhTuple // encrypted Amounts // Key crypto.Hash // stealth address key // Commitment crypto.Hash // commitment public key Height uint64 // height to which this belongs Unlock_Height uint64 // height at which it will unlock } /* func (o *Index_Data) Serialize() (result []byte) { result = append(o.InKey.Destination[:], o.InKey.Mask[:]...) result = append(result, o.ECDHTuple.Mask[:]...) result = append(result, o.ECDHTuple.Amount[:]...) result = append(result, itob(o.Height)...) return } func (o *Index_Data) Deserialize(buf []byte) (err error) { if len(buf) != ( 32 + 32 + 32+ 32+8){ return fmt.Errorf("Output index needs to be 72 bytes in size but found to be %d bytes", len(buf)) } copy(o.InKey.Destination[:],buf[:32]) copy(o.InKey.Mask[:],buf[32:64]) copy(o.ECDHTuple.Mask[:],buf[64:96]) copy(o.ECDHTuple.Amount[:],buf[96:128]) o.Height = binary.BigEndian.Uint64(buf[64:]) return } */ var account walletapi.Account /* func init() { var err error account , err = wallet.Generate_Account_From_Recovery_Words("PLACE RECOVERY SEED here to test tx evaluation from within daemon") if err != nil { fmt.Printf("err %s\n",err) return } fmt.Printf("%+v\n", account) } */ // this function writes or overwrites the data related to outputs // the following data is collected from each output // the secret key, // the commitment ( for miner tx the commitment is created from scratch // 8 bytes blockheight to which this output belongs // this function should always succeed or panic showing something is not correct // NOTE: this function should only be called after all the tx and the block has been stored to DB func (chain *Blockchain) write_output_index(block_id crypto.Hash) { // load the block bl, err := chain.Load_BL_FROM_ID(block_id) if err != nil { logger.Warnf("No such block %s for writing output index", block_id) return } index_start := chain.Get_Block_Output_Index(block_id) // get index position height := chain.Load_Height_for_BL_ID(block_id) logger.Debugf("Writing Output Index for block %s height %d output index %d", block_id, height, index_start) // ads miner tx separately as a special case var o globals.TX_Output_Data var d Index_Data // extract key and commitment mask from for miner tx d.InKey.Destination = ringct.Key(bl.Miner_tx.Vout[0].Target.(transaction.Txout_to_key).Key) // mask can be calculated for miner tx on the wallet side as below d.InKey.Mask = ringct.ZeroCommitment_From_Amount(bl.Miner_tx.Vout[0].Amount) d.Height = height d.Unlock_Height = height + config.MINER_TX_AMOUNT_UNLOCK o.TXID = bl.Miner_tx.GetHash() o.InKey.Destination = ringct.Key(bl.Miner_tx.Vout[0].Target.(transaction.Txout_to_key).Key) o.InKey.Mask = ringct.ZeroCommitment_From_Amount(bl.Miner_tx.Vout[0].Amount) o.Height = height o.Unlock_Height = 0 // miner tx caannot be locked o.Index_within_tx = 0 o.Index_Global = index_start o.Amount = bl.Miner_tx.Vout[0].Amount o.SigType = 0 o.Block_Time = bl.Timestamp //ECDHTuple & sender pk is not available for miner tx if bl.Miner_tx.Parse_Extra() { o.Tx_Public_Key = bl.Miner_tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key) //o.Derivation_Public_Key_From_Vout = bl.Miner_tx.Vout[0].Target.(transaction.Txout_to_key).Key /* * PRE-WALLET code, can be used to track down bugs in wallet if account.Is_Output_Ours(bl.Miner_tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key),0, bl.Miner_tx.Vout[0].Target.(transaction.Txout_to_key).Key){ logger.Warnf("Miner Output is ours in tx %s height %d",bl.Miner_tx.GetHash(),height) } */ } serialized, err := msgpack.Marshal(&o) if err != nil { panic(err) } //fmt.Printf("index %d %x\n",index_start,d.InKey.Destination) // store the index and relevant keys together in compact form chain.store.StoreObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(index_start), serialized) index_start++ // now loops through all the transactions, and store there ouutputs also for i := 0; i < len(bl.Tx_hashes); i++ { // load all tx one by one tx, err := chain.Load_TX_FROM_ID(bl.Tx_hashes[i]) if err != nil { panic(fmt.Errorf("Cannot load tx for %x err %s", bl.Tx_hashes[i], err)) } //fmt.Printf("tx %s",bl.Tx_hashes[i]) index_within_tx := uint64(0) o.TXID = bl.Tx_hashes[i] o.Height = height o.SigType = uint64(tx.RctSignature.Get_Sig_Type()) // TODO unlock specific outputs on specific height o.Unlock_Height = height + config.NORMAL_TX_AMOUNT_UNLOCK // build the key image list and pack it for j := 0; j < len(tx.Vin); j++ { k_image := ringct.Key(tx.Vin[j].(transaction.Txin_to_key).K_image) o.Key_Images = append(o.Key_Images, crypto.Key(k_image)) } extra_parsed := tx.Parse_Extra() // tx has been loaded, now lets get the vout for j := uint64(0); j < uint64(len(tx.Vout)); j++ { //fmt.Printf("Processing vout %d\n", j) d.InKey.Destination = ringct.Key(tx.Vout[j].Target.(transaction.Txout_to_key).Key) d.InKey.Mask = ringct.Key(tx.RctSignature.OutPk[j].Mask) o.InKey.Destination = ringct.Key(tx.Vout[j].Target.(transaction.Txout_to_key).Key) o.InKey.Mask = ringct.Key(tx.RctSignature.OutPk[j].Mask) o.ECDHTuple = tx.RctSignature.ECdhInfo[j] o.Index_within_tx = index_within_tx o.Index_Global = index_start o.Amount = tx.Vout[j].Amount o.Unlock_Height = 0 if j == 0 && tx.Unlock_Time != 0 { // only first output of a TX can be locked o.Unlock_Height = tx.Unlock_Time } // include the key image list in the first output itself // rest all the outputs donot contain the keyimage if j != 0 && len(o.Key_Images) > 0 { o.Key_Images = o.Key_Images[:0] } if extra_parsed { o.Tx_Public_Key = tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key) /* during emergency, for debugging purpose only NOTE: remove this before rekeasing code 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){ 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) account.Decode_RingCT_Output(tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key), j, crypto.Key(tx.RctSignature.OutPk[j].Mask), tx.RctSignature.ECdhInfo[j], 2) } */ } serialized, err := msgpack.Marshal(&o) if err != nil { panic(err) } chain.store.StoreObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(index_start), serialized) // fmt.Printf("index %d %x\n",index_start,d.InKey.Destination) index_start++ index_within_tx++ } } } // this will load the index data for specific index // this should be done while holding the chain lock, // since during reorganisation we might give out wrong keys, // to avoid that pitfall take the chain lock // NOTE: this function is now for internal use only by the blockchain itself // func (chain *Blockchain) load_output_index(index uint64) (idata globals.TX_Output_Data) { // chain.Lock() // defer chain.Unlock() data_bytes, err := chain.store.LoadObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(index)) if err != nil { logger.Warnf("err while loading output index data index = %d err %s", index, err) return } err = msgpack.Unmarshal(data_bytes, &idata) if err != nil { logger.Warnf("err while unmarshallin output index data index = %d data_len %d err %s", index, len(data_bytes), err) return } return } // this will read the output index data but will not deserialize it // this is exposed for rpcserver giving access to wallet func (chain *Blockchain) Read_output_index(index uint64) (data_bytes []byte, err error) { chain.Lock() defer chain.Unlock() data_bytes, err = chain.store.LoadObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(index)) if err != nil { logger.Warnf("err while loading output index data index = %d err %s", index, err) return } return data_bytes, err } // this function finds output index for the tx // first find a block index , and get the start offset // then loop the index till you find the key in the result // if something is not right, we return 0 func (chain *Blockchain) Find_TX_Output_Index(tx_hash crypto.Hash) (offset uint64) { Block_Height := chain.Load_TX_Height(tx_hash) // get height block_id, err := chain.Load_BL_ID_at_Height(Block_Height) if err != nil { logger.Warnf("error while finding tx_output_index %s", tx_hash) return 0 } block_index_start := chain.Get_Block_Output_Index(block_id) // output_max_count := chain.Block_Count_Vout(block_id) // this function will load/serdes all tx contained within block /*for index_start:= block_index_start; index_start < (block_index_start+output_max_count); index_start++{ } */ bl, err := chain.Load_BL_FROM_ID(block_id) if err != nil { logger.Warnf("Cannot load block for %s err %s", block_id, err) return } if tx_hash == bl.Miner_tx.GetHash() { return block_index_start } offset = block_index_start + 1 // shift by 1 for i := 0; i < len(bl.Tx_hashes); i++ { // load all tx one by one if bl.Tx_hashes[i] == tx_hash { return offset } tx, err := chain.Load_TX_FROM_ID(bl.Tx_hashes[i]) if err != nil { logger.Warnf("Cannot load tx for %s err %s", bl.Tx_hashes[i], err) } // tx has been loaded, now lets get the vout vout_count := uint64(len(tx.Vout)) offset += vout_count } // we will reach here only if tx is linked to wrong block // this may be possible during reorganisation // return 0 logger.Warnf("Index Position must never reach here") return 0 }