// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package core

import (
	"crypto/ecdsa"
	"encoding/binary"
	"fmt"
	"math/big"
	"math/rand"
	"time"

	coreCommon "github.com/dexon-foundation/dexon-consensus/common"
	coreCrypto "github.com/dexon-foundation/dexon-consensus/core/crypto"
	coreDKG "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg"
	coreEcdsa "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa"
	coreTypes "github.com/dexon-foundation/dexon-consensus/core/types"
	coreTypesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg"

	"github.com/dexon-foundation/dexon/common"
	"github.com/dexon-foundation/dexon/consensus"
	"github.com/dexon-foundation/dexon/consensus/misc"
	"github.com/dexon-foundation/dexon/core/state"
	"github.com/dexon-foundation/dexon/core/types"
	"github.com/dexon-foundation/dexon/core/vm"
	"github.com/dexon-foundation/dexon/crypto"
	"github.com/dexon-foundation/dexon/ethdb"
	"github.com/dexon-foundation/dexon/params"
	"github.com/dexon-foundation/dexon/rlp"
)

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

// BlockGen creates blocks for testing.
// See GenerateChain for a detailed explanation.
type BlockGen struct {
	i       int
	parent  *types.Block
	chain   []*types.Block
	header  *types.Header
	statedb *state.StateDB

	gasPool  *GasPool
	txs      []*types.Transaction
	receipts []*types.Receipt
	uncles   []*types.Header

	config *params.ChainConfig
	engine consensus.Engine
}

// SetCoinbase sets the coinbase of the generated block.
// It can be called at most once.
func (b *BlockGen) SetCoinbase(addr common.Address) {
	if b.gasPool != nil {
		if len(b.txs) > 0 {
			panic("coinbase must be set before adding transactions")
		}
		panic("coinbase can only be set once")
	}
	b.header.Coinbase = addr
	b.gasPool = new(GasPool).AddGas(b.header.GasLimit)
}

// SetExtra sets the extra data field of the generated block.
func (b *BlockGen) SetExtra(data []byte) {
	b.header.Extra = data
}

// SetNonce sets the nonce field of the generated block.
func (b *BlockGen) SetNonce(nonce types.BlockNonce) {
	b.header.Nonce = nonce
}

// AddTx adds a transaction to the generated block. If no coinbase has
// been set, the block's coinbase is set to the zero address.
//
// AddTx panics if the transaction cannot be executed. In addition to
// the protocol-imposed limitations (gas limit, etc.), there are some
// further limitations on the content of transactions that can be
// added. Notably, contract code relying on the BLOCKHASH instruction
// will panic during execution.
func (b *BlockGen) AddTx(tx *types.Transaction) {
	b.AddTxWithChain(nil, tx)
}

// AddTxWithChain adds a transaction to the generated block. If no coinbase has
// been set, the block's coinbase is set to the zero address.
//
// AddTxWithChain panics if the transaction cannot be executed. In addition to
// the protocol-imposed limitations (gas limit, etc.), there are some
// further limitations on the content of transactions that can be
// added. If contract code relies on the BLOCKHASH instruction,
// the block in chain will be returned.
func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
	if b.gasPool == nil {
		b.SetCoinbase(common.Address{})
	}
	b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs))
	receipt, _, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
	if err != nil {
		panic(err)
	}
	b.txs = append(b.txs, tx)
	b.receipts = append(b.receipts, receipt)
}

// Number returns the block number of the block being generated.
func (b *BlockGen) Number() *big.Int {
	return new(big.Int).Set(b.header.Number)
}

// AddUncheckedReceipt forcefully adds a receipts to the block without a
// backing transaction.
//
// AddUncheckedReceipt will cause consensus failures when used during real
// chain processing. This is best used in conjunction with raw block insertion.
func (b *BlockGen) AddUncheckedReceipt(receipt *types.Receipt) {
	b.receipts = append(b.receipts, receipt)
}

