Files
tendermint/types/tx.go
2022-03-14 14:52:19 -04:00

316 lines
8.7 KiB
Go

package types
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"sort"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/merkle"
"github.com/tendermint/tendermint/crypto/tmhash"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
// Tx is an arbitrary byte array.
// NOTE: Tx has no types at this level, so when wire encoded it's just length-prefixed.
// Might we want types here ?
type Tx []byte
// Key produces a fixed-length key for use in indexing.
func (tx Tx) Key() TxKey { return sha256.Sum256(tx) }
// Hash computes the TMHASH hash of the wire encoded transaction.
func (tx Tx) Hash() []byte { return tmhash.Sum(tx) }
// String returns the hex-encoded transaction as a string.
func (tx Tx) String() string { return fmt.Sprintf("Tx{%X}", []byte(tx)) }
// Txs is a slice of Tx.
type Txs []Tx
// Hash returns the Merkle root hash of the transaction hashes.
// i.e. the leaves of the tree are the hashes of the txs.
func (txs Txs) Hash() []byte {
// These allocations will be removed once Txs is switched to [][]byte,
// ref #2603. This is because golang does not allow type casting slices without unsafe
txBzs := make([][]byte, len(txs))
for i := 0; i < len(txs); i++ {
txBzs[i] = txs[i].Hash()
}
return merkle.HashFromByteSlices(txBzs)
}
// Index returns the index of this transaction in the list, or -1 if not found
func (txs Txs) Index(tx Tx) int {
for i := range txs {
if bytes.Equal(txs[i], tx) {
return i
}
}
return -1
}
// IndexByHash returns the index of this transaction hash in the list, or -1 if not found
func (txs Txs) IndexByHash(hash []byte) int {
for i := range txs {
if bytes.Equal(txs[i].Hash(), hash) {
return i
}
}
return -1
}
func (txs Txs) Len() int { return len(txs) }
func (txs Txs) Swap(i, j int) { txs[i], txs[j] = txs[j], txs[i] }
func (txs Txs) Less(i, j int) bool {
return bytes.Compare(txs[i], txs[j]) == -1
}
// ToSliceOfBytes converts a Txs to slice of byte slices.
//
// NOTE: This method should become obsolete once Txs is switched to [][]byte.
// ref: #2603
// TODO This function is to disappear when TxRecord is introduced
func (txs Txs) ToSliceOfBytes() [][]byte {
txBzs := make([][]byte, len(txs))
for i := 0; i < len(txs); i++ {
txBzs[i] = txs[i]
}
return txBzs
}
// ToTxs converts a raw slice of byte slices into a Txs type.
// TODO This function is to disappear when TxRecord is introduced
func ToTxs(txs [][]byte) Txs {
txBzs := make(Txs, len(txs))
for i := 0; i < len(txs); i++ {
txBzs[i] = txs[i]
}
return txBzs
}
// TxRecordsToTxs converts from the abci Tx type to the the Txs type.
func TxRecordsToTxs(trs []*abci.TxRecord) Txs {
txs := make([]Tx, len(trs))
for i, tr := range trs {
txs[i] = Tx(tr.Tx)
}
return txs
}
// TxRecordSet contains indexes into an underlying set of transactions.
// These indexes are useful for validating and working with a list of TxRecords
// from the PrepareProposal response.
type TxRecordSet struct {
txs Txs
added Txs
unmodified Txs
included Txs
removed Txs
unknown Txs
}
func NewTxRecordSet(trs []*abci.TxRecord) TxRecordSet {
txrSet := TxRecordSet{}
txrSet.txs = make([]Tx, len(trs))
for i, tr := range trs {
txrSet.txs[i] = Tx(tr.Tx)
switch tr.GetAction() {
case abci.TxRecord_UNKNOWN:
txrSet.unknown = append(txrSet.unknown, txrSet.txs[i])
case abci.TxRecord_UNMODIFIED:
txrSet.unmodified = append(txrSet.unmodified, txrSet.txs[i])
txrSet.included = append(txrSet.included, txrSet.txs[i])
case abci.TxRecord_ADDED:
txrSet.added = append(txrSet.added, txrSet.txs[i])
txrSet.included = append(txrSet.included, txrSet.txs[i])
case abci.TxRecord_REMOVED:
txrSet.removed = append(txrSet.removed, txrSet.txs[i])
}
}
return txrSet
}
// GetAddedTxs returns the transactions marked for inclusion in a block.
func (t TxRecordSet) GetIncludedTxs() []Tx {
return t.included
}
// GetAddedTxs returns the transactions added by the application.
func (t TxRecordSet) GetAddedTxs() []Tx {
return t.added
}
// GetRemovedTxs returns the transactions marked for removal by the application.
func (t TxRecordSet) GetRemovedTxs() []Tx {
return t.removed
}
// Validate checks that the record set was correctly constructed from the original
// list of transactions.
func (t TxRecordSet) Validate(maxSizeBytes int64, otxs Txs) error {
if len(t.unknown) > 0 {
return fmt.Errorf("transaction incorrectly marked as unknown, transaction hash: %x", t.unknown[0].Hash())
}
var size int64
cp := make([]Tx, len(t.txs))
copy(cp, t.txs)
sort.Sort(Txs(cp))
for i := 0; i < len(cp); i++ {
size += int64(len(cp[i]))
if size > maxSizeBytes {
return fmt.Errorf("transaction data size %d exceeds maximum %d", size, maxSizeBytes)
}
if i < len(cp)-1 && bytes.Equal(cp[i], cp[i+1]) {
return fmt.Errorf("TxRecords contains duplicate transaction, transaction hash: %x", cp[i].Hash())
}
}
addedCopy := make([]Tx, len(t.added))
copy(addedCopy, t.added)
removedCopy := make([]Tx, len(t.removed))
copy(removedCopy, t.removed)
unmodifiedCopy := make([]Tx, len(t.unmodified))
copy(unmodifiedCopy, t.unmodified)
sort.Sort(otxs)
sort.Sort(Txs(addedCopy))
sort.Sort(Txs(removedCopy))
sort.Sort(Txs(unmodifiedCopy))
unmodifiedIdx, addedIdx, removedIdx := 0, 0, 0
for i := 0; i < len(otxs); i++ {
if addedIdx == len(addedCopy) &&
removedIdx == len(removedCopy) &&
unmodifiedIdx == len(unmodifiedCopy) {
break
}
LOOP:
for addedIdx < len(addedCopy) {
switch bytes.Compare(addedCopy[addedIdx], otxs[i]) {
case 0:
return fmt.Errorf("existing transaction incorrectly marked as added, transaction hash: %x", otxs[i].Hash())
case -1:
addedIdx++
case 1:
break LOOP
}
}
if removedIdx < len(removedCopy) {
switch bytes.Compare(removedCopy[removedIdx], otxs[i]) {
case 0:
removedIdx++
case -1:
return fmt.Errorf("new transaction incorrectly marked as removed, transaction hash: %x", removedCopy[i].Hash())
}
}
if unmodifiedIdx < len(unmodifiedCopy) {
switch bytes.Compare(unmodifiedCopy[unmodifiedIdx], otxs[i]) {
case 0:
unmodifiedIdx++
case -1:
return fmt.Errorf("new transaction incorrectly marked as unmodified, transaction hash: %x", removedCopy[i].Hash())
}
}
}
if unmodifiedIdx != len(unmodifiedCopy) {
return fmt.Errorf("new transaction incorrectly marked as unmodified, transaction hash: %x", unmodifiedCopy[unmodifiedIdx].Hash())
}
if removedIdx != len(removedCopy) {
return fmt.Errorf("new transaction incorrectly marked as removed, transaction hash: %x", removedCopy[removedIdx].Hash())
}
return nil
}
func (t TxRecordSet) GetTxs() []Tx {
return t.txs
}
// TxsToTxRecords converts from a list of Txs to a list of TxRecords. All of the
// resulting TxRecords are returned with the status TxRecord_UNMODIFIED.
func TxsToTxRecords(txs []Tx) []*abci.TxRecord {
trs := make([]*abci.TxRecord, len(txs))
for i, tx := range txs {
trs[i] = &abci.TxRecord{
Action: abci.TxRecord_UNMODIFIED,
Tx: tx,
}
}
return trs
}
// TxProof represents a Merkle proof of the presence of a transaction in the Merkle tree.
type TxProof struct {
RootHash tmbytes.HexBytes `json:"root_hash"`
Data Tx `json:"data"`
Proof merkle.Proof `json:"proof"`
}
// Leaf returns the hash(tx), which is the leaf in the merkle tree which this proof refers to.
func (tp TxProof) Leaf() []byte {
return tp.Data.Hash()
}
// Validate verifies the proof. It returns nil if the RootHash matches the dataHash argument,
// and if the proof is internally consistent. Otherwise, it returns a sensible error.
func (tp TxProof) Validate(dataHash []byte) error {
if !bytes.Equal(dataHash, tp.RootHash) {
return errors.New("proof matches different data hash")
}
if tp.Proof.Index < 0 {
return errors.New("proof index cannot be negative")
}
if tp.Proof.Total <= 0 {
return errors.New("proof total must be positive")
}
valid := tp.Proof.Verify(tp.RootHash, tp.Leaf())
if valid != nil {
return errors.New("proof is not internally consistent")
}
return nil
}
func (tp TxProof) ToProto() tmproto.TxProof {
pbProof := tp.Proof.ToProto()
pbtp := tmproto.TxProof{
RootHash: tp.RootHash,
Data: tp.Data,
Proof: pbProof,
}
return pbtp
}
func TxProofFromProto(pb tmproto.TxProof) (TxProof, error) {
pbProof, err := merkle.ProofFromProto(pb.Proof)
if err != nil {
return TxProof{}, err
}
pbtp := TxProof{
RootHash: pb.RootHash,
Data: pb.Data,
Proof: *pbProof,
}
return pbtp, nil
}
// ComputeProtoSizeForTxs wraps the transactions in tmproto.Data{} and calculates the size.
// https://developers.google.com/protocol-buffers/docs/encoding
func ComputeProtoSizeForTxs(txs []Tx) int64 {
data := Data{Txs: txs}
pdData := data.ToProto()
return int64(pdData.Size())
}