package types import ( "bytes" "encoding/hex" "fmt" "io" "github.com/vapor/encoding/blockchain" "github.com/vapor/errors" "github.com/vapor/protocol/bc" ) const serRequired = 0x7 // Bit mask accepted serialization flag. // Tx holds a transaction along with its hash. type Tx struct { TxData *bc.Tx `json:"-"` } // NewTx returns a new Tx containing data and its hash. If you have already // computed the hash, use struct literal notation to make a Tx object directly. func NewTx(data TxData) *Tx { return &Tx{ TxData: data, Tx: MapTx(&data), } } // OutputID return the hash of the output position func (tx *Tx) OutputID(outputIndex int) *bc.Hash { return tx.ResultIds[outputIndex] } // UnmarshalText fulfills the encoding.TextUnmarshaler interface. func (tx *Tx) UnmarshalText(p []byte) error { if err := tx.TxData.UnmarshalText(p); err != nil { return err } tx.Tx = MapTx(&tx.TxData) return nil } // SetInputArguments sets the Arguments field in input n. func (tx *Tx) SetInputArguments(n uint32, args [][]byte) { tx.Inputs[n].SetArguments(args) id := tx.Tx.InputIDs[n] e := tx.Entries[id] switch e := e.(type) { case *bc.Spend: e.WitnessArguments = args case *bc.CrossChainInput: e.WitnessArguments = args } } // TxData encodes a transaction in the blockchain. type TxData struct { Version uint64 SerializedSize uint64 TimeRange uint64 Inputs []*TxInput Outputs []*TxOutput } // MarshalText fulfills the json.Marshaler interface. func (tx *TxData) MarshalText() ([]byte, error) { var buf bytes.Buffer if _, err := tx.WriteTo(&buf); err != nil { return nil, err } b := make([]byte, hex.EncodedLen(buf.Len())) hex.Encode(b, buf.Bytes()) return b, nil } // UnmarshalText fulfills the encoding.TextUnmarshaler interface. func (tx *TxData) UnmarshalText(p []byte) error { b := make([]byte, hex.DecodedLen(len(p))) if _, err := hex.Decode(b, p); err != nil { return err } r := blockchain.NewReader(b) if err := tx.readFrom(r); err != nil { return err } if trailing := r.Len(); trailing > 0 { return fmt.Errorf("trailing garbage (%d bytes)", trailing) } return nil } func (tx *TxData) readFrom(r *blockchain.Reader) (err error) { startSerializedSize := r.Len() var serflags [1]byte if _, err = io.ReadFull(r, serflags[:]); err != nil { return errors.Wrap(err, "reading serialization flags") } if serflags[0] != serRequired { return fmt.Errorf("unsupported serflags %#x", serflags[0]) } if tx.Version, err = blockchain.ReadVarint63(r); err != nil { return errors.Wrap(err, "reading transaction version") } if tx.TimeRange, err = blockchain.ReadVarint63(r); err != nil { return err } n, err := blockchain.ReadVarint31(r) if err != nil { return errors.Wrap(err, "reading number of transaction inputs") } for ; n > 0; n-- { ti := new(TxInput) if err = ti.readFrom(r); err != nil { return errors.Wrapf(err, "reading input %d", len(tx.Inputs)) } tx.Inputs = append(tx.Inputs, ti) } n, err = blockchain.ReadVarint31(r) if err != nil { return errors.Wrap(err, "reading number of transaction outputs") } for ; n > 0; n-- { to := new(TxOutput) if err = to.readFrom(r); err != nil { return errors.Wrapf(err, "reading output %d", len(tx.Outputs)) } tx.Outputs = append(tx.Outputs, to) } tx.SerializedSize = uint64(startSerializedSize - r.Len()) return nil } // WriteTo writes tx to w. func (tx *TxData) WriteTo(w io.Writer) (int64, error) { ew := errors.NewWriter(w) if err := tx.writeTo(ew, serRequired); err != nil { return 0, err } return ew.Written(), ew.Err() } func (tx *TxData) writeTo(w io.Writer, serflags byte) error { if _, err := w.Write([]byte{serflags}); err != nil { return errors.Wrap(err, "writing serialization flags") } if _, err := blockchain.WriteVarint63(w, tx.Version); err != nil { return errors.Wrap(err, "writing transaction version") } if _, err := blockchain.WriteVarint63(w, tx.TimeRange); err != nil { return errors.Wrap(err, "writing transaction maxtime") } if _, err := blockchain.WriteVarint31(w, uint64(len(tx.Inputs))); err != nil { return errors.Wrap(err, "writing tx input count") } for i, ti := range tx.Inputs { if err := ti.writeTo(w); err != nil { return errors.Wrapf(err, "writing tx input %d", i) } } if _, err := blockchain.WriteVarint31(w, uint64(len(tx.Outputs))); err != nil { return errors.Wrap(err, "writing tx output count") } for i, to := range tx.Outputs { if err := to.writeTo(w); err != nil { return errors.Wrapf(err, "writing tx output %d", i) } } return nil }