// TxNonce returns the next valid transaction nonce for the
// account at addr. It panics if the account does not exist.
func (b *BlockGen) TxNonce(addr common.Address) uint64 {
	if !b.statedb.Exist(addr) {
		panic("account does not exist")
	}
	return b.statedb.GetNonce(addr)
}

// AddUncle adds an uncle header to the generated block.
func (b *BlockGen) AddUncle(h *types.Header) {
	b.uncles = append(b.uncles, h)
}

// PrevBlock returns a previously generated block by number. It panics if
// num is greater or equal to the number of the block being generated.
// For index -1, PrevBlock returns the parent block given to GenerateChain.
func (b *BlockGen) PrevBlock(index int) *types.Block {
	if index >= b.i {
		panic(fmt.Errorf("block index %d out of range (%d,%d)", index, -1, b.i))
	}
	if index == -1 {
		return b.parent
	}
	return b.chain[index]
}

// OffsetTime modifies the time instance of a block, implicitly changing its
// associated difficulty. It's useful to test scenarios where forking is not
// tied to chain length directly.
func (b *BlockGen) OffsetTime(seconds int64) {
	b.header.Time += uint64(seconds)
	if b.header.Time <= b.parent.Header().Time {
		panic("block time out of range")
	}
	chainreader := &fakeChainReader{config: b.config}
	b.header.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, b.parent.Header())
}

// GenerateChain creates a chain of n blocks. The first block's
// parent will be the provided parent. db is used to store
// intermediate states and should contain the parent's state trie.
//
// The generator function is called with a new block generator for
// every block. Any transactions and uncles added to the generator
// become part of the block. If gen is nil, the blocks will be empty
// and their coinbase will be the zero address.
//
// Blocks created by GenerateChain do not contain valid proof of work
// values. Inserting them into BlockChain requires use of FakePow or
// a similar non-validating proof of work implementation.
func GenerateChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
	if config == nil {
		config = params.TestChainConfig
	}
	blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
	chainreader := &fakeChainReader{config: config}
	genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
		b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine}
		b.header = makeHeader(chainreader, parent, statedb, b.engine)

		// Mutate the state and block according to any hard-fork specs
		if daoBlock := config.DAOForkBlock; daoBlock != nil {
			limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
			if b.header.Number.Cmp(daoBlock) >= 0 && b.header.Number.Cmp(limit) < 0 {
				if config.DAOForkSupport {
					b.header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
				}
			}
		}
		if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
			misc.ApplyDAOHardFork(statedb)
		}
		// Execute any user modifications to the block
		if gen != nil {
			gen(i, b)
		}
		if b.engine != nil {
			// Finalize and seal the block
			block, _ := b.engine.Finalize(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts)

			// Write state changes to db
			root, err := statedb.Commit(config.IsEIP158(b.header.Number))
			if err != nil {
				panic(fmt.Sprintf("state write error: %v", err))
			}
			if err := statedb.Database().TrieDB().Commit(root, false); err != nil {
				panic(fmt.Sprintf("trie write error: %v", err))
			}
			return block, b.receipts
		}
		return nil, nil
	}
	for i := 0; i < n; i++ {
		statedb, err := state.New(parent.Root(), state.NewDatabase(db))
		if err != nil {
			panic(err)
		}
		block, receipt := genblock(i, parent, statedb)
		blocks[i] = block
		receipts[i] = receipt
		parent = block
	}
	return blocks, receipts
}

func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
	var time uint64
	if parent.Time() == 0 {
		time = 10
	} else {
		time = parent.Time() + 10 // block time is fixed at 10 seconds
	}

	return &types.Header{
		Root:       state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())),
		ParentHash: parent.Hash(),
		Coinbase:   parent.Coinbase(),
		Difficulty: engine.CalcDifficulty(chain, time, &types.Header{
			Number:     parent.Number(),
			Time:       time - 10,
			Difficulty: parent.Difficulty(),
			UncleHash:  parent.UncleHash(),
		}),
		GasLimit: CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()),
		Number:   new(big.Int).Add(parent.Number(), common.Big1),
		Time:     time,
	}
}

