You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

336 lines
12 KiB

  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. package blockchain
  17. // NOTE: this is extremely critical code ( as a single error or typo here will lead to invalid transactions )
  18. //
  19. // thhis file implements code which controls output indexes
  20. // rewrites them during chain reorganisation
  21. import "fmt"
  22. //import "os"
  23. //import "io/ioutil"
  24. //import "sync"
  25. //import "encoding/binary"
  26. import "github.com/vmihailenco/msgpack"
  27. import "github.com/deroproject/derosuite/config"
  28. import "github.com/deroproject/derosuite/crypto"
  29. import "github.com/deroproject/derosuite/globals"
  30. import "github.com/deroproject/derosuite/crypto/ringct"
  31. import "github.com/deroproject/derosuite/transaction"
  32. import "github.com/deroproject/derosuite/walletapi"
  33. type Index_Data struct {
  34. InKey ringct.CtKey
  35. ECDHTuple ringct.ECdhTuple // encrypted Amounts
  36. // Key crypto.Hash // stealth address key
  37. // Commitment crypto.Hash // commitment public key
  38. Height uint64 // height to which this belongs
  39. Unlock_Height uint64 // height at which it will unlock
  40. }
  41. /*
  42. func (o *Index_Data) Serialize() (result []byte) {
  43. result = append(o.InKey.Destination[:], o.InKey.Mask[:]...)
  44. result = append(result, o.ECDHTuple.Mask[:]...)
  45. result = append(result, o.ECDHTuple.Amount[:]...)
  46. result = append(result, itob(o.Height)...)
  47. return
  48. }
  49. func (o *Index_Data) Deserialize(buf []byte) (err error) {
  50. if len(buf) != ( 32 + 32 + 32+ 32+8){
  51. return fmt.Errorf("Output index needs to be 72 bytes in size but found to be %d bytes", len(buf))
  52. }
  53. copy(o.InKey.Destination[:],buf[:32])
  54. copy(o.InKey.Mask[:],buf[32:64])
  55. copy(o.ECDHTuple.Mask[:],buf[64:96])
  56. copy(o.ECDHTuple.Amount[:],buf[96:128])
  57. o.Height = binary.BigEndian.Uint64(buf[64:])
  58. return
  59. }
  60. */
  61. var account walletapi.Account
  62. /*
  63. func init() {
  64. var err error
  65. account , err = wallet.Generate_Account_From_Recovery_Words("PLACE RECOVERY SEED here to test tx evaluation from within daemon")
  66. if err != nil {
  67. fmt.Printf("err %s\n",err)
  68. return
  69. }
  70. fmt.Printf("%+v\n", account)
  71. }
  72. */
  73. // this function writes or overwrites the data related to outputs
  74. // the following data is collected from each output
  75. // the secret key,
  76. // the commitment ( for miner tx the commitment is created from scratch
  77. // 8 bytes blockheight to which this output belongs
  78. // this function should always succeed or panic showing something is not correct
  79. // NOTE: this function should only be called after all the tx and the block has been stored to DB
  80. func (chain *Blockchain) write_output_index(block_id crypto.Hash) {
  81. // load the block
  82. bl, err := chain.Load_BL_FROM_ID(block_id)
  83. if err != nil {
  84. logger.Warnf("No such block %s for writing output index", block_id)
  85. return
  86. }
  87. index_start := chain.Get_Block_Output_Index(block_id) // get index position
  88. height := chain.Load_Height_for_BL_ID(block_id)
  89. logger.Debugf("Writing Output Index for block %s height %d output index %d", block_id, height, index_start)
  90. // ads miner tx separately as a special case
  91. var o globals.TX_Output_Data
  92. var d Index_Data
  93. // extract key and commitment mask from for miner tx
  94. d.InKey.Destination = ringct.Key(bl.Miner_tx.Vout[0].Target.(transaction.Txout_to_key).Key)
  95. // mask can be calculated for miner tx on the wallet side as below
  96. d.InKey.Mask = ringct.ZeroCommitment_From_Amount(bl.Miner_tx.Vout[0].Amount)
  97. d.Height = height
  98. d.Unlock_Height = height + config.MINER_TX_AMOUNT_UNLOCK
  99. o.TXID = bl.Miner_tx.GetHash()
  100. o.InKey.Destination = ringct.Key(bl.Miner_tx.Vout[0].Target.(transaction.Txout_to_key).Key)
  101. o.InKey.Mask = ringct.ZeroCommitment_From_Amount(bl.Miner_tx.Vout[0].Amount)
  102. o.Height = height
  103. o.Unlock_Height = 0 // miner tx caannot be locked
  104. o.Index_within_tx = 0
  105. o.Index_Global = index_start
  106. o.Amount = bl.Miner_tx.Vout[0].Amount
  107. o.SigType = 0
  108. o.Block_Time = bl.Timestamp
  109. //ECDHTuple & sender pk is not available for miner tx
  110. if bl.Miner_tx.Parse_Extra() {
  111. o.Tx_Public_Key = bl.Miner_tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key)
  112. //o.Derivation_Public_Key_From_Vout = bl.Miner_tx.Vout[0].Target.(transaction.Txout_to_key).Key
  113. /*
  114. * PRE-WALLET code, can be used to track down bugs in wallet
  115. 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){
  116. logger.Warnf("Miner Output is ours in tx %s height %d",bl.Miner_tx.GetHash(),height)
  117. }
  118. */
  119. }
  120. serialized, err := msgpack.Marshal(&o)
  121. if err != nil {
  122. panic(err)
  123. }
  124. //fmt.Printf("index %d %x\n",index_start,d.InKey.Destination)
  125. // store the index and relevant keys together in compact form
  126. chain.store.StoreObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(index_start), serialized)
  127. index_start++
  128. // now loops through all the transactions, and store there ouutputs also
  129. for i := 0; i < len(bl.Tx_hashes); i++ { // load all tx one by one
  130. tx, err := chain.Load_TX_FROM_ID(bl.Tx_hashes[i])
  131. if err != nil {
  132. panic(fmt.Errorf("Cannot load tx for %x err %s", bl.Tx_hashes[i], err))
  133. }
  134. //fmt.Printf("tx %s",bl.Tx_hashes[i])
  135. index_within_tx := uint64(0)
  136. o.TXID = bl.Tx_hashes[i]
  137. o.Height = height
  138. o.SigType = uint64(tx.RctSignature.Get_Sig_Type())
  139. // TODO unlock specific outputs on specific height
  140. o.Unlock_Height = height + config.NORMAL_TX_AMOUNT_UNLOCK
  141. // build the key image list and pack it
  142. for j := 0; j < len(tx.Vin); j++ {
  143. k_image := ringct.Key(tx.Vin[j].(transaction.Txin_to_key).K_image)
  144. o.Key_Images = append(o.Key_Images, crypto.Key(k_image))
  145. }
  146. extra_parsed := tx.Parse_Extra()
  147. // tx has been loaded, now lets get the vout
  148. for j := uint64(0); j < uint64(len(tx.Vout)); j++ {
  149. //fmt.Printf("Processing vout %d\n", j)
  150. d.InKey.Destination = ringct.Key(tx.Vout[j].Target.(transaction.Txout_to_key).Key)
  151. d.InKey.Mask = ringct.Key(tx.RctSignature.OutPk[j].Mask)
  152. o.InKey.Destination = ringct.Key(tx.Vout[j].Target.(transaction.Txout_to_key).Key)
  153. o.InKey.Mask = ringct.Key(tx.RctSignature.OutPk[j].Mask)
  154. o.ECDHTuple = tx.RctSignature.ECdhInfo[j]
  155. o.Index_within_tx = index_within_tx
  156. o.Index_Global = index_start
  157. o.Amount = tx.Vout[j].Amount
  158. o.Unlock_Height = 0
  159. if j == 0 && tx.Unlock_Time != 0 { // only first output of a TX can be locked
  160. o.Unlock_Height = tx.Unlock_Time
  161. }
  162. // include the key image list in the first output itself
  163. // rest all the outputs donot contain the keyimage
  164. if j != 0 && len(o.Key_Images) > 0 {
  165. o.Key_Images = o.Key_Images[:0]
  166. }
  167. if extra_parsed {
  168. o.Tx_Public_Key = tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key)
  169. /* during emergency, for debugging purpose only
  170. NOTE: remove this before rekeasing code
  171. 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){
  172. 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)
  173. account.Decode_RingCT_Output(tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key),
  174. j,
  175. crypto.Key(tx.RctSignature.OutPk[j].Mask),
  176. tx.RctSignature.ECdhInfo[j],
  177. 2)
  178. }
  179. */
  180. }
  181. serialized, err := msgpack.Marshal(&o)
  182. if err != nil {
  183. panic(err)
  184. }
  185. chain.store.StoreObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(index_start), serialized)
  186. // fmt.Printf("index %d %x\n",index_start,d.InKey.Destination)
  187. index_start++
  188. index_within_tx++
  189. }
  190. }
  191. }
  192. // this will load the index data for specific index
  193. // this should be done while holding the chain lock,
  194. // since during reorganisation we might give out wrong keys,
  195. // to avoid that pitfall take the chain lock
  196. // NOTE: this function is now for internal use only by the blockchain itself
  197. //
  198. func (chain *Blockchain) load_output_index(index uint64) (idata globals.TX_Output_Data) {
  199. // chain.Lock()
  200. // defer chain.Unlock()
  201. data_bytes, err := chain.store.LoadObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(index))
  202. if err != nil {
  203. logger.Warnf("err while loading output index data index = %d err %s", index, err)
  204. return
  205. }
  206. err = msgpack.Unmarshal(data_bytes, &idata)
  207. if err != nil {
  208. logger.Warnf("err while unmarshallin output index data index = %d data_len %d err %s", index, len(data_bytes), err)
  209. return
  210. }
  211. return
  212. }
  213. // this will read the output index data but will not deserialize it
  214. // this is exposed for rpcserver giving access to wallet
  215. func (chain *Blockchain) Read_output_index(index uint64) (data_bytes []byte, err error) {
  216. chain.Lock()
  217. defer chain.Unlock()
  218. data_bytes, err = chain.store.LoadObject(BLOCKCHAIN_UNIVERSE, GALAXY_OUTPUT_INDEX, GALAXY_OUTPUT_INDEX, itob(index))
  219. if err != nil {
  220. logger.Warnf("err while loading output index data index = %d err %s", index, err)
  221. return
  222. }
  223. return data_bytes, err
  224. }
  225. // this function finds output index for the tx
  226. // first find a block index , and get the start offset
  227. // then loop the index till you find the key in the result
  228. // if something is not right, we return 0
  229. func (chain *Blockchain) Find_TX_Output_Index(tx_hash crypto.Hash) (offset uint64) {
  230. Block_Height := chain.Load_TX_Height(tx_hash) // get height
  231. block_id, err := chain.Load_BL_ID_at_Height(Block_Height)
  232. if err != nil {
  233. logger.Warnf("error while finding tx_output_index %s", tx_hash)
  234. return 0
  235. }
  236. block_index_start := chain.Get_Block_Output_Index(block_id)
  237. // output_max_count := chain.Block_Count_Vout(block_id) // this function will load/serdes all tx contained within block
  238. /*for index_start:= block_index_start; index_start < (block_index_start+output_max_count); index_start++{
  239. }
  240. */
  241. bl, err := chain.Load_BL_FROM_ID(block_id)
  242. if err != nil {
  243. logger.Warnf("Cannot load block for %s err %s", block_id, err)
  244. return
  245. }
  246. if tx_hash == bl.Miner_tx.GetHash() {
  247. return block_index_start
  248. }
  249. offset = block_index_start + 1 // shift by 1
  250. for i := 0; i < len(bl.Tx_hashes); i++ { // load all tx one by one
  251. if bl.Tx_hashes[i] == tx_hash {
  252. return offset
  253. }
  254. tx, err := chain.Load_TX_FROM_ID(bl.Tx_hashes[i])
  255. if err != nil {
  256. logger.Warnf("Cannot load tx for %s err %s", bl.Tx_hashes[i], err)
  257. }
  258. // tx has been loaded, now lets get the vout
  259. vout_count := uint64(len(tx.Vout))
  260. offset += vout_count
  261. }
  262. // we will reach here only if tx is linked to wrong block
  263. // this may be possible during reorganisation
  264. // return 0
  265. logger.Warnf("Index Position must never reach here")
  266. return 0
  267. }