neo-go/pkg/core/transaction/transaction.go
Roman Khimov ec7e17ffa6 pkg: make use of the new crypto/hash package
Simplifies a lot of code and removes some duplication. Unfortunately I had to
move test_util random functions in same commit to avoid cycle
dependencies. One of these random functions was also used in core/transaction
testing, to simplify things I've just dropped it there and used a static
string (which is nice to have for a test anyway).

There is still sha256 left in wallet (but it needs to pass Hash structure into
the signing function).
2019-08-26 13:32:19 +03:00

285 lines
7 KiB
Go

package transaction
import (
"bytes"
"encoding/binary"
"io"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/util"
log "github.com/sirupsen/logrus"
)
const (
// MaxTransactionSize is the upper limit size in bytes that a transaction can reach. It is
// set to be 102400.
MaxTransactionSize = 102400
)
// Transaction is a process recorded in the NEO blockchain.
type Transaction struct {
// The type of the transaction.
Type TXType `json:"type"`
// The trading version which is currently 0.
Version uint8 `json:"version"`
// Data specific to the type of the transaction.
// This is always a pointer to a <Type>Transaction.
Data TXer `json:"-"`
// Transaction attributes.
Attributes []*Attribute `json:"attributes"`
// The inputs of the transaction.
Inputs []*Input `json:"vin"`
// The outputs of the transaction.
Outputs []*Output `json:"vout"`
// The scripts that comes with this transaction.
// Scripts exist out of the verification script
// and invocation script.
Scripts []*Witness `json:"scripts"`
// hash of the transaction
hash util.Uint256
// Trimmed indicates this is a transaction from trimmed
// data.
Trimmed bool `json:"-"`
}
// NewTrimmedTX returns a trimmed transaction with only its hash
// and Trimmed to true.
func NewTrimmedTX(hash util.Uint256) *Transaction {
return &Transaction{
hash: hash,
Trimmed: true,
}
}
// Hash return the hash of the transaction.
func (t *Transaction) Hash() util.Uint256 {
if t.hash.Equals(util.Uint256{}) {
t.createHash()
}
return t.hash
}
// AddOutput adds the given output to the transaction outputs.
func (t *Transaction) AddOutput(out *Output) {
t.Outputs = append(t.Outputs, out)
}
// AddInput adds the given input to the transaction inputs.
func (t *Transaction) AddInput(in *Input) {
t.Inputs = append(t.Inputs, in)
}
// DecodeBinary implements the payload interface.
func (t *Transaction) DecodeBinary(r io.Reader) error {
if err := binary.Read(r, binary.LittleEndian, &t.Type); err != nil {
return err
}
if err := binary.Read(r, binary.LittleEndian, &t.Version); err != nil {
return err
}
if err := t.decodeData(r); err != nil {
return err
}
lenAttrs := util.ReadVarUint(r)
t.Attributes = make([]*Attribute, lenAttrs)
for i := 0; i < int(lenAttrs); i++ {
t.Attributes[i] = &Attribute{}
if err := t.Attributes[i].DecodeBinary(r); err != nil {
// @TODO: remove this when TX attribute decode bug is solved.
log.Warnf("failed to decode TX %s", t.hash)
return err
}
}
lenInputs := util.ReadVarUint(r)
t.Inputs = make([]*Input, lenInputs)
for i := 0; i < int(lenInputs); i++ {
t.Inputs[i] = &Input{}
if err := t.Inputs[i].DecodeBinary(r); err != nil {
return err
}
}
lenOutputs := util.ReadVarUint(r)
t.Outputs = make([]*Output, lenOutputs)
for i := 0; i < int(lenOutputs); i++ {
t.Outputs[i] = &Output{}
if err := t.Outputs[i].DecodeBinary(r); err != nil {
return err
}
}
lenScripts := util.ReadVarUint(r)
t.Scripts = make([]*Witness, lenScripts)
for i := 0; i < int(lenScripts); i++ {
t.Scripts[i] = &Witness{}
if err := t.Scripts[i].DecodeBinary(r); err != nil {
return err
}
}
// Create the hash of the transaction at decode, so we dont need
// to do it anymore.
return t.createHash()
}
func (t *Transaction) decodeData(r io.Reader) error {
switch t.Type {
case InvocationType:
t.Data = &InvocationTX{}
return t.Data.(*InvocationTX).DecodeBinary(r)
case MinerType:
t.Data = &MinerTX{}
return t.Data.(*MinerTX).DecodeBinary(r)
case ClaimType:
t.Data = &ClaimTX{}
return t.Data.(*ClaimTX).DecodeBinary(r)
case ContractType:
t.Data = &ContractTX{}
return t.Data.(*ContractTX).DecodeBinary(r)
case RegisterType:
t.Data = &RegisterTX{}
return t.Data.(*RegisterTX).DecodeBinary(r)
case IssueType:
t.Data = &IssueTX{}
return t.Data.(*IssueTX).DecodeBinary(r)
case EnrollmentType:
t.Data = &EnrollmentTX{}
return t.Data.(*EnrollmentTX).DecodeBinary(r)
case PublishType:
t.Data = &PublishTX{}
return t.Data.(*PublishTX).DecodeBinary(r)
case StateType:
t.Data = &StateTX{}
return t.Data.(*StateTX).DecodeBinary(r)
default:
log.Warnf("invalid TX type %s", t.Type)
}
return nil
}
// EncodeBinary implements the payload interface.
func (t *Transaction) EncodeBinary(w io.Writer) error {
if err := t.encodeHashableFields(w); err != nil {
return err
}
if err := util.WriteVarUint(w, uint64(len(t.Scripts))); err != nil {
return err
}
for _, s := range t.Scripts {
if err := s.EncodeBinary(w); err != nil {
return err
}
}
return nil
}
// encodeHashableFields will only encode the fields that are not used for
// signing the transaction, which are all fields except the scripts.
func (t *Transaction) encodeHashableFields(w io.Writer) error {
if err := binary.Write(w, binary.LittleEndian, t.Type); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, t.Version); err != nil {
return err
}
// Underlying TXer.
if t.Data != nil {
if err := t.Data.EncodeBinary(w); err != nil {
return err
}
}
// Attributes
lenAttrs := uint64(len(t.Attributes))
if err := util.WriteVarUint(w, lenAttrs); err != nil {
return err
}
for _, attr := range t.Attributes {
if err := attr.EncodeBinary(w); err != nil {
return err
}
}
// Inputs
if err := util.WriteVarUint(w, uint64(len(t.Inputs))); err != nil {
return err
}
for _, in := range t.Inputs {
if err := in.EncodeBinary(w); err != nil {
return err
}
}
// Outputs
if err := util.WriteVarUint(w, uint64(len(t.Outputs))); err != nil {
return err
}
for _, out := range t.Outputs {
if err := out.EncodeBinary(w); err != nil {
return err
}
}
return nil
}
// createHash creates the hash of the transaction.
func (t *Transaction) createHash() error {
buf := new(bytes.Buffer)
if err := t.encodeHashableFields(buf); err != nil {
return err
}
t.hash = hash.DoubleSha256(buf.Bytes())
return nil
}
// GroupTXInputsByPrevHash groups all TX inputs by their previous hash.
func (t *Transaction) GroupInputsByPrevHash() map[util.Uint256][]*Input {
m := make(map[util.Uint256][]*Input)
for _, in := range t.Inputs {
m[in.PrevHash] = append(m[in.PrevHash], in)
}
return m
}
// GroupOutputByAssetID groups all TX outputs by their assetID.
func (t Transaction) GroupOutputByAssetID() map[util.Uint256][]*Output {
m := make(map[util.Uint256][]*Output)
for _, out := range t.Outputs {
m[out.AssetID] = append(m[out.AssetID], out)
}
return m
}
// Size returns the size of the transaction in term of bytes
func (t *Transaction) Size() int {
attrSize := util.GetVarSize(t.Attributes)
inputSize := util.GetVarSize(t.Inputs)
outputSize := util.GetVarSize(t.Outputs)
witnesSize := util.GetVarSize(t.Scripts)
// uint8 + uint8 + attrSize + inputSize + outputSize + witnesSize
return 2 + attrSize + inputSize + outputSize + witnesSize
}
// Bytes convert the transaction to []byte
func (t *Transaction) Bytes() []byte {
buf := new(bytes.Buffer)
if err := t.EncodeBinary(buf); err != nil {
return nil
}
return buf.Bytes()
}