具有相同结构集的其他字段的结构字段哈希函数 - GoLang

Struct Field Hash function with other Fields of the same Struct Set - GoLang

我是 GoLang 的新手,正开始尝试构建一个简单的区块链。我在创建块的散列时遇到问题。任何人都可以帮助我如何将结构集的其他字段传递到同一结构中的 Hash() 函数,或者它是否需要以某种方式位于 stuct 之外,或者甚至可能......

块结构

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
}

现在,散列函数可以变成 Block 类型本身的接收函数:

// 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 类型可以像您现在所做的那样使用。至少在我看来,缺点是 debugging/dumping 人类可读格式的数据有点烦人,因为散列从不包含在 JSON 转储中,因为 json:"-"标签。您 可以 通过仅在 JSON 输出中包含 Hash 字段(如果已设置)来解决该问题,但这确实会为散列的奇怪错误打开大门没有正确设置。


关于地图评论

因此在 golang 中遍历映射是 non-deterministic。您可能知道,确定性在区块链应用程序中非常重要,而映射通常是非常常用的数据结构。在可以让多个节点处理相同工作负载的情况下处理它们时,每个节点都生成相同的哈希值绝对至关重要(显然,前提是它们执行相同的工作)。假设你已经决定定义你的方块类型,无论出于何种原因将 Txs 作为 ID 映射(因此 Txs map[uint64]Tx),在这种情况下,无法保证 JSON 输出在所有节点上都是相同的。如果是这种情况,您需要 marshal/unmarshal 以解决此问题的方式处理数据:

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

而不是复制 field-by-field,特别是因为我们并不真正关心 Hash 字段是否保留其值,您可以重新分配整个 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
}

但是,是的,正如您可能会说的那样:避免在要散列的类型中使用映射,或者实施不同的方法以更可靠的方式获取散列