Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

structured tx in block #1172

Merged
merged 1 commit into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 16 additions & 21 deletions app/shared/display/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,14 @@ func (r *RespTxQuery) MarshalJSON() ([]byte, error) {
}
// Always try to serialize to verify hash, but only show raw if requested.
if r.Msg.Tx != nil {
raw, err := r.Msg.Tx.MarshalBinary()
if err != nil {
out.Warn = "ERROR encoding transaction: " + err.Error()
} else if r.WithRaw {
raw := r.Msg.Tx.Bytes()
if r.WithRaw {
out.Raw = hex.EncodeToString(raw)
hash := types.HashBytes(raw)
if hash != r.Msg.Hash {
out.Warn = fmt.Sprintf("HASH MISMATCH: requested %s; received %s",
r.Msg.Hash, hash)
}
}
hash := types.HashBytes(raw)
if hash != r.Msg.Hash {
out.Warn = fmt.Sprintf("HASH MISMATCH: requested %s; received %s",
r.Msg.Hash, hash)
}
}
return json.Marshal(out)
Expand Down Expand Up @@ -147,18 +145,15 @@ Log: %s`,
return []byte(msg), nil
}

raw, err := r.Msg.Tx.MarshalBinary()
if err != nil {
msg += "\nERROR encoding transaction: " + err.Error()
} else {
if r.WithRaw {
msg += "\nRaw: " + hex.EncodeToString(raw)
}
hash := types.HashBytes(raw)
if hash != r.Msg.Hash {
msg += fmt.Sprintf("\nWARNING! HASH MISMATCH:\n\tRequested %s\n\tReceived %s",
r.Msg.Hash, hash)
}
raw := r.Msg.Tx.Bytes()

if r.WithRaw {
msg += "\nRaw: " + hex.EncodeToString(raw)
}
hash := types.HashBytes(raw)
if hash != r.Msg.Hash {
msg += fmt.Sprintf("\nWARNING! HASH MISMATCH:\n\tRequested %s\n\tReceived %s",
r.Msg.Hash, hash)
}

return []byte(msg), nil
Expand Down
13 changes: 7 additions & 6 deletions app/shared/display/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ func Example_respTxQuery_json() {
// "gas": 10,
// "log": "This is log",
// "events": null
// }
// },
// "warning": "HASH MISMATCH: requested 0102030400000000000000000000000000000000000000000000000000000000; received a9e1f559c5ec1246078f5b9f362ee59ee4113946305d41448f917cdd96a0c883"
// },
// "error": ""
// }
Expand Down Expand Up @@ -207,8 +208,8 @@ func Example_respTxQuery_WithRaw_json() {
// "log": "This is log",
// "events": null
// },
// "raw": "5500000041000000cb3fed7f6ff36e59054c04a831b215e514052753ee353e6fe31d4b4ef736acd6155127db555d3006ba14fcb4c79bbad56c8e63b81a9896319bb053a9e2534758000c000000736563703235366b315f6570220000005468697320697320612074657374207472616e73616374696f6e20666f7220636c695d0000000001f859b8397866363137616631636137373465626264366432336538666531326335366434316432356132326438316538386636376336633665653064348b6372656174655f75736572d1d0cfc9847465787480c28080c483666f6f070000006578656375746501030000003130300a00000000000000040000006173646606000000636f6e63617400000000",
// "warning": "HASH MISMATCH: requested 0102030400000000000000000000000000000000000000000000000000000000; received ab8465bfd9a09828c348ea32801927598c1632ad37d248d7e945279f6d1b6480"
// "raw": "00005500000041000000cb3fed7f6ff36e59054c04a831b215e514052753ee353e6fe31d4b4ef736acd6155127db555d3006ba14fcb4c79bbad56c8e63b81a9896319bb053a9e2534758000c000000736563703235366b315f6570aa000000220000005468697320697320612074657374207472616e73616374696f6e20666f7220636c695d0000000001f859b8397866363137616631636137373465626264366432336538666531326335366434316432356132326438316538386636376336633665653064348b6372656174655f75736572d1d0cfc9847465787480c28080c483666f6f070000006578656375746501030000003130300a00000000000000040000006173646606000000636f6e63617400000000",
// "warning": "HASH MISMATCH: requested 0102030400000000000000000000000000000000000000000000000000000000; received a9e1f559c5ec1246078f5b9f362ee59ee4113946305d41448f917cdd96a0c883"
// },
// "error": ""
// }
Expand All @@ -222,7 +223,7 @@ func Test_TxHashAndExecResponse(t *testing.T) {
Hash: RespTxHash(hash),
QueryResp: &RespTxQuery{Msg: qr},
}
expectJSON := `{"tx_hash":"0102030405000000000000000000000000000000000000000000000000000000","exec_result":{"hash":"0102030405000000000000000000000000000000000000000000000000000000","height":10,"tx":{"signature":{"sig":"yz/tf2/zblkFTASoMbIV5RQFJ1PuNT5v4x1LTvc2rNYVUSfbVV0wBroU/LTHm7rVbI5juBqYljGbsFOp4lNHWAA=","type":"secp256k1_ep"},"body":{"desc":"This is a test transaction for cli","payload":"AAH4Wbg5eGY2MTdhZjFjYTc3NGViYmQ2ZDIzZThmZTEyYzU2ZDQxZDI1YTIyZDgxZTg4ZjY3YzZjNmVlMGQ0i2NyZWF0ZV91c2Vy0dDPyYR0ZXh0gMKAgMSDZm9v","type":"execute","fee":"100","nonce":10,"chain_id":"asdf"},"serialization":"concat","sender":""},"result":{"code":0,"gas":10,"log":"This is log","events":null}}}`
expectJSON := `{"tx_hash":"0102030405000000000000000000000000000000000000000000000000000000","exec_result":{"hash":"0102030405000000000000000000000000000000000000000000000000000000","height":10,"tx":{"signature":{"sig":"yz/tf2/zblkFTASoMbIV5RQFJ1PuNT5v4x1LTvc2rNYVUSfbVV0wBroU/LTHm7rVbI5juBqYljGbsFOp4lNHWAA=","type":"secp256k1_ep"},"body":{"desc":"This is a test transaction for cli","payload":"AAH4Wbg5eGY2MTdhZjFjYTc3NGViYmQ2ZDIzZThmZTEyYzU2ZDQxZDI1YTIyZDgxZTg4ZjY3YzZjNmVlMGQ0i2NyZWF0ZV91c2Vy0dDPyYR0ZXh0gMKAgMSDZm9v","type":"execute","fee":"100","nonce":10,"chain_id":"asdf"},"serialization":"concat","sender":""},"result":{"code":0,"gas":10,"log":"This is log","events":null},"warning":"HASH MISMATCH: requested 0102030405000000000000000000000000000000000000000000000000000000; received a9e1f559c5ec1246078f5b9f362ee59ee4113946305d41448f917cdd96a0c883"}}`
expectText := "TxHash: 0102030405000000000000000000000000000000000000000000000000000000\nStatus: success\nHeight: 10\nLog: This is log"

outText, err := resp.MarshalText()
Expand Down Expand Up @@ -287,7 +288,7 @@ func TestRespTxQuery_MarshalText(t *testing.T) {
name: "pending status",
input: &RespTxQuery{
Msg: &types.TxQueryResponse{
Hash: mustUnmarshalHash("ff42fabfe6e73e0c566cb2462cc0bf69de0e050c7a90fa9d5a708ace51243589"),
Hash: mustUnmarshalHash("8d741508a6849f9d11c8f478584b5067a6bcfc1114300feff53454b0e064c0a0"),
Height: -1, // -1 height indicates pending
Tx: &types.Transaction{
Body: &types.TransactionBody{
Expand All @@ -305,7 +306,7 @@ func TestRespTxQuery_MarshalText(t *testing.T) {
},
},
},
expected: "Transaction ID: ff42fabfe6e73e0c566cb2462cc0bf69de0e050c7a90fa9d5a708ace51243589\nStatus: pending\nHeight: -1\nLog: transaction pending",
expected: "Transaction ID: 8d741508a6849f9d11c8f478584b5067a6bcfc1114300feff53454b0e064c0a0\nStatus: pending\nHeight: -1\nLog: transaction pending",
},
}

Expand Down
40 changes: 19 additions & 21 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ type BlockHeader struct {

type Block struct {
Header *BlockHeader
Txns [][]byte // TODO: convert to []*Transaction
Signature []byte // Signature is the block producer's signature (leader in our model)
Txns []*Transaction
Signature []byte // Signature is the block producer's signature (leader in our model)
}

func NewBlock(height int64, prevHash, appHash, valSetHash Hash, stamp time.Time, txns [][]byte) *Block {
func NewBlock(height int64, prevHash, appHash, valSetHash Hash, stamp time.Time, txns []*Transaction) *Block {
numTxns := len(txns)
txHashes := make([]Hash, numTxns)
for i := range txns {
txHashes[i] = HashBytes(txns[i])
for i, tx := range txns {
txHashes[i] = tx.Hash()
}
merkelRoot := CalcMerkleRoot(txHashes)
hdr := &BlockHeader{
Expand All @@ -72,8 +72,8 @@ func (b *Block) Hash() Hash {

func (b *Block) MerkleRoot() Hash {
txHashes := make([]Hash, len(b.Txns))
for i := range b.Txns {
txHashes[i] = HashBytes(b.Txns[i])
for i, tx := range b.Txns {
txHashes[i] = tx.Hash()
}
return CalcMerkleRoot(txHashes)
}
Expand Down Expand Up @@ -226,23 +226,17 @@ func (bh *BlockHeader) Hash() Hash {
func EncodeBlock(block *Block) []byte {
headerBytes := EncodeBlockHeader(block.Header)

totalSize := len(headerBytes)
for _, tx := range block.Txns {
totalSize += 4 + len(tx) // 4 bytes for transaction length
}

totalSize += 4 + len(block.Signature) // 4 bytes for signature length

buf := make([]byte, 0, totalSize)
buf := make([]byte, 0, len(headerBytes)+4+len(block.Signature)) // it's a lot more depending on txns, but we don't have size functions yet

buf = append(buf, headerBytes...)

buf = binary.LittleEndian.AppendUint32(buf, uint32(len(block.Signature)))
buf = append(buf, block.Signature...)

for _, tx := range block.Txns {
buf = binary.LittleEndian.AppendUint32(buf, uint32(len(tx)))
buf = append(buf, tx...)
rawTx := tx.Bytes()
buf = binary.LittleEndian.AppendUint32(buf, uint32(len(rawTx)))
buf = append(buf, rawTx...)
}

return buf
Expand Down Expand Up @@ -323,7 +317,7 @@ func DecodeBlock(rawBlk []byte) (*Block, error) {
return nil, fmt.Errorf("failed to read signature: %w", err)
}

txns := make([][]byte, hdr.NumTxns)
txns := make([]*Transaction, hdr.NumTxns)

for i := range txns {
var txLen uint32
Expand All @@ -335,11 +329,15 @@ func DecodeBlock(rawBlk []byte) (*Block, error) {
return nil, fmt.Errorf("invalid transaction length %d", txLen)
}

tx := make([]byte, txLen)
if _, err := io.ReadFull(r, tx); err != nil {
rawTx := make([]byte, txLen)
if _, err := io.ReadFull(r, rawTx); err != nil {
return nil, fmt.Errorf("failed to read tx data: %w", err)
}
txns[i] = tx
var tx Transaction
if err = tx.UnmarshalBinary(rawTx); err != nil {
return nil, fmt.Errorf("invalid transaction (%d): %w", i, err)
}
txns[i] = &tx
}

return &Block{
Expand Down
57 changes: 40 additions & 17 deletions core/types/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,47 @@ package types
import (
"bytes"
"encoding/binary"
"math/big"
"testing"
"time"

"github.com/kwilteam/kwil-db/core/crypto"
"github.com/kwilteam/kwil-db/core/crypto/auth"

"github.com/stretchr/testify/require"
)

func newTx(nonce uint64, sender, payload string) *Transaction {
return &Transaction{
Signature: &auth.Signature{},
Body: &TransactionBody{
Description: "test",
Payload: []byte(payload),
Fee: big.NewInt(0),
Nonce: nonce,
},
Sender: []byte(sender),
}
}

func TestGetRawBlockTx(t *testing.T) {
privKey, pubKey, err := crypto.GenerateSecp256k1Key(nil)
require.NoError(t, err)

makeRawBlock := func(txns [][]byte) []byte {
makeRawBlock := func(txnPayloads [][]byte) ([]byte, *Block) {
txns := make([]*Transaction, len(txnPayloads))
for i, pl := range txnPayloads {
txns[i] = newTx(uint64(i), "bob", string(pl))
}
blk := NewBlock(1, Hash{1, 2, 3}, Hash{6, 7, 8}, Hash{}, time.Unix(1729890593, 0), txns)
err := blk.Sign(privKey)
require.NoError(t, err)
return EncodeBlock(blk)
return EncodeBlock(blk), blk
}

t.Run("valid block signature", func(t *testing.T) {
txns := [][]byte{[]byte("tx1")}
rawBlock := makeRawBlock(txns)
rawBlock, _ := makeRawBlock(txns)
blk, err := DecodeBlock(rawBlock)
require.NoError(t, err)

Expand All @@ -39,13 +58,17 @@ func TestGetRawBlockTx(t *testing.T) {
[]byte("transaction2"),
[]byte("tx3"),
}
rawBlock := makeRawBlock(txns)
tx, err := GetRawBlockTx(rawBlock, 1)
rawBlock, blk := makeRawBlock(txns)
const idx = 1
rawTx, err := GetRawBlockTx(rawBlock, idx)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(tx, txns[1]) {
t.Errorf("got tx %x, want %x", tx, txns[1])
txOut1 := blk.Txns[idx]
rawTxOut1, err := txOut1.MarshalBinary()
require.NoError(t, err)
if !bytes.Equal(rawTx, rawTxOut1) {
t.Errorf("got tx %x, want %x", rawTx, rawTxOut1)
}
})

Expand All @@ -63,7 +86,7 @@ func TestGetRawBlockTx(t *testing.T) {

t.Run("index out of range", func(t *testing.T) {
txns := [][]byte{[]byte("tx1")}
rawBlock := makeRawBlock(txns)
rawBlock, _ := makeRawBlock(txns)

_, err := GetRawBlockTx(rawBlock, 1)
if err != ErrNotFound {
Expand All @@ -73,7 +96,7 @@ func TestGetRawBlockTx(t *testing.T) {

t.Run("corrupted block data", func(t *testing.T) {
txns := [][]byte{[]byte("tx1")}
rawBlock := makeRawBlock(txns)
rawBlock, _ := makeRawBlock(txns)
blk, err := DecodeBlock(rawBlock)
require.NoError(t, err)

Expand All @@ -87,7 +110,7 @@ func TestGetRawBlockTx(t *testing.T) {
})

t.Run("empty block", func(t *testing.T) {
rawBlock := makeRawBlock([][]byte{})
rawBlock, _ := makeRawBlock([][]byte{})

_, err := GetRawBlockTx(rawBlock, 0)
if err != ErrNotFound {
Expand Down Expand Up @@ -190,7 +213,7 @@ func TestBlock_EncodeDecode(t *testing.T) {
Timestamp: time.Now().UTC().Truncate(time.Millisecond),
MerkleRoot: Hash{10, 11, 12},
},
Txns: [][]byte{},
Txns: []*Transaction{},
Signature: []byte("test-signature"),
}

Expand All @@ -203,10 +226,10 @@ func TestBlock_EncodeDecode(t *testing.T) {
})

t.Run("encode and decode block with multiple transactions", func(t *testing.T) {
txns := [][]byte{
[]byte("tx1"),
[]byte("transaction2"),
make([]byte, 1000),
txns := []*Transaction{
newTx(0, "bob", "tx1"),
newTx(1, "bob", "transaction 2"),
newTx(0, "alice", string(make([]byte, 1000))),
}
original := &Block{
Header: &BlockHeader{
Expand All @@ -228,7 +251,7 @@ func TestBlock_EncodeDecode(t *testing.T) {
require.NoError(t, err)
require.Equal(t, original.Header, decoded.Header)
require.Equal(t, original.Signature, decoded.Signature)
require.Equal(t, original.Txns, decoded.Txns)
require.EqualExportedValues(t, original.Txns, decoded.Txns)
})

t.Run("decode with invalid signature length", func(t *testing.T) {
Expand Down Expand Up @@ -266,7 +289,7 @@ func TestBlock_EncodeDecode(t *testing.T) {
NumTxns: 1,
Timestamp: time.Now(),
},
Txns: [][]byte{[]byte("tx1")},
Txns: []*Transaction{newTx(0, "bob", "a")},
Signature: []byte("sig"),
}
encoded := EncodeBlock(original)
Expand Down
10 changes: 5 additions & 5 deletions core/types/chain/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ func (b BlockHeader) MarshalJSON() ([]byte, error) {
}

type Block struct {
Header *BlockHeader `json:"header"`
Txns [][]byte `json:"txns"`
Signature []byte `json:"signature"`
Hash types.Hash `json:"hash"`
AppHash types.Hash `json:"app_hash"`
Header *BlockHeader `json:"header"`
Txns []*types.Transaction `json:"txns"`
Signature []byte `json:"signature"`
Hash types.Hash `json:"hash"`
AppHash types.Hash `json:"app_hash"`
}

type BlockResult struct {
Expand Down
2 changes: 1 addition & 1 deletion core/types/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ type BlockExecutionStatus struct {
EndTime time.Time
Height int64
TxIDs []Hash
TxStatus map[string]bool
TxStatus map[Hash]bool
}
Loading
Loading