neo-go/pkg/core/transaction/transaction.go
Evgenii Stratonikov 72d72296c3 core: implement (*Transaction).GetSignedPart()
Marshalling it and taking all but last byte violates incapsulation
and is just wrong in case transaction already contains any witnesses.
2020-03-02 18:00:00 +03:00

218 lines
5.2 KiB
Go

package transaction
import (
"errors"
"fmt"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
)
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 (double SHA256).
hash util.Uint256
// Hash of the transaction used to verify it (single SHA256).
verificationHash 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 returns the hash of the transaction.
func (t *Transaction) Hash() util.Uint256 {
if t.hash.Equals(util.Uint256{}) {
if t.createHash() != nil {
panic("failed to compute hash!")
}
}
return t.hash
}
// VerificationHash returns the hash of the transaction used to verify it.
func (t *Transaction) VerificationHash() util.Uint256 {
if t.verificationHash.Equals(util.Uint256{}) {
if t.createHash() != nil {
panic("failed to compute hash!")
}
}
return t.verificationHash
}
// 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 Serializable interface.
func (t *Transaction) DecodeBinary(br *io.BinReader) {
t.Type = TXType(br.ReadB())
t.Version = uint8(br.ReadB())
t.decodeData(br)
br.ReadArray(&t.Attributes)
br.ReadArray(&t.Inputs)
br.ReadArray(&t.Outputs)
br.ReadArray(&t.Scripts)
// Create the hash of the transaction at decode, so we dont need
// to do it anymore.
if br.Err == nil {
br.Err = t.createHash()
}
}
func (t *Transaction) decodeData(r *io.BinReader) {
switch t.Type {
case InvocationType:
t.Data = &InvocationTX{Version: t.Version}
t.Data.(*InvocationTX).DecodeBinary(r)
case MinerType:
t.Data = &MinerTX{}
t.Data.(*MinerTX).DecodeBinary(r)
case ClaimType:
t.Data = &ClaimTX{}
t.Data.(*ClaimTX).DecodeBinary(r)
case ContractType:
t.Data = &ContractTX{}
t.Data.(*ContractTX).DecodeBinary(r)
case RegisterType:
t.Data = &RegisterTX{}
t.Data.(*RegisterTX).DecodeBinary(r)
case IssueType:
t.Data = &IssueTX{}
t.Data.(*IssueTX).DecodeBinary(r)
case EnrollmentType:
t.Data = &EnrollmentTX{}
t.Data.(*EnrollmentTX).DecodeBinary(r)
case PublishType:
t.Data = &PublishTX{Version: t.Version}
t.Data.(*PublishTX).DecodeBinary(r)
case StateType:
t.Data = &StateTX{}
t.Data.(*StateTX).DecodeBinary(r)
default:
r.Err = fmt.Errorf("invalid TX type %x", t.Type)
}
}
// EncodeBinary implements Serializable interface.
func (t *Transaction) EncodeBinary(bw *io.BinWriter) {
t.encodeHashableFields(bw)
bw.WriteArray(t.Scripts)
}
// encodeHashableFields encodes the fields that are not used for
// signing the transaction, which are all fields except the scripts.
func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
if t.Data == nil {
bw.Err = errors.New("transaction has no data")
return
}
bw.WriteB(byte(t.Type))
bw.WriteB(byte(t.Version))
// Underlying TXer.
t.Data.EncodeBinary(bw)
// Attributes
bw.WriteArray(t.Attributes)
// Inputs
bw.WriteArray(t.Inputs)
// Outputs
bw.WriteArray(t.Outputs)
}
// createHash creates the hash of the transaction.
func (t *Transaction) createHash() error {
buf := io.NewBufBinWriter()
t.encodeHashableFields(buf.BinWriter)
if buf.Err != nil {
return buf.Err
}
b := buf.Bytes()
t.verificationHash = hash.Sha256(b)
t.hash = hash.Sha256(t.verificationHash.BytesBE())
return nil
}
// GroupOutputByAssetID groups all TX outputs by their assetID.
func (t Transaction) GroupOutputByAssetID() map[util.Uint256][]*Output {
m := make(map[util.Uint256][]*Output)
for i := range t.Outputs {
hash := t.Outputs[i].AssetID
m[hash] = append(m[hash], &t.Outputs[i])
}
return m
}
// GetSignedPart returns a part of the transaction which must be signed.
func (t *Transaction) GetSignedPart() []byte {
buf := io.NewBufBinWriter()
t.encodeHashableFields(buf.BinWriter)
if buf.Err != nil {
return nil
}
return buf.Bytes()
}
// Bytes converts the transaction to []byte
func (t *Transaction) Bytes() []byte {
buf := io.NewBufBinWriter()
t.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return nil
}
return buf.Bytes()
}