我是GoLang的新手,开始尝试构建一个简单的区块链。我在创建块的哈希时遇到问题。有人能帮助我如何将结构集的其他字段传递到同一结构内的Hash()
函数中吗,或者它是否需要以某种方式在结构之外,或者是否可能。。。
块结构
type Block struct {
Index int
PrevHash string
Txs []Tx
Timestamp int64
Hash string
}
设置结构示例
Block{
Index: 0,
PrevHash: "Genesis",
Txs: []Tx{},
Timestamp: time.Now().Unix(),
Hash: Hash(/* How do I pass the other fields data here... */),
}
我的哈希函数
func Hash(text string) string {
hash := md5.Sum([]byte(text))
return hex.EncodeToString(hash[:])
}
我的导入(如果有帮助)
import (
"crypto/md5"
"encoding/hex"
"fmt"
"time"
)
有很多方法可以做到这一点,但鉴于您正在寻找一种简单的方法,您可以串行化数据,对其进行散列并进行分配。最简单的方法是封送Block
类型,对结果进行散列,并将其分配给Hash
字段。
就我个人而言,我更喜欢通过拆分组成哈希的数据,并将这种类型嵌入到块类型本身中,使其更加明确,但这实际上取决于您。请注意,json编组映射可能不是确定性的,因此根据您的Tx
类型,您可能需要更多的工作。
不管怎样,对于嵌入式类型,它看起来是这样的:
// you'll rarely interact with this type directly, never outside of hashing
type InBlock struct {
Index int `json:"index"`
PrevHash string `json:"PrevHash"`
Txs []Tx `json:"txs"`
Timestamp int64 `json:"timestamp"`
}
// almost identical in to the existing block type
type Block struct {
InBlock // embed the block fields
Hash string
}
现在,散列函数可以变成块类型本身的接收器函数:
// CalculateHash will compute the hash, set it on the Block field, returns an error if we can't serialise the hash data
func (b *Block) CalculateHash() error {
data, err := json.Marshal(b.InBlock) // marshal the InBlock data
if err != nil {
return err
}
hash := md5.Sum(data)
b.Hash = hex.EncodeToString(hash[:])
return nil
}
现在唯一真正的区别是如何初始化Block
类型:
block := Block{
InBlock: InBlock{
Index: 0,
PrevHash: "Genesis",
Txs: []Tx{},
Timestamp: time.Now().Unix(),
},
Hash: "", // can be omitted
}
if err := block.CalculateHash(); err != nil {
panic("something went wrong: " + err.Error())
}
// block now has the hash set
要访问block
变量上的字段,不需要指定InBlock
,因为Block
类型没有任何字段的名称可以屏蔽它嵌入的类型的字段,所以这很有效:
txs := block.Txs
// just as well as this
txs := block.InBlock.Txs
如果没有嵌入类型,它最终会看起来像这样:
type Block struct {
Index int `json:"index"`
PrevHash string `json:"PrevHash"`
Txs []Tx `json:"txs"`
Timestamp int64 `json:"timestamp"`
Hash string `json:"-"` // exclude from JSON mashalling
}
然后散列内容看起来是这样的:
func (b *Block) CalculateHash() error {
data, err := json.Marshal(b)
if err != nil {
return err
}
hash := md5.Sum(data)
b.Hash = hex.EncodeToString(hash[:])
return nil
}
通过这种方式,底层Block
类型可以像您现在所做的那样使用。缺点是,至少在我看来,以人类可读的格式调试/转储数据有点烦人,因为由于json:"-"
标记,JSON转储中从未包含哈希。如果设置了Hash
字段,您可以通过在JSON输出中只包含CCD_11来解决这个问题,但这确实会为哈希设置不正确的奇怪错误打开大门。
关于地图注释
所以在golang中迭代映射是不确定的。正如你可能知道的,确定性在区块链应用中非常重要,地图通常是非常常用的数据结构。在可以让多个节点处理相同工作负载的情况下处理它们时,每个节点产生相同的哈希是绝对重要的(显然,前提是它们做相同的工作)。假设您已经决定定义您的块类型,无论出于何种原因,将Txs
作为ID映射(因此为Txs map[uint64]Tx
),在这种情况下,不能保证所有节点上的JSON输出都相同。如果是这种情况,您需要以解决此问题的方式封送/解组数据:
// a new type that you'll only use in custom marshalling
// Txs is a slice here, using a wrapper type to preserve ID
type blockJSON struct {
Index int `json:"index"`
PrevHash string `json:"PrevHash"`
Txs []TxID `json:"txs"`
Timestamp int64 `json:"timestamp"`
Hash string `json:"-"`
}
// TxID is a type that preserves both Tx and ID data
// Tx is a pointer to prevent copying the data later on
type TxID struct {
Tx *Tx `json:"tx"`
ID uint64 `json:"id"`
}
// not the json tags are gone
type Block struct {
Index int
PrevHash string
Txs map[uint64]Tx // as a map
Timestamp int64
Hash string
}
func (b Block) MarshalJSON() ([]byte, error) {
cpy := blockJSON{
Index: b.Index,
PrevHash: b.PrevHash,
Txs: make([]TxID, 0, len(b.Txs)), // allocate slice
Timestamp: b.Timestamp,
}
keys := make([]uint64, 0, len(b.Txs)) // slice of keys
for k := range b.Txs {
keys = append(keys, k) // add keys to the slice
}
// now sort the slice. I prefer Stable, but for int keys Sort
// should work just fine
sort.SliceStable(keys, func(i, j int) bool {
return keys[i] < keys[j]
}
// now we can iterate over our sorted slice and append to the Txs slice ensuring the order is deterministic
for _, k := range keys {
cpy.Txs = append(cpy.Txs, TxID{
Tx: &b.Txs[k],
ID: k,
})
}
// now we've copied over all the data, we can marshal it:
return json.Marshal(cpy)
}
解组也必须这样做,因为串行数据不再与我们原来的Block
类型兼容:
func (b *Block) UnmarshalJSON(data []byte) error {
wrapper := blockJSON{} // the intermediary type
if err := json.Unmarshal(data, &wrapper); err != nil {
return err
}
// copy over fields again
b.Index = wrapper.Index
b.PrevHash = wrapper.PrevHash
b.Timestamp = wrapper.Timestamp
b.Txs = make(map[uint64]Tx, len(wrapper.Txs)) // allocate map
for _, tx := range wrapper.Txs {
b.Txs[tx.ID] = *tx.Tx // copy over values to build the map
}
return nil
}
您可以重新分配整个Block
变量:
func (b *Block) UnmarshalJSON(data []byte) error {
wrapper := blockJSON{} // the intermediary type
if err := json.Unmarshal(data, &wrapper); err != nil {
return err
}
*b = Block{
Index: wrapper.Index,
PrevHash: wrapper.PrevHash,
Txs: make(map[uint64]Tx, len(wrapper.Txs)),
Timestamp: wrapper.Timestamp,
}
for _, tx := range wrapper.Txs {
b.Txs[tx.ID] = *tx.Tx // populate map
}
return nil
}
但是,是的,正如你可能知道的那样:避免使用你想要散列的类型的映射,或者实现不同的方法以更可靠的方式获得散列