412 lines
11 KiB
Go

// Copyright 2017-2021 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 block
import "fmt"
//import "sort"
import "bytes"
import "runtime/debug"
import "encoding/hex"
import "encoding/binary"
import "github.com/ebfe/keccak"
import "github.com/romana/rlog"
import "github.com/deroproject/derohe/crypto"
//import "github.com/deroproject/derosuite/config"
import "github.com/deroproject/derohe/astrobwt"
import "github.com/deroproject/derohe/transaction"
// these are defined in file
//https://github.com/monero-project/monero/src/cryptonote_basic/cryptonote_basic.h
type Block_Header struct {
Major_Version uint64 `json:"major_version"`
Minor_Version uint64 `json:"minor_version"`
Timestamp uint64 `json:"timestamp"`
Height uint64 `json:"height"`
Nonce uint32 `json:"nonce"` // TODO make nonce 32 byte array for infinite work capacity
ExtraNonce [32]byte `json:"-"`
Miner_TX transaction.Transaction `json:"miner_tx"`
}
type Block struct {
Block_Header
Proof [32]byte `json:"-"` // proof is being used to record balance root hash
Tips []crypto.Hash `json:"tips"`
Tx_hashes []crypto.Hash `json:"tx_hashes"`
}
// we process incoming blocks in this format
type Complete_Block struct {
Bl *Block
Txs []*transaction.Transaction
}
// see spec here https://cryptonote.org/cns/cns003.txt
// this function gets the block identifier hash
// this has been simplified and varint length has been removed
func (bl *Block) GetHash() (hash crypto.Hash) {
long_header := bl.GetBlockWork()
// keccak hash of this above blob, gives the block id
return crypto.Keccak256(long_header)
}
// converts a block, into a getwork style work, ready for either submitting the block
// or doing Pow Calculations
func (bl *Block) GetBlockWork() []byte {
var buf []byte // bitcoin/litecoin getworks are 80 bytes
var scratch [8]byte
buf = append(buf, []byte{byte(bl.Major_Version), byte(bl.Minor_Version), 0, 0, 0, 0, 0}...) // 0 first 7 bytes are version in little endia format
binary.LittleEndian.PutUint32(buf[2:6], uint32(bl.Timestamp))
header_hash := crypto.Keccak256(bl.getserializedheaderforwork()) // 0 + 7
buf = append(buf, header_hash[:]...) // 0 + 7 + 32 = 39
binary.LittleEndian.PutUint32(scratch[0:4], bl.Nonce) // check whether it needs to be big endian
buf = append(buf, scratch[:4]...) // 0 + 7 + 32 + 4 = 43
// next place the ExtraNonce
buf = append(buf, bl.ExtraNonce[:]...) // total 7 + 32 + 4 + 32
buf = append(buf, 0) // total 7 + 32 + 4 + 32 + 1 = 76
if len(buf) != 76 {
panic(fmt.Sprintf("Getwork not equal to 76 bytes actual %d", len(buf)))
}
return buf
}
// copy the nonce and the extra nonce from the getwork to the block
func (bl *Block) CopyNonceFromBlockWork(work []byte) (err error) {
if len(work) < 74 {
return fmt.Errorf("work buffer is Invalid")
}
bl.Timestamp = uint64(binary.LittleEndian.Uint32(work[2:]))
bl.Nonce = binary.LittleEndian.Uint32(work[7+32:])
copy(bl.ExtraNonce[:], work[7+32+4:75])
return
}
// copy the nonce and the extra nonce from the getwork to the block
func (bl *Block) SetExtraNonce(extranonce []byte) (err error) {
if len(extranonce) == 0 {
return fmt.Errorf("extra nonce is invalid")
}
max := len(extranonce)
if max > 32 {
max = 32
}
copy(bl.ExtraNonce[:], extranonce[0:max])
return
}
// clear extra nonce
func (bl *Block) ClearExtraNonce() {
for i := range bl.ExtraNonce {
bl.ExtraNonce[i] = 0
}
}
// clear nonce
func (bl *Block) ClearNonce() {
bl.Nonce = 0
}
// 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 := astrobwt.POW_0alloc(long_header) // new astrobwt algorithm
copy(hash[:], tmphash[:32])
return
}
// serialize block header for calculating PoW
func (bl *Block) getserializedheaderforwork() []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])
// it is placed in pow
//n = binary.PutUvarint(buf, bl.Timestamp)
//serialised.Write(buf[:n])
// write miner tx
serialised.Write(bl.Miner_TX.Serialize())
// write tips,, merkle tree should be replaced with something faster
tips_treehash := bl.GetTipsHash()
n = binary.PutUvarint(buf, uint64(len(tips_treehash)))
serialised.Write(buf[:n])
serialised.Write(tips_treehash[:]) // actual tips hash
tx_treehash := bl.GetTXSHash() // hash of all transactions
n = binary.PutUvarint(buf, uint64(len(bl.Tx_hashes))) // count of all transactions
serialised.Write(buf[:n])
serialised.Write(tx_treehash[:]) // actual transactions hash
return serialised.Bytes()
}
// 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])
n = binary.PutUvarint(buf, bl.Height)
serialised.Write(buf[:n])
binary.LittleEndian.PutUint32(buf[0:8], bl.Nonce) // check whether it needs to be big endian
serialised.Write(buf[:4])
serialised.Write(bl.ExtraNonce[:])
// write miner address
serialised.Write(bl.Miner_TX.Serialize())
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)
serialized.Write(bl.Proof[:]) // write proof NOT implemented
// miner tx should always be coinbase
//minex_tx := bl.Miner_tx.Serialize()
//serialized.Write(minex_tx)
n := binary.PutUvarint(buf, uint64(len(bl.Tips)))
serialized.Write(buf[:n])
for _, hash := range bl.Tips {
serialized.Write(hash[:])
}
//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) GetTipsHash() (result crypto.Hash) {
/*if len(bl.Tips) == 0 { // case for genesis block
panic("Block does NOT refer any tips")
}*/
// add all the remaining hashes
h := keccak.New256()
for i := range bl.Tips {
h.Write(bl.Tips[i][:])
}
r := h.Sum(nil)
copy(result[:], r)
return
}
// get block transactions
// we have discarded the merkle tree and have shifted to a plain version
func (bl *Block) GetTXSHash() (result crypto.Hash) {
h := keccak.New256()
for i := range bl.Tx_hashes {
h.Write(bl.Tx_hashes[i][:])
}
r := h.Sum(nil)
copy(result[:], r)
return
}
// only parses header
func (bl *Block) DeserializeHeader(buf []byte) (err error) {
done := 0
bl.Major_Version, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Major Version in Block\n")
}
buf = buf[done:]
bl.Minor_Version, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Minor Version in Block\n")
}
buf = buf[done:]
bl.Timestamp, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Timestamp in Block\n")
}
buf = buf[done:]
bl.Height, done = binary.Uvarint(buf)
if done <= 0 {
return fmt.Errorf("Invalid Height 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:]
copy(bl.ExtraNonce[:], buf[0:32])
buf = buf[32:]
// parse miner tx
err = bl.Miner_TX.DeserializeHeader(buf)
if err != nil {
return err
}
return
}
//parse entire block completely
func (bl *Block) Deserialize(buf []byte) (err error) {
done := 0
defer func() {
if r := recover(); r != nil {
fmt.Printf("Panic while deserialising block, block hex_dump below to make a testcase/debug\n")
fmt.Printf("%s\n", hex.EncodeToString(buf))
fmt.Printf("Recovered while parsing block, Stack trace below block_hash ")
fmt.Printf("Stack trace \n%s", debug.Stack())
err = fmt.Errorf("Invalid Block")
return
}
}()
err = bl.DeserializeHeader(buf)
if err != nil {
return fmt.Errorf("Block Header could not be parsed %s\n", err)
}
buf = buf[len(bl.SerializeHeader()):] // skup number of bytes processed
// read 32 byte proof
copy(bl.Proof[:], buf[0:32])
buf = buf[32:]
// header finished here
// 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].(transaction.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):]
*/
tips_count, done := binary.Uvarint(buf)
if done <= 0 || done > 1 {
return fmt.Errorf("Invalid Tips count in Block\n")
}
buf = buf[done:]
// remember first tx is merkle root
for i := uint64(0); i < tips_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.Tips = append(bl.Tips, h)
}
//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
}