From 674526ba7abcee4295a62a1c26f2759a8408b31c Mon Sep 17 00:00:00 2001 From: Jonathan Chappelow Date: Wed, 4 Dec 2024 14:29:19 -0600 Subject: [PATCH] accountsHash determinism, and global types.Hash use --- app/shared/display/message.go | 9 ++++----- cmd/kwil-cli/cmds/utils/message.go | 8 +++----- core/types/hash.go | 2 +- core/types/transaction.go | 11 ++++++++++- node/consensus/block_executor.go | 30 +++++++++++++++++------------- node/types/block.go | 7 +++---- node/types/block_test.go | 4 ++-- 7 files changed, 40 insertions(+), 31 deletions(-) diff --git a/app/shared/display/message.go b/app/shared/display/message.go index 3ab926368..0fdf8b071 100644 --- a/app/shared/display/message.go +++ b/app/shared/display/message.go @@ -1,7 +1,6 @@ package display import ( - "crypto/sha256" "encoding/hex" "encoding/json" "fmt" @@ -112,9 +111,9 @@ func (r *RespTxQuery) MarshalJSON() ([]byte, error) { out.Warn = "ERROR encoding transaction: " + err.Error() } else if r.WithRaw { out.Raw = hex.EncodeToString(raw) - hash := sha256.Sum256(raw) + hash := types.HashBytes(raw) if hash != r.Msg.Hash { - out.Warn = fmt.Sprintf("HASH MISMATCH: requested %s; received %x", + out.Warn = fmt.Sprintf("HASH MISMATCH: requested %s; received %s", r.Msg.Hash, hash) } } @@ -155,9 +154,9 @@ Log: %s`, if r.WithRaw { msg += "\nRaw: " + hex.EncodeToString(raw) } - hash := sha256.Sum256(raw) + hash := types.HashBytes(raw) if hash != r.Msg.Hash { - msg += fmt.Sprintf("\nWARNING! HASH MISMATCH:\n\tRequested %s\n\tReceived %x", + msg += fmt.Sprintf("\nWARNING! HASH MISMATCH:\n\tRequested %s\n\tReceived %s", r.Msg.Hash, hash) } } diff --git a/cmd/kwil-cli/cmds/utils/message.go b/cmd/kwil-cli/cmds/utils/message.go index b602c6c77..2aaa8af52 100644 --- a/cmd/kwil-cli/cmds/utils/message.go +++ b/cmd/kwil-cli/cmds/utils/message.go @@ -2,9 +2,7 @@ package utils import ( "bytes" - "crypto/sha256" "encoding/base64" - "encoding/hex" "encoding/json" "errors" "fmt" @@ -57,8 +55,8 @@ func (t *transaction) MarshalJSON() ([]byte, error) { } func (t *transaction) MarshalText() ([]byte, error) { - txHash := sha256.Sum256(t.Raw) // tmhash is sha256 - msg := fmt.Sprintf(`Transaction ID: %x + txHash := types.HashBytes(t.Raw) + msg := fmt.Sprintf(`Transaction ID: %s Sender: %s Description: %s Payload type: %s @@ -69,7 +67,7 @@ Signature type: %s Signature: %s `, txHash, - hex.EncodeToString(t.Tx.Sender), // hex because it's an address or pubkey, probably address + t.Tx.Sender.String(), t.Tx.Body.Description, t.Tx.Body.PayloadType, t.Tx.Body.ChainID, diff --git a/core/types/hash.go b/core/types/hash.go index 54ea7a9fe..68149a9d5 100644 --- a/core/types/hash.go +++ b/core/types/hash.go @@ -22,7 +22,7 @@ func HashBytes(b []byte) Hash { } // Hasher is like the standard library's hash.Hash, but with fewer methods and -// returning a Hash instead of a byte slice. Use [NewHasher] to get a Hasher. +// returning a [Hash] instead of a byte slice. Use [NewHasher] to get a Hasher. type Hasher interface { // Write more data to the running hash. It never returns an error. io.Writer diff --git a/core/types/transaction.go b/core/types/transaction.go index 54e2277ef..95c460dc6 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "encoding" "encoding/binary" "encoding/json" "errors" @@ -43,7 +44,7 @@ type Transaction struct { Serialization SignedMsgSerializationType `json:"serialization"` // Sender is the user identifier, which is generally an address but may be - // a public key of the sender. + // a public key of the sender, hence bytes that encode as hexadecimal. Sender HexBytes `json:"sender"` strictUnmarshal bool @@ -258,6 +259,8 @@ func (t *TransactionBody) SerializeMsg(mst SignedMsgSerializationType) ([]byte, return nil, errors.New("invalid serialization type") } +var _ encoding.BinaryMarshaler = (*Transaction)(nil) + // MarshalBinary produces the full binary serialization of the transaction, // which is the form used in p2p messaging and blockchain storage. func (t *Transaction) MarshalBinary() ([]byte, error) { @@ -278,6 +281,8 @@ func (t *Transaction) ReadFrom(r io.Reader) (int64, error) { return int64(n), nil } +var _ encoding.BinaryUnmarshaler = (*Transaction)(nil) + func (t *Transaction) UnmarshalBinary(data []byte) error { r := bytes.NewReader(data) n, err := t.deserialize(r) @@ -296,6 +301,8 @@ func (t *Transaction) UnmarshalBinary(data []byte) error { return nil } +var _ encoding.BinaryMarshaler = (*TransactionBody)(nil) + func (tb *TransactionBody) MarshalBinary() ([]byte, error) { buf := new(bytes.Buffer) @@ -386,6 +393,8 @@ func (tb *TransactionBody) ReadFrom(r io.Reader) (int64, error) { return int64(n), nil } +var _ encoding.BinaryUnmarshaler = (*TransactionBody)(nil) + func (tb *TransactionBody) UnmarshalBinary(data []byte) error { buf := bytes.NewReader(data) n, err := tb.ReadFrom(buf) diff --git a/node/consensus/block_executor.go b/node/consensus/block_executor.go index a271fb124..f73209472 100644 --- a/node/consensus/block_executor.go +++ b/node/consensus/block_executor.go @@ -1,11 +1,12 @@ package consensus import ( + "bytes" "context" - "crypto/sha256" "encoding/binary" "encoding/hex" "fmt" + "slices" "sort" "github.com/kwilteam/kwil-db/common" @@ -74,15 +75,15 @@ func (ce *ConsensusEngine) executeBlock() (err error) { // TODO: log tracker - var txResults []ktypes.TxResult + txResults := make([]ktypes.TxResult, len(ce.state.blkProp.blk.Txns)) - for _, tx := range ce.state.blkProp.blk.Txns { + for i, tx := range ce.state.blkProp.blk.Txns { decodedTx := &ktypes.Transaction{} if err := decodedTx.UnmarshalBinary(tx); err != nil { ce.log.Error("Failed to unmarshal the block tx", "err", err) return err } - txHash := sha256.Sum256(tx) + txHash := types.HashBytes(tx) auth := auth.GetAuthenticator(decodedTx.Signature.Type) @@ -118,7 +119,7 @@ func (ce *ConsensusEngine) executeBlock() (err error) { txResult.Log = "success" } - txResults = append(txResults, txResult) + txResults[i] = txResult } } @@ -195,7 +196,7 @@ func (ce *ConsensusEngine) executeBlock() (err error) { // nextAppHash calculates the appHash that encapsulates the state changes occurred during the block execution. // sha256(prevAppHash || changesetHash || valUpdatesHash || accountsHash || txResultsHash) func (ce *ConsensusEngine) nextAppHash(prevAppHash, changesetHash, valUpdatesHash, accountsHash, txResultsHash types.Hash) types.Hash { - hasher := sha256.New() + hasher := ktypes.NewHasher() hasher.Write(prevAppHash[:]) hasher.Write(changesetHash[:]) @@ -204,29 +205,32 @@ func (ce *ConsensusEngine) nextAppHash(prevAppHash, changesetHash, valUpdatesHas hasher.Write(txResultsHash[:]) ce.log.Info("AppState updates: ", "prevAppHash", prevAppHash, "changesetsHash", changesetHash, "valUpdatesHash", valUpdatesHash, "accountsHash", accountsHash, "txResultsHash", txResultsHash) - return types.Hash(hasher.Sum(nil)) + return hasher.Sum(nil) } func txResultsHash(results []ktypes.TxResult) types.Hash { - hasher := sha256.New() + hasher := ktypes.NewHasher() for _, res := range results { binary.Write(hasher, binary.BigEndian, res.Code) binary.Write(hasher, binary.BigEndian, res.Gas) } - return types.Hash(hasher.Sum(nil)) + return hasher.Sum(nil) } func (ce *ConsensusEngine) accountsHash() types.Hash { accounts := ce.accounts.Updates() - hasher := sha256.New() + slices.SortFunc(accounts, func(a, b *ktypes.Account) int { + return bytes.Compare(a.Identifier, b.Identifier) + }) + hasher := ktypes.NewHasher() for _, acc := range accounts { hasher.Write(acc.Identifier) binary.Write(hasher, binary.BigEndian, acc.Balance.Bytes()) binary.Write(hasher, binary.BigEndian, acc.Nonce) } - return types.Hash(hasher.Sum(nil)) + return hasher.Sum(nil) } func validatorUpdatesHash(updates map[string]*ktypes.Validator) types.Hash { @@ -239,7 +243,7 @@ func validatorUpdatesHash(updates map[string]*ktypes.Validator) types.Hash { } sort.Strings(keys) - hash := sha256.New() + hash := ktypes.NewHasher() for _, k := range keys { // hash the validator address hash.Write(updates[k].PubKey) @@ -247,7 +251,7 @@ func validatorUpdatesHash(updates map[string]*ktypes.Validator) types.Hash { binary.Write(hash, binary.BigEndian, updates[k].Power) } - return types.Hash(hash.Sum(nil)) + return hash.Sum(nil) } // Commit method commits the block to the blockstore and postgres database. diff --git a/node/types/block.go b/node/types/block.go index 82e791d1f..f3eef45de 100644 --- a/node/types/block.go +++ b/node/types/block.go @@ -2,7 +2,6 @@ package types import ( "bytes" - "crypto/sha256" "encoding/binary" "fmt" "io" @@ -208,9 +207,9 @@ func EncodeBlockHeader(hdr *BlockHeader) []byte { } func (bh *BlockHeader) Hash() Hash { - hasher := sha256.New() + hasher := types.NewHasher() bh.writeBlockHeader(hasher) - return Hash(hasher.Sum(nil)) + return hasher.Sum(nil) } /*func encodeBlockHeaderOneAlloc(hdr *BlockHeader) []byte { @@ -296,7 +295,7 @@ func CalcMerkleRoot(leaves []Hash) Hash { for i := range len(leaves) / 2 { copy(left, leaves[i*2][:]) copy(right, leaves[i*2+1][:]) - leaves[i] = sha256.Sum256(both) + leaves[i] = types.HashBytes(both) } leaves = leaves[:len(leaves)/2] } diff --git a/node/types/block_test.go b/node/types/block_test.go index 093bdb920..526d787bb 100644 --- a/node/types/block_test.go +++ b/node/types/block_test.go @@ -2,12 +2,12 @@ package types import ( "bytes" - "crypto/sha256" "encoding/binary" "testing" "time" "github.com/kwilteam/kwil-db/core/crypto" + "github.com/kwilteam/kwil-db/core/types" "github.com/stretchr/testify/require" ) @@ -120,7 +120,7 @@ func TestCalcMerkleRoot(t *testing.T) { var buf [HashLen * 2]byte copy(buf[:HashLen], leaf1[:]) copy(buf[HashLen:], leaf2[:]) - expected := sha256.Sum256(buf[:]) + expected := types.HashBytes(buf[:]) if root != expected { t.Errorf("got root %x, want %x", root, expected)