func GenerateChainWithRoundChange(config *params.ChainConfig, parent *types.Block,
	engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen),
	nodeSet *NodeSet, roundInterval int) ([]*types.Block, []types.Receipts) {
	if config == nil {
		config = params.TestChainConfig
	}

	round := parent.Header().Round

	blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
	chainreader := &fakeChainReader{config: config}
	genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
		b := &BlockGen{i: i, parent: parent, chain: blocks, statedb: statedb, config: config, engine: engine}
		b.header = makeHeader(chainreader, parent, statedb, b.engine)
		b.header.DexconMeta = makeDexconMeta(round, parent, nodeSet)

		switch i % roundInterval {
		case 0:
			// First block of this round, notify round height
			tx := nodeSet.NotifyRoundHeightTx(round, b.header.Number.Uint64(), b)
			b.AddTx(tx)
		case roundInterval / 2:
			// Run DKG for next round part 1, AddMasterPublicKey
			nodeSet.RunDKG(round, 2)
			for _, node := range nodeSet.nodes[round] {
				tx := node.MasterPublicKeyTx(round, b.TxNonce(node.address))
				b.AddTx(tx)
			}
		case (roundInterval / 2) + 1:
			// Run DKG for next round part 2, DKG finalize
			for _, node := range nodeSet.nodes[round] {
				tx := node.DKGFinalizeTx(round, b.TxNonce(node.address))
				b.AddTx(tx)
			}
		case (roundInterval / 2) + 2:
			// Current DKG set create signed CRS for next round and propose it
			nodeSet.SignedCRS(round)
			tx := nodeSet.CRSTx(round+1, b)
			b.AddTx(tx)
		case roundInterval - 1:
			// Round change
			round++
		}

		// Execute any user modifications to the block and finalize it
		if gen != nil {
			gen(i, b)
		}

		if b.engine != nil {
			block, _ := b.engine.Finalize(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts)
			// Write state changes to db
			root, err := statedb.Commit(config.IsEIP158(b.header.Number))
			if err != nil {
				panic(fmt.Sprintf("state write error: %v", err))
			}
			if err := statedb.Database().TrieDB().Commit(root, false); err != nil {
				panic(fmt.Sprintf("trie write error: %v", err))
			}
			return block, b.receipts
		}
		return nil, nil
	}
	for i := 0; i < n; i++ {
		statedb, err := state.New(parent.Root(), state.NewDatabase(db))
		if err != nil {
			panic(err)
		}
		block, receipt := genblock(i, parent, statedb)
		blocks[i] = block
		receipts[i] = receipt
		parent = block
	}
	return blocks, receipts
}

type witnessData struct {
	Root        common.Hash
	TxHash      common.Hash
	ReceiptHash common.Hash
}

func makeDexconMeta(round uint64, parent *types.Block, nodeSet *NodeSet) []byte {
	data, err := rlp.EncodeToBytes(&witnessData{
		Root:        parent.Root(),
		TxHash:      parent.TxHash(),
		ReceiptHash: parent.ReceiptHash(),
	})
	if err != nil {
		panic(err)
	}

	// only put required data, ignore information for BA, ex: acks, votes
	coreBlock := coreTypes.Block{
		Witness: coreTypes.Witness{
			Height: parent.Number().Uint64(),
			Data:   data,
		},
	}

	blockHash, err := hashBlock(&coreBlock)
	if err != nil {
		panic(err)
	}

	var parentCoreBlock coreTypes.Block

	if parent.Number().Uint64() != 0 {
		if err := rlp.DecodeBytes(
			parent.Header().DexconMeta, &parentCoreBlock); err != nil {
			panic(err)
		}
	}

	parentCoreBlockHash, err := hashBlock(&parentCoreBlock)
	if err != nil {
		panic(err)
	}
	randomness := nodeSet.Randomness(round, blockHash)
	coreBlock.Finalization.ParentHash = coreCommon.Hash(parentCoreBlockHash)
	coreBlock.Finalization.Randomness = randomness
	coreBlock.Finalization.Timestamp = time.Now().UTC()
	coreBlock.Finalization.Height = parent.Number().Uint64()

	dexconMeta, err := rlp.EncodeToBytes(&coreBlock)
	if err != nil {
		panic(err)
	}
	return dexconMeta
}

