diff --git a/app/shared/display/message.go b/app/shared/display/message.go index 4a1b88999..dd6e9f3e6 100644 --- a/app/shared/display/message.go +++ b/app/shared/display/message.go @@ -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) @@ -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 diff --git a/app/shared/display/message_test.go b/app/shared/display/message_test.go index ffb8f6a68..f3c63ca1f 100644 --- a/app/shared/display/message_test.go +++ b/app/shared/display/message_test.go @@ -172,7 +172,8 @@ func Example_respTxQuery_json() { // "gas": 10, // "log": "This is log", // "events": null - // } + // }, + // "warning": "HASH MISMATCH: requested 0102030400000000000000000000000000000000000000000000000000000000; received a9e1f559c5ec1246078f5b9f362ee59ee4113946305d41448f917cdd96a0c883" // }, // "error": "" // } @@ -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": "" // } @@ -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() @@ -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{ @@ -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", }, } diff --git a/core/types/block.go b/core/types/block.go index dd4aae249..6188604ff 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -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{ @@ -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) } @@ -226,14 +226,7 @@ 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...) @@ -241,8 +234,9 @@ func EncodeBlock(block *Block) []byte { 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 @@ -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 @@ -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{ diff --git a/core/types/block_test.go b/core/types/block_test.go index d4cde8986..138c60abf 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -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) @@ -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) } }) @@ -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 { @@ -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) @@ -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 { @@ -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"), } @@ -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{ @@ -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) { @@ -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) diff --git a/core/types/chain/types.go b/core/types/chain/types.go index 07e28b7ad..9a03aa219 100644 --- a/core/types/chain/types.go +++ b/core/types/chain/types.go @@ -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 { diff --git a/core/types/messages.go b/core/types/messages.go index 7013aa791..2d0afdcca 100644 --- a/core/types/messages.go +++ b/core/types/messages.go @@ -48,5 +48,5 @@ type BlockExecutionStatus struct { EndTime time.Time Height int64 TxIDs []Hash - TxStatus map[string]bool + TxStatus map[Hash]bool } diff --git a/core/types/results.go b/core/types/results.go index f89627b05..ec30b62ea 100644 --- a/core/types/results.go +++ b/core/types/results.go @@ -3,6 +3,7 @@ package types import ( "encoding/binary" "errors" + "fmt" "math" ) @@ -49,20 +50,26 @@ type TxResult struct { Events []Event `json:"events"` } +// txResultsVer is the results structure or serialization version known presently +const txResultsVer uint16 = 0 + func (tr TxResult) MarshalBinary() ([]byte, error) { - data := make([]byte, 4+4, 4+4+2+2) // put 8 bytes, append the rest + data := make([]byte, 2+4+4, 2+4+4+2+2) // put 10 bytes, append the rest + + // version + binary.BigEndian.PutUint16(data, txResultsVer) // Encode code as 4 bytes - binary.BigEndian.PutUint32(data, tr.Code) + binary.BigEndian.PutUint32(data[2:], tr.Code) // Encode log length as 4 bytes for length followed by log string - binary.BigEndian.PutUint32(data[4:], uint32(len(tr.Log))) + binary.BigEndian.PutUint32(data[6:], uint32(len(tr.Log))) data = append(data, []byte(tr.Log)...) // Events numEvents := len(tr.Events) if numEvents > math.MaxUint16 { - return nil, errors.New("to many events") + return nil, errors.New("too many events") } data = binary.BigEndian.AppendUint16(data, uint16(numEvents)) for _, event := range tr.Events { @@ -78,14 +85,20 @@ func (tr TxResult) MarshalBinary() ([]byte, error) { } func (tr *TxResult) UnmarshalBinary(data []byte) error { - if len(data) < 8 { // Minimum length: 4 bytes code + 4 bytes log length + if len(data) < 10 { // Minimum length: 2 bytes version + 4 bytes code + 4 bytes log length return errors.New("insufficient data") } var offset int + version := binary.BigEndian.Uint16(data) + if version != txResultsVer { + return fmt.Errorf("unsupported version %d", version) + } + offset += 2 + // Decode code from first 4 bytes - tr.Code = binary.BigEndian.Uint32(data) + tr.Code = binary.BigEndian.Uint32(data[offset:]) offset += 4 // Decode log length and string diff --git a/core/types/transaction.go b/core/types/transaction.go index 6e5c05014..aea58b2cd 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -57,12 +57,9 @@ func (t *Transaction) StrictUnmarshal() { // Hash gives the hash of the transaction that is the unique identifier for the // transaction. -func (t *Transaction) Hash() (Hash, error) { - raw, err := t.MarshalBinary() - if err != nil { - return Hash{}, err - } - return HashBytes(raw), nil +func (t *Transaction) Hash() Hash { + raw := t.Bytes() + return HashBytes(raw) } // TransactionBody is the body of a transaction that gets included in the @@ -306,13 +303,17 @@ func (t *Transaction) WriteTo(w io.Writer) (int64, error) { var _ encoding.BinaryMarshaler = (*Transaction)(nil) +// Bytes returns the serialized transaction. +func (t *Transaction) Bytes() []byte { + bts, _ := t.MarshalBinary() // guaranteed not to error + return bts +} + // 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) { buf := new(bytes.Buffer) - if err := t.serialize(buf); err != nil { - return nil, err - } + t.serialize(buf) // guaranteed not to error with bytes.Buffer return buf.Bytes(), nil } @@ -467,9 +468,12 @@ func (tb *TransactionBody) UnmarshalBinary(data []byte) error { return nil } +const txVersion uint16 = 0 + func (t *Transaction) serialize(w io.Writer) (err error) { - if t.Body == nil { - return errors.New("missing transaction body") + // version + if err := binary.Write(w, binary.LittleEndian, txVersion); err != nil { + return fmt.Errorf("failed to write transaction version: %w", err) } // Tx Signature @@ -482,19 +486,13 @@ func (t *Transaction) serialize(w io.Writer) (err error) { } // Tx Body - if _, err := t.Body.WriteTo(w); err != nil { - return fmt.Errorf("failed to write transaction body: %w", err) - } - /*var txBodyBytes []byte - if t.Body != nil { // why support this? - txBodyBytes, err = t.Body.MarshalBinary() - if err != nil { - return fmt.Errorf("failed to marshal transaction body: %w", err) - } + var bodyBytes []byte + if t.Body != nil { + bodyBytes = t.Body.Bytes() } - if err := writeBytes(w, txBodyBytes); err != nil { + if err := writeBytes(w, bodyBytes); err != nil { return fmt.Errorf("failed to write transaction body: %w", err) - }*/ + } // SerializationType if err := writeString(w, string(t.Serialization)); err != nil { @@ -512,6 +510,16 @@ func (t *Transaction) serialize(w io.Writer) (err error) { func (t *Transaction) deserialize(r io.Reader) (int64, error) { cr := utils.NewCountingReader(r) + // version + var ver uint16 + err := binary.Read(cr, binary.LittleEndian, &ver) + if err != nil { + return cr.ReadCount(), fmt.Errorf("failed to read transaction version: %w", err) + } + if ver != txVersion { // in the future we can have different transaction (sub)structs, switch to different handling, etc. + return cr.ReadCount(), fmt.Errorf("unsupported transaction version %d", ver) + } + // Signature sigBytes, err := readBytes(cr) if err != nil { @@ -527,25 +535,20 @@ func (t *Transaction) deserialize(r io.Reader) (int64, error) { } // TxBody - var body TransactionBody - _, err = body.ReadFrom(cr) - if err != nil { - return cr.ReadCount(), fmt.Errorf("failed to read transaction body: %w", err) - } - t.Body = &body - /* if we need to support nil body... bodyBytes, err := readBytes(cr) if err != nil { return 0, fmt.Errorf("failed to read transaction body: %w", err) } if len(bodyBytes) != 0 { var body TransactionBody - body.StrictUnmarshal() + body.StrictUnmarshal() // not reading from a stream and we supposedly have the entire body here, so allow no trailing junk if err := body.UnmarshalBinary(bodyBytes); err != nil { return 0, fmt.Errorf("failed to unmarshal transaction body: %w", err) } t.Body = &body - }*/ + } else { + t.Body = nil // in case Transaction is being reused + } // SerializationType serType, err := readString(cr) diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 548c0b15f..800dbc267 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -319,15 +319,13 @@ func TestTransactionMarshalUnmarshal(t *testing.T) { require.NoError(t, err) testcases := []struct { - name string - signer auth.Signer - expectError bool - fn func(t *testing.T) *Transaction + name string + signer auth.Signer + fn func(t *testing.T) *Transaction }{ { - name: "valid transaction", - signer: secp256k1Signer(t), - expectError: false, + name: "valid transaction", + signer: secp256k1Signer(t), fn: func(t *testing.T) *Transaction { // sign tx tx := &Transaction{ @@ -368,7 +366,6 @@ func TestTransactionMarshalUnmarshal(t *testing.T) { return tx }, - expectError: false, }, { name: "empty signature type", @@ -392,7 +389,6 @@ func TestTransactionMarshalUnmarshal(t *testing.T) { return tx }, - expectError: false, }, { name: "empty signature data", @@ -415,10 +411,9 @@ func TestTransactionMarshalUnmarshal(t *testing.T) { return tx }, - expectError: false, }, { - name: "empty body", + name: "empty body (allowed now)", fn: func(t *testing.T) *Transaction { return &Transaction{ Body: nil, @@ -430,7 +425,6 @@ func TestTransactionMarshalUnmarshal(t *testing.T) { Serialization: DefaultSignedMsgSerType, } }, - expectError: true, }, { name: "empty sender", @@ -480,18 +474,13 @@ func TestTransactionMarshalUnmarshal(t *testing.T) { tx := tt.fn(t) data, err := tx.MarshalBinary() - if tt.expectError { - require.Error(t, err) - return - } else { - require.NoError(t, err) - } + require.NoError(t, err) newTx := &Transaction{} err = newTx.UnmarshalBinary(data) require.NoError(t, err) - require.Equal(t, tx, newTx) + require.EqualExportedValues(t, tx, newTx) newData, err := newTx.MarshalBinary() require.NoError(t, err) diff --git a/node/block.go b/node/block.go index 1342653f3..481fa792f 100644 --- a/node/block.go +++ b/node/block.go @@ -25,7 +25,7 @@ func (n *Node) blkGetStreamHandler(s network.Stream) { var req blockHashReq if _, err := req.ReadFrom(s); err != nil { - n.log.Warn("Bad get block (hash) request", "error", err) // Debug when we ship + n.log.Debug("Bad get block (hash) request", "error", err) return } n.log.Debug("Peer requested block", "hash", req.Hash) diff --git a/node/block_processor/processor.go b/node/block_processor/processor.go index 049691027..0835fb14d 100644 --- a/node/block_processor/processor.go +++ b/node/block_processor/processor.go @@ -221,11 +221,7 @@ func (bp *BlockProcessor) Rollback(ctx context.Context, height int64, appHash kt } func (bp *BlockProcessor) CheckTx(ctx context.Context, tx *ktypes.Transaction, recheck bool) error { - rawTx, err := tx.MarshalBinary() - if err != nil { - return fmt.Errorf("invalid transaction: %v", err) // e.g. missing fields - } - txHash := types.HashBytes(rawTx) + txHash := tx.Hash() // If the network is halted for migration, we reject all transactions. if bp.chainCtx.NetworkParameters.MigrationStatus == ktypes.MigrationCompleted { @@ -367,31 +363,26 @@ func (bp *BlockProcessor) ExecuteBlock(ctx context.Context, req *ktypes.BlockExe txResults := make([]ktypes.TxResult, len(req.Block.Txns)) - bp.initBlockExecutionStatus(req.Block) + txHashes := bp.initBlockExecutionStatus(req.Block) for i, tx := range req.Block.Txns { - decodedTx := &ktypes.Transaction{} - if err := decodedTx.UnmarshalBinary(tx); err != nil { - // bp.log.Error("Failed to unmarshal the block tx", "err", err) - return nil, fmt.Errorf("failed to unmarshal the block tx: %w", err) - } - txHash := types.HashBytes(tx) - - auth := auth.GetAuthenticator(decodedTx.Signature.Type) + auth := auth.GetAuthenticator(tx.Signature.Type) if auth == nil { - return nil, fmt.Errorf("unsupported signature type: %v", decodedTx.Signature.Type) + return nil, fmt.Errorf("unsupported signature type: %v", tx.Signature.Type) } - identifier, err := auth.Identifier(decodedTx.Sender) + identifier, err := auth.Identifier(tx.Sender) if err != nil { return nil, fmt.Errorf("failed to get identifier for the block tx: %w", err) } + txHash := txHashes[i] + txCtx := &common.TxContext{ Ctx: ctx, - TxID: hex.EncodeToString(txHash[:]), - Signer: decodedTx.Sender, - Authenticator: decodedTx.Signature.Type, + TxID: txHash.String(), + Signer: tx.Sender, + Authenticator: tx.Signature.Type, Caller: identifier, BlockContext: blockCtx, } @@ -400,7 +391,7 @@ func (bp *BlockProcessor) ExecuteBlock(ctx context.Context, req *ktypes.BlockExe case <-ctx.Done(): return nil, ctx.Err() // notify the caller about the context cancellation or deadline exceeded error default: - res := bp.txapp.Execute(txCtx, bp.consensusTx, decodedTx) + res := bp.txapp.Execute(txCtx, bp.consensusTx, tx) txResult := ktypes.TxResult{ Code: uint32(res.ResponseCode), Gas: res.Spend, @@ -580,7 +571,7 @@ func (bp *BlockProcessor) Commit(ctx context.Context, req *ktypes.CommitRequest) // that consensus limits such as the maximum block size, maxVotesPerTx are met. It also adds // validator vote transactions for events observed by the leader. This function is // used exclusively by the leader node to prepare the proposal block. -func (bp *BlockProcessor) PrepareProposal(ctx context.Context, txs [][]byte) (finalTxs [][]byte, invalidTxs [][]byte, err error) { +func (bp *BlockProcessor) PrepareProposal(ctx context.Context, txs []*ktypes.Transaction) (finalTxs []*ktypes.Transaction, invalidTxs []*ktypes.Transaction, err error) { // unmarshal and index the transactions return bp.prepareBlockTransactions(ctx, txs) } diff --git a/node/block_processor/status.go b/node/block_processor/status.go index b703e16f9..85de404c7 100644 --- a/node/block_processor/status.go +++ b/node/block_processor/status.go @@ -11,7 +11,7 @@ type blockExecStatus struct { startTime, endTime time.Time height int64 txIDs []ktypes.Hash - txStatus map[string]bool + txStatus map[ktypes.Hash]bool } // Used by the rpc server to get the execution status of the block being processed. @@ -29,7 +29,7 @@ func (bp *BlockProcessor) BlockExecutionStatus() *ktypes.BlockExecutionStatus { EndTime: bp.status.endTime, Height: bp.status.height, TxIDs: slices.Clone(bp.status.txIDs), - TxStatus: make(map[string]bool), + TxStatus: make(map[ktypes.Hash]bool, len(bp.status.txStatus)), } for k, v := range bp.status.txStatus { @@ -39,24 +39,29 @@ func (bp *BlockProcessor) BlockExecutionStatus() *ktypes.BlockExecutionStatus { return status } -func (bp *BlockProcessor) initBlockExecutionStatus(blk *ktypes.Block) { +func (bp *BlockProcessor) initBlockExecutionStatus(blk *ktypes.Block) []ktypes.Hash { + txIDs := make([]ktypes.Hash, len(blk.Txns)) + for i, tx := range blk.Txns { + txID := tx.Hash() + txIDs[i] = txID + } bp.statusMu.Lock() defer bp.statusMu.Unlock() status := &blockExecStatus{ startTime: time.Now(), height: blk.Header.Height, - txStatus: make(map[string]bool), - txIDs: make([]ktypes.Hash, len(blk.Txns)), + txStatus: make(map[ktypes.Hash]bool, len(txIDs)), + txIDs: txIDs, } - for i, tx := range blk.Txns { - txID := ktypes.HashBytes(tx) - status.txIDs[i] = txID - status.txStatus[txID.String()] = false // not needed, just for clarity + for _, txID := range txIDs { + status.txStatus[txID] = false // not needed, just for clarity } bp.status = status + + return txIDs } func (bp *BlockProcessor) clearBlockExecutionStatus() { @@ -70,7 +75,7 @@ func (bp *BlockProcessor) updateBlockExecutionStatus(txID ktypes.Hash) { bp.statusMu.Lock() defer bp.statusMu.Unlock() - bp.status.txStatus[txID.String()] = true + bp.status.txStatus[txID] = true } func (bp *BlockProcessor) recordBlockExecEndTime() { diff --git a/node/block_processor/transactions.go b/node/block_processor/transactions.go index 84cd8e5ba..46ac56def 100644 --- a/node/block_processor/transactions.go +++ b/node/block_processor/transactions.go @@ -54,6 +54,8 @@ func (txl txSubList) Swap(i int, j int) { type indexedTxn struct { i int // index in superset slice *types.Transaction + sz int + hash types.Hash is int // not used for sorting, only referencing the marshalled txn slice } @@ -63,20 +65,15 @@ type indexedTxn struct { // enforces block size limits, and applies the maxVotesPerTx limit for voteID transactions. // Additionally, it includes the ValidatorVoteBody transaction for unresolved events. // The final transaction order is: MempoolProposerTxns, ValidatorVoteBodyTx, Other MempoolTxns (Nonce ordered, stable sorted). -func (bp *BlockProcessor) prepareBlockTransactions(ctx context.Context, txs [][]byte) (finalTxs [][]byte, invalidTxs [][]byte, err error) { +func (bp *BlockProcessor) prepareBlockTransactions(ctx context.Context, txs []*ktypes.Transaction) (finalTxs []*ktypes.Transaction, invalidTxs []*ktypes.Transaction, err error) { // Unmarshal and index the transactions. var okTxns []*indexedTxn - invalidTxs = make([][]byte, 0, len(txs)) + invalidTxs = make([]*ktypes.Transaction, 0, len(txs)) var i int for is, tx := range txs { - txn := &types.Transaction{} - if err = txn.UnmarshalBinary(tx); err != nil { - invalidTxs = append(invalidTxs, tx) - bp.log.Error("Failed to unmarshal transaction that was previously accepted to mempool", "error", err) - continue - } - okTxns = append(okTxns, &indexedTxn{i, txn, is}) + rawTx := tx.Bytes() + okTxns = append(okTxns, &indexedTxn{i, tx, len(rawTx), types.HashBytes(rawTx), is}) i++ } @@ -98,8 +95,7 @@ func (bp *BlockProcessor) prepareBlockTransactions(ctx context.Context, txs [][] readTx, err := bp.db.BeginReadTx(ctx) if err != nil { - bp.log.Error("Failed to begin read transaction", "error", err) - return nil, nil, err + return nil, nil, fmt.Errorf("failed to begin read transaction: %w", err) } defer readTx.Rollback(ctx) @@ -114,14 +110,14 @@ func (bp *BlockProcessor) prepareBlockTransactions(ctx context.Context, txs [][] // Enforce maxVptesPerTx limit for voteID transactions if tx.Body.PayloadType == types.PayloadTypeValidatorVoteIDs { voteIDs := &types.ValidatorVoteIDs{} - if err = voteIDs.UnmarshalBinary(tx.Body.Payload); err != nil { - invalidTxs = append(invalidTxs, txs[tx.is]) + if err := voteIDs.UnmarshalBinary(tx.Body.Payload); err != nil { + invalidTxs = append(invalidTxs, tx.Transaction) bp.log.Warn("Dropping voteID tx: failed to unmarshal ValidatorVoteIDs transaction", "error", err) continue } if len(voteIDs.ResolutionIDs) > int(bp.chainCtx.NetworkParameters.MaxVotesPerTx) { - invalidTxs = append(invalidTxs, txs[tx.is]) + invalidTxs = append(invalidTxs, tx.Transaction) bp.log.Warn("Dropping voteID tx: exceeds max votes per tx", "numVotes", len(voteIDs.ResolutionIDs), "maxVotes", bp.chainCtx.NetworkParameters.MaxVotesPerTx) continue @@ -137,7 +133,7 @@ func (bp *BlockProcessor) prepareBlockTransactions(ctx context.Context, txs [][] } if nonce == 0 && balance.Sign() == 0 { - invalidTxs = append(invalidTxs, txs[tx.is]) + invalidTxs = append(invalidTxs, tx.Transaction) bp.log.Warn("Dropping tx from unfunded account while preparing the block", "account", hex.EncodeToString(tx.Sender)) continue } @@ -156,28 +152,27 @@ func (bp *BlockProcessor) prepareBlockTransactions(ctx context.Context, txs [][] // Enforce block size limits // Txs order: MempoolProposerTxns, ProposerInjectedTxns, MempoolTxns - finalTxs = make([][]byte, 0) + finalTxs = make([]*ktypes.Transaction, 0, len(otherTxns)+len(propTxs)+1) maxTxBytes := bp.chainCtx.NetworkParameters.MaxBlockSize for _, tx := range propTxs { - txBts := txs[tx.is] - txSize := int64(len(txBts)) + txSize := int64(tx.sz) if maxTxBytes < txSize { break } maxTxBytes -= txSize - finalTxs = append(finalTxs, txBts) + finalTxs = append(finalTxs, tx.Transaction) } - var voteBodyTx []byte // TODO: check proposerNonce value again + var voteBodyTx *ktypes.Transaction // TODO: check proposerNonce value again voteBodyTx, err = bp.prepareValidatorVoteBodyTx(ctx, int64(proposerNonce), maxTxBytes) if err != nil { - bp.log.Error("Failed to prepare validator vote body transaction", "error", err) - return nil, nil, err + return nil, nil, fmt.Errorf("failed to prepare validator vote body transaction: %w", err) } if voteBodyTx != nil { finalTxs = append(finalTxs, voteBodyTx) - maxTxBytes -= int64(len(voteBodyTx)) + voteBodyTxBts := voteBodyTx.Bytes() + maxTxBytes -= int64(len(voteBodyTxBts)) } // senders tracks the sender of transactions that has pushed over the bytes limit for the block. @@ -192,7 +187,7 @@ func (bp *BlockProcessor) prepareBlockTransactions(ctx context.Context, txs [][] continue } - txSize := int64(len(txs[tx.is])) + txSize := int64(tx.sz) if maxTxBytes < txSize { // Ignore the transaction and all subsequent transactions from the sender senders[sender] = true @@ -200,10 +195,10 @@ func (bp *BlockProcessor) prepareBlockTransactions(ctx context.Context, txs [][] } maxTxBytes -= txSize - finalTxs = append(finalTxs, txs[tx.is]) + finalTxs = append(finalTxs, tx.Transaction) } - return finalTxs, invalidTxs, err + return finalTxs, invalidTxs, nil } // prepareValidatorVoteBodyTx authors the ValidatorVoteBody transaction to be included by the leader in the block. @@ -211,7 +206,7 @@ func (bp *BlockProcessor) prepareBlockTransactions(ctx context.Context, txs [][] // The number of events to be included in a single transaction is limited either by MaxVotesPerTx or the maxTxSize // whichever is reached first. The estimated fee for validatorVOteBodies transaction is directly proportional to // the size of the event body. The transaction is signed by the leader and returned. -func (bp *BlockProcessor) prepareValidatorVoteBodyTx(ctx context.Context, nonce int64, maxTxSize int64) ([]byte, error) { +func (bp *BlockProcessor) prepareValidatorVoteBodyTx(ctx context.Context, nonce int64, maxTxSize int64) (*ktypes.Transaction, error) { readTx, err := bp.db.BeginReadTx(ctx) if err != nil { return nil, err @@ -313,14 +308,9 @@ func (bp *BlockProcessor) prepareValidatorVoteBodyTx(ctx context.Context, nonce return nil, err } - bts, err := tx.MarshalBinary() - if err != nil { - return nil, err - } - bp.log.Info("Created a ValidatorVoteBody transaction", "events", len(finalEvents), "nonce", n, "GasPrice", fee.String()) - return bts, nil + return tx, nil } // emptyVodeBodyTxSize returns the size of an empty validator vote body transaction. @@ -340,10 +330,7 @@ func (bp *BlockProcessor) emptyVoteBodyTxSize() (int64, error) { return -1, err } - bts, err := tx.MarshalBinary() - if err != nil { - return -1, err - } + bts := tx.Bytes() return int64(len(bts)), nil } diff --git a/node/block_processor/transactions_test.go b/node/block_processor/transactions_test.go index baf3e025a..dd9d5da42 100644 --- a/node/block_processor/transactions_test.go +++ b/node/block_processor/transactions_test.go @@ -1,7 +1,6 @@ package blockprocessor import ( - "bytes" "context" "math/big" "testing" @@ -18,13 +17,13 @@ import ( "github.com/stretchr/testify/require" ) -func marshalTx(t *testing.T, tx *types.Transaction) []byte { +/*func marshalTx(t *testing.T, tx *types.Transaction) []byte { b, err := tx.MarshalBinary() if err != nil { t.Fatalf("could not marshal transaction! %v", err) } return b -} +}*/ func cloneTx(tx *types.Transaction) *types.Transaction { sig := make([]byte, len(tx.Signature.Data)) @@ -66,19 +65,20 @@ func TestPrepareMempoolTxns(t *testing.T) { // tA is the template transaction. Several fields may not be nil because of // a legacy RLP issue where objects may be encoded that cannot be decoded. - bp := &BlockProcessor{ - db: &mockDB{}, - log: log.DiscardLogger, - signer: secp256k1Signer(t), - chainCtx: &common.ChainContext{ - ChainID: "test", - NetworkParameters: &common.NetworkParameters{ - MaxBlockSize: 6 * 1024 * 1024, - MaxVotesPerTx: 100, - DisabledGasCosts: true, - }, + chainCtx := &common.ChainContext{ + ChainID: "test", + NetworkParameters: &common.NetworkParameters{ + MaxBlockSize: 6 * 1024 * 1024, + MaxVotesPerTx: 100, + DisabledGasCosts: true, }, - txapp: &mockTxApp{}, + } + bp := &BlockProcessor{ + db: &mockDB{}, + log: log.DiscardLogger, + signer: secp256k1Signer(t), + chainCtx: chainCtx, + txapp: &mockTxApp{}, } tA := &types.Transaction{ @@ -89,17 +89,17 @@ func TestPrepareMempoolTxns(t *testing.T) { Body: &types.TransactionBody{ Description: "t", Payload: []byte(`x`), - Fee: big.NewInt(0), + Fee: big.NewInt(900), Nonce: 0, }, Sender: []byte(`guy`), } - tAb := marshalTx(t, tA) + // tAb := marshalTx(t, tA) // same sender, incremented nonce tB := cloneTx(tA) tB.Body.Nonce++ - tBb := marshalTx(t, tB) + // tBb := marshalTx(t, tB) nextTx := func(tx *types.Transaction) *types.Transaction { tx2 := cloneTx(tx) @@ -110,100 +110,110 @@ func TestPrepareMempoolTxns(t *testing.T) { // second party tOtherSenderA := cloneTx(tA) tOtherSenderA.Sender = []byte(`otherguy`) - tOtherSenderAb := marshalTx(t, tOtherSenderA) + // tOtherSenderAb := marshalTx(t, tOtherSenderA) // Same nonce tx, different body (diff bytes) tOtherSenderAbDup := cloneTx(tOtherSenderA) tOtherSenderAbDup.Body.Description = "dup" // not "t" - tOtherSenderAbDupb := marshalTx(t, tOtherSenderAbDup) + // tOtherSenderAbDupb := marshalTx(t, tOtherSenderAbDup) tOtherSenderB := nextTx(tOtherSenderA) - tOtherSenderBb := marshalTx(t, tOtherSenderB) + // tOtherSenderBb := marshalTx(t, tOtherSenderB) tOtherSenderC := nextTx(tOtherSenderB) - tOtherSenderCb := marshalTx(t, tOtherSenderC) + // tOtherSenderCb := marshalTx(t, tOtherSenderC) // proposer party tProposer := cloneTx(tA) tProposer.Sender = bp.signer.Identity() - tProposerb := marshalTx(t, tProposer) + // tProposerb := marshalTx(t, tProposer) - invalid := []byte{9, 90, 22} + zeroFeeTx := cloneTx(tA) + zeroFeeTx.Body.Fee = &big.Int{} tests := []struct { name string - txs [][]byte - want [][]byte + txs []*ktypes.Transaction + want []*ktypes.Transaction + gas bool }{ { "empty", - [][]byte{}, - [][]byte{}, - }, - { - "one and only invalid", - [][]byte{invalid}, - [][]byte{}, + []*ktypes.Transaction{}, + []*ktypes.Transaction{}, + false, }, { - "one of two invalid", - [][]byte{invalid, tBb}, - [][]byte{tBb}, + "one and only low gas", + []*ktypes.Transaction{zeroFeeTx}, + []*ktypes.Transaction{}, + true, }, { "one valid", - [][]byte{tAb}, - [][]byte{tAb}, + []*ktypes.Transaction{tA}, + []*ktypes.Transaction{tA}, + false, }, { "two valid", - [][]byte{tAb, tBb}, - [][]byte{tAb, tBb}, + []*ktypes.Transaction{tA, tB}, + []*ktypes.Transaction{tA, tB}, + false, }, { "two valid misordered", - [][]byte{tBb, tAb}, - [][]byte{tAb, tBb}, + []*ktypes.Transaction{tB, tA}, + []*ktypes.Transaction{tA, tB}, + false, }, { "multi-party, one misordered, stable", - [][]byte{tOtherSenderAb, tBb, tOtherSenderBb, tAb}, - [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tBb}, + []*ktypes.Transaction{tOtherSenderA, tB, tOtherSenderB, tA}, + []*ktypes.Transaction{tOtherSenderA, tA, tOtherSenderB, tB}, + false, }, { "multi-party, one misordered, one dup nonce, stable", - [][]byte{tOtherSenderAb, tOtherSenderAbDupb, tBb, tAb}, - [][]byte{tOtherSenderAb, tAb, tBb}, + []*ktypes.Transaction{tOtherSenderA, tOtherSenderAbDup, tB, tA}, + []*ktypes.Transaction{tOtherSenderA, tA, tB}, + false, }, { "multi-party, both misordered, stable", - [][]byte{tOtherSenderBb, tBb, tOtherSenderAb, tAb}, - [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tBb}, + []*ktypes.Transaction{tOtherSenderB, tB, tOtherSenderA, tA}, + []*ktypes.Transaction{tOtherSenderA, tA, tOtherSenderB, tB}, + false, }, { "multi-party, both misordered, alt. stable", - [][]byte{tBb, tOtherSenderBb, tOtherSenderAb, tAb}, - [][]byte{tAb, tOtherSenderAb, tOtherSenderBb, tBb}, - }, - { - "multi-party, big, with invalid in middle", - [][]byte{tOtherSenderCb, tBb, invalid, tOtherSenderBb, tOtherSenderAb, tAb}, - [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb}, - }, + []*ktypes.Transaction{tB, tOtherSenderB, tOtherSenderA, tA}, + []*ktypes.Transaction{tA, tOtherSenderA, tOtherSenderB, tB}, + false, + }, + // { // can't mix fee... + // "multi-party, big, with invalid in middle", + // []*ktypes.Transaction{tOtherSenderC, tB, zeroFeeTx, tOtherSenderB, tOtherSenderA, tA}, + // []*ktypes.Transaction{tOtherSenderA, tA, tOtherSenderB, tOtherSenderC, tB}, + // true, + // }, { "multi-party, big, already correct", - [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb}, - [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb}, + []*ktypes.Transaction{tOtherSenderA, tA, tOtherSenderB, tOtherSenderC, tB}, + []*ktypes.Transaction{tOtherSenderA, tA, tOtherSenderB, tOtherSenderC, tB}, + false, }, { "multi-party,proposer in the last, reorder", - [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb, tProposerb}, - [][]byte{tProposerb, tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb}, + []*ktypes.Transaction{tOtherSenderA, tA, tOtherSenderB, tOtherSenderC, tB, tProposer}, + []*ktypes.Transaction{tProposer, tOtherSenderA, tA, tOtherSenderB, tOtherSenderC, tB}, + false, }, { "multi-party,proposer in the middle, reorder", - [][]byte{tOtherSenderAb, tAb, tOtherSenderBb, tProposerb, tOtherSenderCb, tBb}, - [][]byte{tProposerb, tOtherSenderAb, tAb, tOtherSenderBb, tOtherSenderCb, tBb}, + []*ktypes.Transaction{tOtherSenderA, tA, tOtherSenderB, tProposer, tOtherSenderC, tB}, + []*ktypes.Transaction{tProposer, tOtherSenderA, tA, tOtherSenderB, tOtherSenderC, tB}, + false, }, } @@ -214,15 +224,21 @@ func TestPrepareMempoolTxns(t *testing.T) { return nil, nil } - got, _, err := bp.prepareBlockTransactions(ctx, tt.txs) + chainCtx.NetworkParameters.DisabledGasCosts = !tt.gas + + got, invalids, err := bp.prepareBlockTransactions(ctx, tt.txs) require.NoError(t, err) + if len(got) != len(tt.want) { t.Errorf("got %d txns, expected %d", len(got), len(tt.want)) } + + require.Equal(t, len(invalids), len(tt.txs)-len(got)) + for i, txi := range got { - if !bytes.Equal(txi, tt.want[i]) { - t.Errorf("mismatched tx %d", i) - } + gotHash := txi.Hash() + wantHash := tt.want[i].Hash() + require.Equal(t, gotHash, wantHash) } }) } @@ -492,12 +508,8 @@ func TestPrepareVoteBodyTx(t *testing.T) { require.NoError(t, err) require.NotNil(t, tx) - txn := &types.Transaction{} - err = txn.UnmarshalBinary(tx) - require.NoError(t, err) - var payload = &ktypes.ValidatorVoteBodies{} - err = payload.UnmarshalBinary(txn.Body.Payload) + err = payload.UnmarshalBinary(tx.Body.Payload) require.NoError(t, err) require.Len(t, payload.Events, 2) @@ -514,12 +526,8 @@ func TestPrepareVoteBodyTx(t *testing.T) { require.NoError(t, err) require.NotNil(t, tx) - txn := &types.Transaction{} - err = txn.UnmarshalBinary(tx) - require.NoError(t, err) - var payload = &ktypes.ValidatorVoteBodies{} - err = payload.UnmarshalBinary(txn.Body.Payload) + err = payload.UnmarshalBinary(tx.Body.Payload) require.NoError(t, err) require.Len(t, payload.Events, 1) @@ -545,12 +553,8 @@ func TestPrepareVoteBodyTx(t *testing.T) { require.NoError(t, err) require.NotNil(t, tx) - txn := &types.Transaction{} - err = txn.UnmarshalBinary(tx) - require.NoError(t, err) - var payload = &ktypes.ValidatorVoteBodies{} - err = payload.UnmarshalBinary(txn.Body.Payload) + err = payload.UnmarshalBinary(tx.Body.Payload) require.NoError(t, err) require.Len(t, payload.Events, 1) @@ -591,12 +595,8 @@ func TestPrepareVoteBodyTx(t *testing.T) { require.NoError(t, err) require.NotNil(t, tx) - txn := &types.Transaction{} - err = txn.UnmarshalBinary(tx) - require.NoError(t, err) - var payload = &ktypes.ValidatorVoteBodies{} - err = payload.UnmarshalBinary(txn.Body.Payload) + err = payload.UnmarshalBinary(tx.Body.Payload) require.NoError(t, err) require.Len(t, payload.Events, 2) @@ -673,7 +673,7 @@ func (m *mockTxApp) GetValidators(ctx context.Context, db sql.DB) ([]*types.Vali return nil, nil } -func (m *mockTxApp) ProposerTxs(ctx context.Context, db sql.DB, txNonce uint64, maxTxSz int64, block *common.BlockContext) ([][]byte, error) { +func (m *mockTxApp) ProposerTxs(ctx context.Context, db sql.DB, txNonce uint64, maxTxSz int64, block *common.BlockContext) ([]*ktypes.Transaction, error) { return nil, nil } diff --git a/node/consensus/block.go b/node/consensus/block.go index 3d278a876..5ab869d23 100644 --- a/node/consensus/block.go +++ b/node/consensus/block.go @@ -6,7 +6,6 @@ import ( "fmt" ktypes "github.com/kwilteam/kwil-db/core/types" - "github.com/kwilteam/kwil-db/node/types" ) // TODO: should include consensus params hash @@ -116,7 +115,7 @@ func (ce *ConsensusEngine) commit(ctx context.Context) error { // remove transactions from the mempool for _, txn := range blkProp.blk.Txns { - txHash := types.HashBytes(txn) // TODO: can this be saved instead of recalculating? + txHash := txn.Hash() ce.mempool.Remove(txHash) } diff --git a/node/consensus/interfaces.go b/node/consensus/interfaces.go index 54c79e7c6..81fe32a48 100644 --- a/node/consensus/interfaces.go +++ b/node/consensus/interfaces.go @@ -43,7 +43,7 @@ type BlockProcessor interface { InitChain(ctx context.Context) (int64, []byte, error) SetBroadcastTxFn(fn blockprocessor.BroadcastTxFn) - PrepareProposal(ctx context.Context, txs [][]byte) (finalTxs [][]byte, invalidTxs [][]byte, err error) + PrepareProposal(ctx context.Context, txs []*ktypes.Transaction) (finalTxs []*ktypes.Transaction, invalidTxs []*ktypes.Transaction, err error) ExecuteBlock(ctx context.Context, req *ktypes.BlockExecRequest) (*ktypes.BlockExecResult, error) Commit(ctx context.Context, req *ktypes.CommitRequest) error Rollback(ctx context.Context, height int64, appHash ktypes.Hash) error diff --git a/node/consensus/leader.go b/node/consensus/leader.go index c952d2ecf..723f309b5 100644 --- a/node/consensus/leader.go +++ b/node/consensus/leader.go @@ -138,17 +138,9 @@ func (ce *ConsensusEngine) startNewRound(ctx context.Context) error { // does basic gas and balance checks and enforces the block size limits. func (ce *ConsensusEngine) createBlockProposal(ctx context.Context) (*blockProposal, error) { nTxs := ce.mempool.PeekN(blockTxCount) - var txns [][]byte - for _, namedTx := range nTxs { - rawTx, err := namedTx.Tx.MarshalBinary() - if err != nil { // this is a bug - ce.log.Errorf("invalid transaction from mempool rejected", - "hash", namedTx.Hash, "error", err) - ce.mempool.Remove(namedTx.Hash) - continue - // return nil, fmt.Errorf("invalid transaction: %v", err) // e.g. nil/missing body - } - txns = append(txns, rawTx) + txns := make([]*ktypes.Transaction, len(nTxs)) + for i, ntx := range nTxs { + txns[i] = ntx.Tx } finalTxs, invalidTxs, err := ce.blockProcessor.PrepareProposal(ctx, txns) @@ -159,7 +151,8 @@ func (ce *ConsensusEngine) createBlockProposal(ctx context.Context) (*blockPropo // remove invalid transactions from the mempool for _, tx := range invalidTxs { - ce.mempool.Remove(types.Hash(tx)) + txid := tx.Hash() + ce.mempool.Remove(txid) } valSetHash := ce.validatorSetHash() diff --git a/node/node.go b/node/node.go index ef80f1eab..7ec553db4 100644 --- a/node/node.go +++ b/node/node.go @@ -635,7 +635,7 @@ func (n *Node) TxQuery(ctx context.Context, hash types.Hash, prove bool) (*ktype } func (n *Node) BroadcastTx(ctx context.Context, tx *ktypes.Transaction, _ /*sync TODO*/ uint8) (*ktypes.ResultBroadcastTx, error) { - rawTx, _ := tx.MarshalBinary() + rawTx := tx.Bytes() txHash := types.HashBytes(rawTx) if err := n.ce.CheckTx(ctx, tx); err != nil { diff --git a/node/node_test.go b/node/node_test.go index e03dc1542..4d9ed7c3b 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "math/big" "net" "os" "slices" @@ -19,6 +20,7 @@ import ( "github.com/kwilteam/kwil-db/config" "github.com/kwilteam/kwil-db/core/crypto" + "github.com/kwilteam/kwil-db/core/crypto/auth" "github.com/kwilteam/kwil-db/core/log" ktypes "github.com/kwilteam/kwil-db/core/types" blockprocessor "github.com/kwilteam/kwil-db/node/block_processor" @@ -177,14 +179,28 @@ func fakeAppHash(height int64) types.Hash { return types.HashBytes(binary.LittleEndian.AppendUint64(nil, uint64(height))) } -func createTestBlock(height int64, numTxns int) (*ktypes.Block, types.Hash) { - txns := make([][]byte, numTxns) +func newTx(nonce uint64, sender, payload string) *ktypes.Transaction { + return &ktypes.Transaction{ + Signature: &auth.Signature{}, + Body: &ktypes.TransactionBody{ + Description: "test", + Payload: []byte(payload), + Fee: big.NewInt(0), + Nonce: nonce, + }, + Sender: []byte(sender), + } +} + +func createTestBlock(t *testing.T, height int64, numTxns int) (*ktypes.Block, types.Hash) { + txns := make([]*ktypes.Transaction, numTxns) for i := range numTxns { - txns[i] = []byte(strconv.FormatInt(height, 10) + strconv.Itoa(i) + + txns[i] = newTx(uint64(i), "bob", strconv.FormatInt(height, 10)+strconv.Itoa(i)+ strings.Repeat("data", 1000)) } - return ktypes.NewBlock(height, types.Hash{2, 3, 4}, types.Hash{6, 7, 8}, types.Hash{5, 5, 5}, - time.Unix(1729723553+height, 0), txns), fakeAppHash(height) + blk := ktypes.NewBlock(height, types.Hash{2, 3, 4}, types.Hash{6, 7, 8}, types.Hash{5, 5, 5}, + time.Unix(1729723553+height, 0), txns) + return blk, fakeAppHash(height) } func newGenesis(t *testing.T, nodekeys [][]byte) ([]crypto.PrivateKey, *config.GenesisConfig) { @@ -475,7 +491,7 @@ func TestStreamsBlockFetch(t *testing.T) { h1 := n1.host // to n1's block store, one block at height 1 with 2 txns - blk1, appHash1 := createTestBlock(1, 2) + blk1, appHash1 := createTestBlock(t, 1, 2) n1.bki.Store(blk1, appHash1) startNodes(t, nodes) diff --git a/node/nogossip.go b/node/nogossip.go index 44eed40da..127e9b8ab 100644 --- a/node/nogossip.go +++ b/node/nogossip.go @@ -227,11 +227,7 @@ func (n *Node) startTxAnns(ctx context.Context, reannouncePeriod time.Duration) n.log.Infof("re-announcing %d unconfirmed txns", len(txns)) for _, nt := range txns { - rawTx, err := nt.Tx.MarshalBinary() - if err != nil { - n.log.Errorf("Failed to marshal transaction %v: %v", nt.Hash, err) - continue - } + rawTx := nt.Tx.Bytes() n.announceTx(ctx, nt.Hash, rawTx, n.host.ID()) // response handling is async if ctx.Err() != nil { n.log.Warn("interrupting long re-broadcast") diff --git a/node/services/jsonrpc/adminsvc/service.go b/node/services/jsonrpc/adminsvc/service.go index 3b1b43d36..5f57547dd 100644 --- a/node/services/jsonrpc/adminsvc/service.go +++ b/node/services/jsonrpc/adminsvc/service.go @@ -594,7 +594,7 @@ func (svc *Service) BlockExecStatus(ctx context.Context, req *adminjson.BlockExe for i, txID := range status.TxIDs { txInfo[i] = &types.TxInfo{ ID: txID, - Status: status.TxStatus[txID.String()], + Status: status.TxStatus[txID], } } resp.TxInfo = txInfo diff --git a/node/store/memstore/memstore.go b/node/store/memstore/memstore.go index 23349d267..2beae2e63 100644 --- a/node/store/memstore/memstore.go +++ b/node/store/memstore/memstore.go @@ -85,7 +85,7 @@ func (bs *MemBS) Store(block *ktypes.Block, appHash types.Hash) error { appHash: appHash, } for _, tx := range block.Txns { - txHash := types.HashBytes(tx) + txHash := tx.Hash() bs.txIds[txHash] = blkHash } return nil @@ -170,12 +170,9 @@ func (bs *MemBS) GetTx(txHash types.Hash) (tx *ktypes.Transaction, height int64, if !have { return nil, 0, types.Hash{}, 0, types.ErrNotFound } - for idx, rawTx := range blk.Txns { - if types.HashBytes(rawTx) == txHash { - tx := new(ktypes.Transaction) - if err = tx.UnmarshalBinary(rawTx); err != nil { - return nil, 0, types.Hash{}, 0, err - } + for idx, tx := range blk.Txns { + txHashi := tx.Hash() + if txHashi == txHash { return tx, blk.Header.Height, blk.Hash(), uint32(idx), nil } } diff --git a/node/store/memstore/memstore_test.go b/node/store/memstore/memstore_test.go index 5cbd2804a..8a0934252 100644 --- a/node/store/memstore/memstore_test.go +++ b/node/store/memstore/memstore_test.go @@ -30,7 +30,7 @@ func newTx(nonce uint64, sender string) *ktypes.Transaction { } } -func createTestBlock(height int64, numTxns int) (*ktypes.Block, types.Hash, []*ktypes.Transaction) { +func createTestBlock(t *testing.T, height int64, numTxns int) (*ktypes.Block, types.Hash) { txs := make([]*ktypes.Transaction, numTxns) txns := make([][]byte, numTxns) for i := range numTxns { @@ -43,14 +43,15 @@ func createTestBlock(height int64, numTxns int) (*ktypes.Block, types.Hash, []*k txs[i] = tx txns[i] = rawTx } - return ktypes.NewBlock(height, types.Hash{2, 3, 4}, types.Hash{6, 7, 8}, types.Hash{5, 5, 5}, - time.Unix(1729723553+height, 0), txns), fakeAppHash(height), txs + blk := ktypes.NewBlock(height, types.Hash{2, 3, 4}, types.Hash{6, 7, 8}, types.Hash{5, 5, 5}, + time.Unix(1729723553+height, 0), txs) + return blk, fakeAppHash(height) } func TestMemBS_StoreAndGet(t *testing.T) { bs := NewMemBS() - block, appHash, _ := createTestBlock(1, 2) + block, appHash := createTestBlock(t, 1, 2) err := bs.Store(block, appHash) if err != nil { @@ -147,14 +148,14 @@ func TestMemBS_StoreAndGetTx(t *testing.T) { // tx2 := []byte("tx2") // txns := [][]byte{tx1, tx2} // block := types.NewBlock(1, prevHash, appHash, valSetHash, time.Unix(123456789, 0), txns) - block, _, _ := createTestBlock(1, 2) + block, _ := createTestBlock(t, 1, 2) tx1 := block.Txns[0] if err := bs.Store(block, types.Hash{1, 2, 3}); err != nil { t.Fatal(err) } - txHash := types.HashBytes(tx1) + txHash := tx1.Hash() gotTx, height, hash, idx, err := bs.GetTx(txHash) if err != nil { t.Fatal(err) @@ -164,12 +165,13 @@ func TestMemBS_StoreAndGetTx(t *testing.T) { t.Errorf("got height %d, want 1", height) } + rawTx1, _ := tx1.MarshalBinary() gotRawTx, err := gotTx.MarshalBinary() if err != nil { t.Fatal(err) } - if !bytes.Equal(gotRawTx, tx1) { - t.Errorf("got tx %x, want %x", gotRawTx, tx1) + if !bytes.Equal(gotRawTx, rawTx1) { + t.Errorf("got tx %x, want %x", gotRawTx, rawTx1) } if blkHash := block.Hash(); hash != blkHash { diff --git a/node/store/store.go b/node/store/store.go index 16a5cb350..c5f7968ec 100644 --- a/node/store/store.go +++ b/node/store/store.go @@ -287,6 +287,13 @@ func (bki *BlockStore) Store(blk *ktypes.Block, appHash types.Hash) error { blkHash := blk.Hash() height := blk.Header.Height + rawBlk := ktypes.EncodeBlock(blk) + + txHashes := make([]ktypes.Hash, blk.Header.NumTxns) + for i, tx := range blk.Txns { + txHashes[i] = tx.Hash() + } + txn := bki.db.NewTransaction(true) defer txn.Discard() @@ -299,7 +306,7 @@ func (bki *BlockStore) Store(blk *ktypes.Block, appHash types.Hash) error { // Store the block contents with the nsBlock prefix key = slices.Concat(nsBlock, blkHash[:]) - err = txn.Set(key, ktypes.EncodeBlock(blk)) + err = txn.Set(key, rawBlk) if err != nil { return err } @@ -314,8 +321,7 @@ func (bki *BlockStore) Store(blk *ktypes.Block, appHash types.Hash) error { // this is possibly a suboptimal design. // Store the txn index - for idx, tx := range blk.Txns { - txHash := types.HashBytes(tx) + for idx, txHash := range txHashes { key = slices.Concat(nsTxn, txHash[:]) // "t:txHash" => height + blkHash + blkIdx val := makeTxVal(height, blkHash, uint32(idx)) err := txn.Set(key, val) diff --git a/node/store/store_test.go b/node/store/store_test.go index d3b2f6159..16831c2ae 100644 --- a/node/store/store_test.go +++ b/node/store/store_test.go @@ -17,6 +17,7 @@ import ( "github.com/kwilteam/kwil-db/core/crypto/auth" ktypes "github.com/kwilteam/kwil-db/core/types" "github.com/kwilteam/kwil-db/node/types" + "github.com/stretchr/testify/require" ) func getFileSizes(dirPath string) ([][2]string, error) { @@ -68,7 +69,7 @@ func fakeAppHash(height int64) types.Hash { return types.HashBytes(binary.LittleEndian.AppendUint64(nil, uint64(height))) } -func createTestBlock(height int64, numTxns int) (*ktypes.Block, types.Hash, []*ktypes.Transaction) { +func createTestBlock(t *testing.T, height int64, numTxns int) (*ktypes.Block, types.Hash, [][]byte) { txs := make([]*ktypes.Transaction, numTxns) txns := make([][]byte, numTxns) for i := range numTxns { @@ -81,14 +82,15 @@ func createTestBlock(height int64, numTxns int) (*ktypes.Block, types.Hash, []*k txs[i] = tx txns[i] = rawTx } - return ktypes.NewBlock(height, types.Hash{2, 3, 4}, types.Hash{6, 7, 8}, types.Hash{5, 5, 5}, - time.Unix(1729723553+height, 0), txns), fakeAppHash(height), txs + blk := ktypes.NewBlock(height, types.Hash{2, 3, 4}, types.Hash{6, 7, 8}, types.Hash{5, 5, 5}, + time.Unix(1729723553+height, 0), txs) + return blk, fakeAppHash(height), txns } func TestBlockStore_StoreAndGet(t *testing.T) { bs, _ := setupTestBlockStore(t) - block, appHash, _ := createTestBlock(1, 2) + block, appHash, _ := createTestBlock(t, 1, 2) err := bs.Store(block, appHash) if err != nil { t.Fatal(err) @@ -100,7 +102,8 @@ func TestBlockStore_StoreAndGet(t *testing.T) { if err != nil { t.Fatal(err) } - height, data := blk.Header.Height, ktypes.EncodeBlock(blk) + height := blk.Header.Height + data := ktypes.EncodeBlock(blk) if height != block.Header.Height { t.Errorf("Expected height %d, got %d", block.Header.Height, height) @@ -129,7 +132,7 @@ func TestBlockStore_StoreAndGet(t *testing.T) { func TestBlockStore_GetByHeight(t *testing.T) { bs, _ := setupTestBlockStore(t) - block, appHash, _ := createTestBlock(1, 2) + block, appHash, _ := createTestBlock(t, 1, 2) bs.Store(block, appHash) gotHash, blk, gotAppHash, err := bs.GetByHeight(1) @@ -156,7 +159,7 @@ func TestBlockStore_GetByHeight(t *testing.T) { func TestBlockStore_Have(t *testing.T) { bs, _ := setupTestBlockStore(t) - block, appHash, _ := createTestBlock(1, 2) + block, appHash, _ := createTestBlock(t, 1, 2) hash := block.Hash() if bs.Have(hash) { @@ -175,15 +178,17 @@ func TestBlockStore_Have(t *testing.T) { func TestBlockStore_GetTx(t *testing.T) { bs, _ := setupTestBlockStore(t) - block, appHash, _ := createTestBlock(1, 3) + block, appHash, _ := createTestBlock(t, 1, 3) bs.Store(block, appHash) - for i := range block.Txns { - txHash := types.HashBytes(block.Txns[i]) + for _, tx := range block.Txns { + txHash := tx.Hash() tx, height, _, _, err := bs.GetTx(txHash) if err != nil { t.Fatal(err) } + rawTx, err := tx.MarshalBinary() + require.NoError(t, err) if height != block.Header.Height { t.Errorf("Expected tx height %d, got %d", block.Header.Height, height) @@ -194,7 +199,7 @@ func TestBlockStore_GetTx(t *testing.T) { t.Fatal(err) } - if !bytes.Equal(txData, block.Txns[i]) { + if !bytes.Equal(txData, rawTx) { t.Error("Retrieved transaction data doesn't match original") } } @@ -203,8 +208,8 @@ func TestBlockStore_GetTx(t *testing.T) { func TestBlockStore_HaveTx(t *testing.T) { bs, dir := setupTestBlockStore(t) - block, appHash, _ := createTestBlock(1, 6) - txHash := types.HashBytes(block.Txns[0]) + block, appHash, _ := createTestBlock(t, 1, 6) + txHash := block.Txns[0].Hash() if bs.HaveTx(txHash) { t.Error("Transaction should not exist before storing block") @@ -235,7 +240,7 @@ func TestBlockStore_HaveTx(t *testing.T) { func TestBlockStore_StoreWithNoTransactions(t *testing.T) { bs, _ := setupTestBlockStore(t) block := ktypes.NewBlock(1, types.Hash{2, 3, 4}, types.Hash{6, 7, 8}, types.Hash{}, - time.Unix(1729723553, 0), [][]byte{}) + time.Unix(1729723553, 0), []*ktypes.Transaction{}) appHash := fakeAppHash(1) err := bs.Store(block, appHash) @@ -255,29 +260,6 @@ func TestBlockStore_StoreWithNoTransactions(t *testing.T) { } } -func TestBlockStore_StoreWithEmptyTransactions(t *testing.T) { - bs, _ := setupTestBlockStore(t) - block := ktypes.NewBlock(1, types.Hash{2, 3, 4}, types.Hash{6, 7, 8}, types.Hash{}, - time.Unix(1729723553, 0), [][]byte{{}, {}}) - appHash := fakeAppHash(1) - - err := bs.Store(block, appHash) - if err != nil { - t.Fatal(err) - } - - blk, gotAppHash, err := bs.Get(block.Hash()) - if err != nil { - t.Fatal(err) - } - if len(blk.Txns) != 2 { - t.Error("Expected two transactions") - } - if appHash != gotAppHash { - t.Errorf("Expected app hash %x, got %x", appHash, gotAppHash) - } -} - func TestBlockStore_StoreConcurrent(t *testing.T) { bs, _ := setupTestBlockStore(t) done := make(chan bool) @@ -286,7 +268,7 @@ func TestBlockStore_StoreConcurrent(t *testing.T) { for i := range 3 { go func(start int) { for j := range blockCount { - block, appHash, _ := createTestBlock(int64(start*blockCount+j), 2) + block, appHash, _ := createTestBlock(t, int64(start*blockCount+j), 2) err := bs.Store(block, appHash) if err != nil { t.Error(err) @@ -319,7 +301,7 @@ func TestBlockStore_StoreConcurrent(t *testing.T) { func TestBlockStore_StoreDuplicateBlock(t *testing.T) { bs, _ := setupTestBlockStore(t) - block, appHash, _ := createTestBlock(1, 2) + block, appHash, _ := createTestBlock(t, 1, 2) err := bs.Store(block, appHash) if err != nil { @@ -365,7 +347,7 @@ func TestBlockStore_StoreWithLargeTransactions(t *testing.T) { } block := ktypes.NewBlock(1, types.Hash{2, 3, 4}, types.Hash{6, 7, 8}, types.Hash{}, - time.Unix(1729723553, 0), [][]byte{largeTxRaw, otherTxRaw}) + time.Unix(1729723553, 0), []*ktypes.Transaction{largeTx, otherTx}) appHash := fakeAppHash(1) err = bs.Store(block, appHash) @@ -409,12 +391,16 @@ func TestBlockStore_StoreWithLargeTransactions(t *testing.T) { // } } -func newTx(nonce uint64, sender string) *ktypes.Transaction { +func newTx(nonce uint64, sender string, optPayload ...string) *ktypes.Transaction { + payload := `random payload` + if len(optPayload) > 0 { + payload = optPayload[0] + } return &ktypes.Transaction{ Signature: &auth.Signature{}, Body: &ktypes.TransactionBody{ Description: "test", - Payload: []byte(`random payload`), + Payload: []byte(payload), Fee: big.NewInt(0), Nonce: nonce, }, @@ -454,19 +440,21 @@ func TestLargeBlockStore(t *testing.T) { // Create blocks with random transactions for height := int64(1); height <= numBlocks; height++ { // Generate random transactions - txs := make([][]byte, txsPerBlock) - for i := range txs { + rawTxns := make([][]byte, txsPerBlock) + txns := make([]*ktypes.Transaction, txsPerBlock) + for i := range rawTxns { tx := newTx(uint64(i), "sendername") tx.Body.Payload = make([]byte, txSize) copy(tx.Body.Payload, txPayload) - txs[i], err = tx.MarshalBinary() + txns[i] = tx + rawTxns[i], err = tx.MarshalBinary() if err != nil { t.Fatal(err) } } // Create and store block - block := ktypes.NewBlock(height, prevHash, prevAppHash, types.Hash{}, time.Now(), txs) + block := ktypes.NewBlock(height, prevHash, prevAppHash, types.Hash{}, time.Now(), txns) appHash := types.HashBytes([]byte(fmt.Sprintf("app-%d", height))) err = bs.Store(block, appHash) if err != nil { @@ -493,8 +481,8 @@ func TestLargeBlockStore(t *testing.T) { // Verify random transaction retrieval - txIdx := rng.IntN(len(txs)) - txHash := types.HashBytes(txs[txIdx]) + txIdx := rng.IntN(len(txns)) + txHash := types.HashBytes(rawTxns[txIdx]) tx, gotHeight, _, _, err := bs.GetTx(txHash) if err != nil { t.Errorf("Failed to get tx at height %d, idx %d: %v", height, txIdx, err) @@ -506,7 +494,7 @@ func TestLargeBlockStore(t *testing.T) { if gotHeight != height { t.Errorf("Wrong tx height. Got %d, want %d", gotHeight, height) } - if !bytes.Equal(txData, txs[txIdx]) { + if !bytes.Equal(txData, rawTxns[txIdx]) { t.Error("Retrieved tx data mismatch") } } @@ -542,7 +530,7 @@ func getDirSize(path string) int64 { func TestBlockStore_StoreAndGetResults(t *testing.T) { bs, _ := setupTestBlockStore(t) - block, appHash, _ := createTestBlock(1, 3) + block, appHash, _ := createTestBlock(t, 1, 3) err := bs.Store(block, appHash) if err != nil { t.Fatal(err) @@ -582,7 +570,7 @@ func TestBlockStore_StoreResultsEmptyBlock(t *testing.T) { bs, _ := setupTestBlockStore(t) block := ktypes.NewBlock(1, types.Hash{2, 3, 4}, types.Hash{6, 7, 8}, types.Hash{}, - time.Unix(1729723553, 0), [][]byte{}) + time.Unix(1729723553, 0), []*ktypes.Transaction{}) appHash := fakeAppHash(1) err := bs.Store(block, appHash) @@ -619,7 +607,7 @@ func TestBlockStore_ResultsNonExistentBlock(t *testing.T) { func TestBlockStore_StoreResultsLargeData(t *testing.T) { bs, _ := setupTestBlockStore(t) - block, appHash, _ := createTestBlock(1, 2) + block, appHash, _ := createTestBlock(t, 1, 2) err := bs.Store(block, appHash) if err != nil { t.Fatal(err) @@ -655,7 +643,7 @@ func TestBlockStore_StoreResultsLargeData(t *testing.T) { func TestBlockStore_StoreResultsMismatchedCount(t *testing.T) { bs, _ := setupTestBlockStore(t) - block, appHash, _ := createTestBlock(1, 2) + block, appHash, _ := createTestBlock(t, 1, 2) err := bs.Store(block, appHash) if err != nil { t.Fatal(err) @@ -682,7 +670,7 @@ func TestBlockStore_StoreResultsMismatchedCount(t *testing.T) { func TestBlockStore_Result(t *testing.T) { bs, _ := setupTestBlockStore(t) - block, appHash, _ := createTestBlock(1, 3) + block, appHash, _ := createTestBlock(t, 1, 3) err := bs.Store(block, appHash) if err != nil { t.Fatal(err)