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 }