// makeHeaderChain creates a deterministic chain of headers rooted at parent.
func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header {
	blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed)
	headers := make([]*types.Header, len(blocks))
	for i, block := range blocks {
		headers[i] = block.Header()
	}
	return headers
}

// makeBlockChain creates a deterministic chain of blocks rooted at parent.
func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Block {
	blocks, _ := GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *BlockGen) {
		b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
	})
	return blocks
}

type fakeChainReader struct {
	config  *params.ChainConfig
	genesis *types.Block
}

// Config returns the chain configuration.
func (cr *fakeChainReader) Config() *params.ChainConfig {
	return cr.config
}

func (cr *fakeChainReader) CurrentHeader() *types.Header                            { return nil }
func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header           { return nil }
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header          { return nil }
func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil }
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block   { return nil }

type node struct {
	cryptoKey coreCrypto.PrivateKey
	ecdsaKey  *ecdsa.PrivateKey

	id                  coreTypes.NodeID
	dkgid               coreDKG.ID
	address             common.Address
	prvShares           *coreDKG.PrivateKeyShares
	pubShares           *coreDKG.PublicKeyShares
	receivedPrvShares   *coreDKG.PrivateKeyShares
	recoveredPrivateKey *coreDKG.PrivateKey
	signer              types.Signer

	mpk *coreTypesDKG.MasterPublicKey
}

func newNode(privkey *ecdsa.PrivateKey, signer types.Signer) *node {
	k := coreEcdsa.NewPrivateKeyFromECDSA(privkey)
	id := coreTypes.NewNodeID(k.PublicKey())
	return &node{
		cryptoKey: k,
		ecdsaKey:  privkey,
		id:        id,
		dkgid:     coreDKG.NewID(id.Bytes()),
		address:   crypto.PubkeyToAddress(privkey.PublicKey),
		signer:    signer,
	}
}

func (n *node) ID() coreTypes.NodeID {
	return n.id
}

func (n *node) DKGID() coreDKG.ID {
	return n.dkgid
}

// return signed dkg master public key
func (n *node) MasterPublicKeyTx(round uint64, nonce uint64) *types.Transaction {
	mpk := &coreTypesDKG.MasterPublicKey{
		ProposerID:      n.ID(),
		Round:           round,
		DKGID:           n.DKGID(),
		PublicKeyShares: *n.pubShares,
	}

	binaryRound := make([]byte, 8)
	binary.LittleEndian.PutUint64(binaryRound, mpk.Round)

	hash := crypto.Keccak256Hash(
		mpk.ProposerID.Hash[:],
		mpk.DKGID.GetLittleEndian(),
		mpk.PublicKeyShares.MasterKeyBytes(),
		binaryRound,
	)

	var err error
	mpk.Signature, err = n.cryptoKey.Sign(coreCommon.Hash(hash))
	if err != nil {
		panic(err)
	}

	method := vm.GovernanceContractName2Method["addDKGMasterPublicKey"]
	encoded, err := rlp.EncodeToBytes(mpk)
	if err != nil {
		panic(err)
	}

	res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
	if err != nil {
		panic(err)
	}
	data := append(method.Id(), res...)
	return n.CreateGovTx(nonce, data)
}

