package blockchain import "fmt" import "bytes" import "encoding/hex" import "encoding/binary" import "github.com/romana/rlog" import "github.com/deroproject/derosuite/crypto" import "github.com/deroproject/derosuite/config" import "github.com/deroproject/derosuite/cryptonight" // these are defined in file //https://github.com/monero-project/monero/src/cryptonote_basic/cryptonote_basic.h type Block_Header struct { Major_Version uint32 `json:"major_version"` Minor_Version uint32 `json:"minor_version"` Timestamp uint64 `json:"timestamp"` Prev_Hash crypto.Hash `json:"prev_id"` Nonce uint32 `json:"nonce"` } type Block struct { Block_Header Miner_tx Transaction `json:"miner_tx"` Merkle_Root crypto.Hash `json:"-"` Tx_hashes []crypto.Hash `json:"tx_hashes"` treehash crypto.Hash } // see spec here https://cryptonote.org/cns/cns003.txt // this function gets the block identifier hash func (bl *Block) GetHash() (hash crypto.Hash) { buf := make([]byte, binary.MaxVarintLen64) long_header := bl.GetBlockWork() length := uint64(len(long_header)) n := binary.PutUvarint(buf, length) // buf = buf[:n] block_id_blob := append(buf, long_header...) // keccak hash of this above blob, gives the block id hash2 := crypto.Keccak256(block_id_blob) return crypto.Hash(hash2) } // converts a block, into a getwork style work, ready for either submitting the block // or doing Pow Calculations func (bl *Block) GetBlockWork() []byte { buf := make([]byte, binary.MaxVarintLen64) header := bl.SerializeHeader() tx_treehash := bl.GetTreeHash() // treehash of all transactions // length of all transactions n := binary.PutUvarint(buf, uint64(len(bl.Tx_hashes)+1)) // +1 for miner TX buf = buf[:n] long_header := append(header, tx_treehash[:]...) long_header = append(long_header, buf...) return long_header } // Get PoW hash , this is very slow function func (bl *Block) GetPoWHash() (hash crypto.Hash) { long_header := bl.GetBlockWork() rlog.Tracef(9, "longheader %x\n", long_header) tmphash := cryptonight.SlowHash(long_header) copy(hash[:], tmphash[:32]) return } // Reward is, total amount in the miner tx - fees func (bl *Block) GetReward() uint64 { total_amount := bl.Miner_tx.Vout[0].Amount total_fees := uint64(0) // load all the TX and get the fees, since we are in a post rct world // extract the fees from the rct sig return total_amount - total_fees } // serialize block header func (bl *Block) SerializeHeader() []byte { var serialised bytes.Buffer buf := make([]byte, binary.MaxVarintLen64) n := binary.PutUvarint(buf, uint64(bl.Major_Version)) serialised.Write(buf[:n]) n = binary.PutUvarint(buf, uint64(bl.Minor_Version)) serialised.Write(buf[:n]) n = binary.PutUvarint(buf, bl.Timestamp) serialised.Write(buf[:n]) serialised.Write(bl.Prev_Hash[:32]) // write previous ID binary.LittleEndian.PutUint32(buf[0:8], bl.Nonce) // check whether it needs to be big endian serialised.Write(buf[:4]) return serialised.Bytes() } // serialize entire block ( block_header + miner_tx + tx_list ) func (bl *Block) Serialize() []byte { var serialized bytes.Buffer buf := make([]byte, binary.MaxVarintLen64) header := bl.SerializeHeader() serialized.Write(header) // miner tx should always be coinbase minex_tx := bl.Miner_tx.Serialize() serialized.Write(minex_tx) //fmt.Printf("serializing tx hashes %d\n", len(bl.Tx_hashes)) n := binary.PutUvarint(buf, uint64(len(bl.Tx_hashes))) serialized.Write(buf[:n]) for _, hash := range bl.Tx_hashes { serialized.Write(hash[:]) } return serialized.Bytes() } // get block transactions tree hash func (bl *Block) GetTreeHash() (hash crypto.Hash) { var hash_list []crypto.Hash hash_list = append(hash_list, bl.Miner_tx.GetHash()) // add all the remaining hashes for i := range bl.Tx_hashes { hash_list = append(hash_list, bl.Tx_hashes[i]) } return TreeHash(hash_list) } // input is the list of transactions hashes func TreeHash(hashes []crypto.Hash) (hash crypto.Hash) { switch len(hashes) { case 0: panic("Treehash cannot have 0 transactions, atleast miner tx will be present") case 1: copy(hash[:], hashes[0][:32]) case 2: var buf []byte for i := 0; i < len(hashes); i++ { buf = append(buf, hashes[i][:32]...) } tmp_hash := crypto.Keccak256(buf) copy(hash[:], tmp_hash[:32]) default: count := uint64(len(hashes)) cnt := tree_hash_cnt(count) //fmt.Printf("cnt %d count %d\n",cnt, count) ints := make([]byte, 32*cnt, 32*cnt) hashes_buf := make([]byte, 32*count, 32*count) for i := uint64(0); i < count; i++ { copy(hashes_buf[i*32:], hashes[i][:32]) // copy hashes 1 by 1 } for i := uint64(0); i < ((2 * cnt) - count); i++ { copy(ints[i*32:], hashes[i][:32]) // copy hashes 1 by 1 } i := ((2 * cnt) - count) j := ((2 * cnt) - count) for ; j < cnt; i, j = i+2, j+1 { hash := crypto.Keccak256(hashes_buf[i*32 : (i*32)+64]) // find hash of 64 bytes copy(ints[j*32:], hash[:32]) } if i != count { panic("please fix tree hash") } for cnt > 2 { cnt = cnt >> 1 i = 0 j = 0 for ; j < cnt; i, j = i+2, j+1 { hash := crypto.Keccak256(ints[i*32 : (i*32)+64]) // find hash of 64 bytes copy(ints[j*32:], hash[:32]) } } hash = crypto.Hash(crypto.Keccak256(ints[0:64])) // find hash of 64 bytes } return } // see crypto/tree-hash.c // this function has a naughty history func tree_hash_cnt(count uint64) uint64 { pow := uint64(2) for pow < count { pow = pow << 1 } return pow >> 1 } func (bl *Block) Deserialize(buf []byte) (err error) { done := 0 var tmp uint64 defer func() { if r := recover(); r != nil { logger.Warnf("Panic while deserialising block, block hex_dump below to make a testcase/debug\n") logger.Warnf("%s", hex.EncodeToString(buf)) err = fmt.Errorf("Invalid Block") return } }() tmp, done = binary.Uvarint(buf) if done <= 0 { return fmt.Errorf("Invalid Version in Block\n") } buf = buf[done:] bl.Major_Version = uint32(tmp) if uint64(bl.Major_Version) != tmp { return fmt.Errorf("Invalid Block major version") } tmp, done = binary.Uvarint(buf) if done <= 0 { return fmt.Errorf("Invalid minor Version in Block\n") } buf = buf[done:] bl.Minor_Version = uint32(tmp) if uint64(bl.Minor_Version) != tmp { return fmt.Errorf("Invalid Block minor version") } bl.Timestamp, done = binary.Uvarint(buf) if done <= 0 { return fmt.Errorf("Invalid Timestamp in Block\n") } buf = buf[done:] copy(bl.Prev_Hash[:], buf[:32]) // hash is always 32 byte buf = buf[32:] bl.Nonce = binary.LittleEndian.Uint32(buf) buf = buf[4:] // nonce is always 4 bytes // read and parse transaction err = bl.Miner_tx.DeserializeHeader(buf) if err != nil { return fmt.Errorf("Cannot parse miner TX %x", buf) } // if tx was parse, make sure it's coin base if len(bl.Miner_tx.Vin) != 1 || bl.Miner_tx.Vin[0].(Txin_gen).Height > config.MAX_CHAIN_HEIGHT { // serialize transaction again to get the tx size, so as parsing could continue return fmt.Errorf("Invalid Miner TX") } miner_tx_serialized_size := bl.Miner_tx.Serialize() buf = buf[len(miner_tx_serialized_size):] //fmt.Printf("miner tx %x\n", miner_tx_serialized_size) // read number of transactions tx_count, done := binary.Uvarint(buf) if done <= 0 { return fmt.Errorf("Invalid Tx count in Block\n") } buf = buf[done:] // remember first tx is merkle root for i := uint64(0); i < tx_count; i++ { //fmt.Printf("Parsing transaction hash %d tx_count %d\n", i, tx_count) var h crypto.Hash copy(h[:], buf[:32]) buf = buf[32:] bl.Tx_hashes = append(bl.Tx_hashes, h) } //fmt.Printf("%d member in tx hashes \n",len(bl.Tx_hashes)) return }