|
// 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
|
|
}
|