func (n *node) DKGFinalizeTx(round uint64, nonce uint64) *types.Transaction {
	final := coreTypesDKG.Finalize{
		ProposerID: n.ID(),
		Round:      round,
	}
	binaryRound := make([]byte, 8)
	binary.LittleEndian.PutUint64(binaryRound, final.Round)
	hash := crypto.Keccak256Hash(
		final.ProposerID.Hash[:],
		binaryRound,
	)

	var err error
	final.Signature, err = n.cryptoKey.Sign(coreCommon.Hash(hash))
	if err != nil {
		panic(err)
	}

	method := vm.GovernanceContractName2Method["addDKGFinalize"]

	encoded, err := rlp.EncodeToBytes(final)
	if err != nil {
		panic(err)
	}

	res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
	if err != nil {
		panic(err)
	}

	data := append(method.Id(), res...)
	return n.CreateGovTx(nonce, data)
}

func (n *node) CreateGovTx(nonce uint64, data []byte) *types.Transaction {
	tx, err := types.SignTx(types.NewTransaction(
		nonce,
		vm.GovernanceContractAddress,
		big.NewInt(0),
		uint64(2000000),
		big.NewInt(1e10),
		data), n.signer, n.ecdsaKey)
	if err != nil {
		panic(err)
	}
	return tx
}

type NodeSet struct {
	signer    types.Signer
	privkeys  []*ecdsa.PrivateKey
	nodes     map[uint64][]*node
	crs       map[uint64]common.Hash
	signedCRS map[uint64][]byte
}

func NewNodeSet(round uint64, crs common.Hash, signer types.Signer,
	privkeys []*ecdsa.PrivateKey) *NodeSet {
	n := &NodeSet{
		signer:    signer,
		privkeys:  privkeys,
		nodes:     make(map[uint64][]*node),
		crs:       make(map[uint64]common.Hash),
		signedCRS: make(map[uint64][]byte),
	}
	n.crs[round] = crs
	n.RunDKG(round, 2)
	return n
}

func (n *NodeSet) CRS(round uint64) common.Hash {
	if c, ok := n.crs[round]; ok {
		return c
	}
	panic("crs not exist")
}

// Assume All nodes in NodeSet are in DKG Set too.
func (n *NodeSet) RunDKG(round uint64, threshold int) {
	var ids coreDKG.IDs
	var nodes []*node
	for _, key := range n.privkeys {
		node := newNode(key, n.signer)
		nodes = append(nodes, node)
		ids = append(ids, node.DKGID())
	}

	for _, node := range nodes {
		node.prvShares, node.pubShares = coreDKG.NewPrivateKeyShares(threshold)
		node.prvShares.SetParticipants(ids)
		node.receivedPrvShares = coreDKG.NewEmptyPrivateKeyShares()
	}

	// exchange keys
	for _, sender := range nodes {
		for _, receiver := range nodes {
			// no need to verify
			prvShare, ok := sender.prvShares.Share(receiver.DKGID())
			if !ok {
				panic("not ok")
			}
			receiver.receivedPrvShares.AddShare(sender.DKGID(), prvShare)
		}
	}

	// recover private key
	for _, node := range nodes {
		privKey, err := node.receivedPrvShares.RecoverPrivateKey(ids)
		if err != nil {
			panic(err)
		}
		node.recoveredPrivateKey = privKey
	}

	// store these nodes
	n.nodes[round] = nodes
}

func (n *NodeSet) Randomness(round uint64, hash common.Hash) []byte {
	if round == 0 {
		return []byte{}
	}
	return n.TSig(round-1, hash)
}

func (n *NodeSet) SignedCRS(round uint64) {
	signedCRS := n.TSig(round, n.crs[round])
	n.signedCRS[round+1] = signedCRS
	n.crs[round+1] = crypto.Keccak256Hash(signedCRS)
}

func (n *NodeSet) TSig(round uint64, hash common.Hash) []byte {
	var ids coreDKG.IDs
	var psigs []coreDKG.PartialSignature
	for _, node := range n.nodes[round] {
		ids = append(ids, node.DKGID())
	}
	for _, node := range n.nodes[round] {
		sig, err := node.recoveredPrivateKey.Sign(coreCommon.Hash(hash))
		if err != nil {
			panic(err)
		}
		psigs = append(psigs, coreDKG.PartialSignature(sig))
		// ids = append(ids, node.DKGID())

		// FIXME: Debug verify signature
		pk := coreDKG.NewEmptyPublicKeyShares()
		for _, nnode := range n.nodes[round] {
			p, err := nnode.pubShares.Share(node.DKGID())
			if err != nil {
				panic(err)
			}
			err = pk.AddShare(nnode.DKGID(), p)
			if err != nil {
				panic(err)
			}
		}

		recovered, err := pk.RecoverPublicKey(ids)
		if err != nil {
			panic(err)
		}

		if !recovered.VerifySignature(coreCommon.Hash(hash), sig) {
			panic("##########can not verify signature")
		}
	}

	sig, err := coreDKG.RecoverSignature(psigs, ids)
	if err != nil {
		panic(err)
	}
	return sig.Signature
}

func (n *NodeSet) CRSTx(round uint64, b *BlockGen) *types.Transaction {
	method := vm.GovernanceContractName2Method["proposeCRS"]
	res, err := method.Inputs.Pack(big.NewInt(int64(round)), n.signedCRS[round])
	if err != nil {
		panic(err)
	}
	data := append(method.Id(), res...)

	node := n.nodes[round-1][0]
	return node.CreateGovTx(b.TxNonce(node.address), data)
}

func (n *NodeSet) NotifyRoundHeightTx(round, height uint64,
	b *BlockGen) *types.Transaction {
	method := vm.GovernanceContractName2Method["snapshotRound"]
	res, err := method.Inputs.Pack(
		big.NewInt(int64(round)), big.NewInt(int64(height)))
	if err != nil {
		panic(err)
	}
	data := append(method.Id(), res...)

	var r uint64
	if round < 1 {
		r = 0
	} else {
		r = round - 1
	}
	node := n.nodes[r][0]
	return node.CreateGovTx(b.TxNonce(node.address), data)
}

// Copy from dexon consensus core
// TODO(sonic): polish this
func hashBlock(block *coreTypes.Block) (common.Hash, error) {
	hashPosition := hashPosition(block.Position)
	// Handling Block.Acks.
	binaryAcks := make([][]byte, len(block.Acks))
	for idx, ack := range block.Acks {
		binaryAcks[idx] = ack[:]
	}
	hashAcks := crypto.Keccak256Hash(binaryAcks...)
	binaryTimestamp, err := block.Timestamp.UTC().MarshalBinary()
	if err != nil {
		return common.Hash{}, err
	}
	binaryWitness, err := hashWitness(&block.Witness)
	if err != nil {
		return common.Hash{}, err
	}

	hash := crypto.Keccak256Hash(
		block.ProposerID.Hash[:],
		block.ParentHash[:],
		hashPosition[:],
		hashAcks[:],
		binaryTimestamp[:],
		block.PayloadHash[:],
		binaryWitness[:])
	return hash, nil
}

func hashPosition(position coreTypes.Position) common.Hash {
	binaryChainID := make([]byte, 4)
	binary.LittleEndian.PutUint32(binaryChainID, position.ChainID)

	binaryHeight := make([]byte, 8)
	binary.LittleEndian.PutUint64(binaryHeight, position.Height)

	return crypto.Keccak256Hash(
		binaryChainID,
		binaryHeight,
	)
}

func hashWitness(witness *coreTypes.Witness) (common.Hash, error) {
	binaryHeight := make([]byte, 8)
	binary.LittleEndian.PutUint64(binaryHeight, witness.Height)
	return crypto.Keccak256Hash(
		binaryHeight,
		witness.Data), nil
}