2018-03-04 13:56:49 +00:00
|
|
|
package transaction
|
|
|
|
|
|
|
|
import (
|
2020-03-23 14:31:28 +00:00
|
|
|
"encoding/json"
|
2020-01-29 13:38:58 +00:00
|
|
|
"errors"
|
2020-08-19 13:39:49 +00:00
|
|
|
"fmt"
|
2020-10-07 15:23:10 +00:00
|
|
|
"math"
|
2020-06-05 13:07:04 +00:00
|
|
|
"math/rand"
|
2019-11-27 10:12:04 +00:00
|
|
|
|
2020-06-18 09:00:51 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
2020-03-23 14:31:28 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2018-03-04 13:56:49 +00:00
|
|
|
)
|
|
|
|
|
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
|
|
|
const (
|
2020-10-07 15:23:10 +00:00
|
|
|
// MaxScriptLength is the limit for transaction's script length.
|
|
|
|
MaxScriptLength = math.MaxUint16
|
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
|
|
|
// MaxTransactionSize is the upper limit size in bytes that a transaction can reach. It is
|
|
|
|
// set to be 102400.
|
|
|
|
MaxTransactionSize = 102400
|
2020-11-05 19:08:02 +00:00
|
|
|
// MaxValidUntilBlockIncrement is the upper increment size of blockhain height in blocks
|
|
|
|
// exceeding that a transaction should fail validation. It is set to estimated daily number
|
|
|
|
// of blocks with 15s interval.
|
|
|
|
MaxValidUntilBlockIncrement = 5760
|
2020-07-29 16:57:38 +00:00
|
|
|
// MaxAttributes is maximum number of attributes including signers that can be contained
|
|
|
|
// within a transaction. It is set to be 16.
|
|
|
|
MaxAttributes = 16
|
2020-10-15 11:45:29 +00:00
|
|
|
// DummyVersion represents reserved transaction version for trimmed transactions.
|
|
|
|
DummyVersion = 255
|
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold'
* Added new files:
* Added new method: CompareTo
* Fixed empty Slice case
* Added new methods: LessThan, GreaterThan, Equal, CompareTo
* Added new method: InputIntersection
* Added MaxTransactionSize, GroupOutputByAssetID
* Added ned method: ScriptHash
* Added new method: IsDoubleSpend
* Refactor blockchainer, Added Feer interface, Verify and GetMemPool method
* 1) Added MemPool
2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool
* Added new methods: RelayTxn, RelayDirectly
* Fixed tests
* Implemented RPC server method sendrawtransaction
* Refactor getrawtransaction, sendrawtransaction in separate methods
* Moved 'secondsPerBlock' to config file
* Implemented Kim suggestions:
1) Fixed data race issues
2) refactor Verify method
3) Get rid of unused InputIntersection method due to refactoring Verify method
4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135
5) minor simplications of the code
* Fixed minor issues related to
1) space
2) getter methods do not need pointer on the receiver
3) error message
4) refactoring CompareTo method in uint256.go
* Fixed small issues
* Use sync.RWMutex instead of sync.Mutex
* Refined (R)Lock/(R)Unlock
* return error instead of bool in Verify methods
2019-03-20 12:30:05 +00:00
|
|
|
)
|
|
|
|
|
2018-03-04 13:56:49 +00:00
|
|
|
// Transaction is a process recorded in the NEO blockchain.
|
|
|
|
type Transaction struct {
|
|
|
|
// The trading version which is currently 0.
|
2020-03-23 14:31:28 +00:00
|
|
|
Version uint8
|
2018-03-04 13:56:49 +00:00
|
|
|
|
2020-04-10 10:41:49 +00:00
|
|
|
// Random number to avoid hash collision.
|
|
|
|
Nonce uint32
|
|
|
|
|
2020-05-08 17:54:24 +00:00
|
|
|
// Fee to be burned.
|
2020-06-23 14:15:35 +00:00
|
|
|
SystemFee int64
|
2020-05-08 17:54:24 +00:00
|
|
|
|
|
|
|
// Fee to be distributed to consensus nodes.
|
2020-06-23 14:15:35 +00:00
|
|
|
NetworkFee int64
|
2020-05-08 17:54:24 +00:00
|
|
|
|
2020-04-15 06:50:13 +00:00
|
|
|
// Maximum blockchain height exceeding which
|
|
|
|
// transaction should fail verification.
|
|
|
|
ValidUntilBlock uint32
|
|
|
|
|
2020-06-05 13:07:04 +00:00
|
|
|
// Code to run in NeoVM for this transaction.
|
|
|
|
Script []byte
|
2018-03-04 13:56:49 +00:00
|
|
|
|
|
|
|
// Transaction attributes.
|
2020-03-23 14:31:28 +00:00
|
|
|
Attributes []Attribute
|
2018-03-04 13:56:49 +00:00
|
|
|
|
2020-07-29 16:57:38 +00:00
|
|
|
// Transaction signers list (starts with Sender).
|
|
|
|
Signers []Signer
|
2020-04-30 13:00:33 +00:00
|
|
|
|
2018-03-04 13:56:49 +00:00
|
|
|
// The scripts that comes with this transaction.
|
|
|
|
// Scripts exist out of the verification script
|
|
|
|
// and invocation script.
|
2020-03-23 14:31:28 +00:00
|
|
|
Scripts []Witness
|
2018-03-14 09:36:59 +00:00
|
|
|
|
2020-06-18 09:00:51 +00:00
|
|
|
// Network magic number. This one actually is not a part of the
|
|
|
|
// wire-representation of Transaction, but it's absolutely necessary
|
|
|
|
// for correct signing/verification.
|
|
|
|
Network netmode.Magic
|
|
|
|
|
2020-09-10 16:28:16 +00:00
|
|
|
// size is transaction's serialized size.
|
|
|
|
size int
|
2020-07-03 14:37:01 +00:00
|
|
|
|
2019-09-23 17:13:44 +00:00
|
|
|
// Hash of the transaction (double SHA256).
|
2018-03-14 09:36:59 +00:00
|
|
|
hash util.Uint256
|
2018-03-17 11:53:21 +00:00
|
|
|
|
2019-09-23 17:13:44 +00:00
|
|
|
// Hash of the transaction used to verify it (single SHA256).
|
|
|
|
verificationHash util.Uint256
|
|
|
|
|
2018-03-17 11:53:21 +00:00
|
|
|
// Trimmed indicates this is a transaction from trimmed
|
|
|
|
// data.
|
2020-03-23 14:31:28 +00:00
|
|
|
Trimmed bool
|
2018-03-17 11:53:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
}
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
|
|
|
|
2020-06-05 13:07:04 +00:00
|
|
|
// New returns a new transaction to execute given script and pay given system
|
|
|
|
// fee.
|
2020-06-23 14:15:35 +00:00
|
|
|
func New(network netmode.Magic, script []byte, gas int64) *Transaction {
|
2020-06-05 13:07:04 +00:00
|
|
|
return &Transaction{
|
|
|
|
Version: 0,
|
|
|
|
Nonce: rand.Uint32(),
|
|
|
|
Script: script,
|
|
|
|
SystemFee: gas,
|
|
|
|
Attributes: []Attribute{},
|
2020-07-29 16:57:38 +00:00
|
|
|
Signers: []Signer{},
|
2020-06-05 13:07:04 +00:00
|
|
|
Scripts: []Witness{},
|
2020-06-18 09:00:51 +00:00
|
|
|
Network: network,
|
2020-06-05 13:07:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-22 14:56:03 +00:00
|
|
|
// Hash returns the hash of the transaction.
|
2018-03-14 09:36:59 +00:00
|
|
|
func (t *Transaction) Hash() util.Uint256 {
|
2018-03-25 10:45:54 +00:00
|
|
|
if t.hash.Equals(util.Uint256{}) {
|
2019-09-23 17:26:53 +00:00
|
|
|
if t.createHash() != nil {
|
|
|
|
panic("failed to compute hash!")
|
|
|
|
}
|
2018-03-25 10:45:54 +00:00
|
|
|
}
|
2018-03-14 09:36:59 +00:00
|
|
|
return t.hash
|
2018-03-04 13:56:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-29 15:44:45 +00:00
|
|
|
// GetSignedHash returns a hash of the transaction used to verify it.
|
|
|
|
func (t *Transaction) GetSignedHash() util.Uint256 {
|
2019-09-23 17:13:44 +00:00
|
|
|
if t.verificationHash.Equals(util.Uint256{}) {
|
2019-09-23 17:26:53 +00:00
|
|
|
if t.createHash() != nil {
|
|
|
|
panic("failed to compute hash!")
|
|
|
|
}
|
2019-09-23 17:13:44 +00:00
|
|
|
}
|
|
|
|
return t.verificationHash
|
|
|
|
}
|
|
|
|
|
2020-08-19 13:20:48 +00:00
|
|
|
// HasAttribute returns true iff t has an attribute of type typ.
|
|
|
|
func (t *Transaction) HasAttribute(typ AttrType) bool {
|
|
|
|
for i := range t.Attributes {
|
|
|
|
if t.Attributes[i].Type == typ {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-10-20 15:39:01 +00:00
|
|
|
// GetAttributes returns the list of transaction's attributes of the given type.
|
|
|
|
// Returns nil in case if attributes not found.
|
|
|
|
func (t *Transaction) GetAttributes(typ AttrType) []Attribute {
|
|
|
|
var result []Attribute
|
|
|
|
for _, attr := range t.Attributes {
|
|
|
|
if attr.Type == typ {
|
|
|
|
result = append(result, attr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2020-06-18 08:39:47 +00:00
|
|
|
// decodeHashableFields decodes the fields that are used for signing the
|
|
|
|
// transaction, which are all fields except the scripts.
|
|
|
|
func (t *Transaction) decodeHashableFields(br *io.BinReader) {
|
2019-12-12 17:17:50 +00:00
|
|
|
t.Version = uint8(br.ReadB())
|
2020-04-10 10:41:49 +00:00
|
|
|
t.Nonce = br.ReadU32LE()
|
2020-06-23 14:15:35 +00:00
|
|
|
t.SystemFee = int64(br.ReadU64LE())
|
|
|
|
t.NetworkFee = int64(br.ReadU64LE())
|
2020-04-15 06:50:13 +00:00
|
|
|
t.ValidUntilBlock = br.ReadU32LE()
|
2020-07-29 16:57:38 +00:00
|
|
|
br.ReadArray(&t.Signers, MaxAttributes)
|
|
|
|
br.ReadArray(&t.Attributes, MaxAttributes-len(t.Signers))
|
2020-10-07 15:23:10 +00:00
|
|
|
t.Script = br.ReadVarBytes(MaxScriptLength)
|
2020-07-30 13:34:27 +00:00
|
|
|
if br.Err == nil {
|
|
|
|
br.Err = t.isValid()
|
2020-06-05 13:07:04 +00:00
|
|
|
}
|
2020-06-18 08:39:47 +00:00
|
|
|
}
|
2020-06-05 13:07:04 +00:00
|
|
|
|
2020-06-18 08:39:47 +00:00
|
|
|
// DecodeBinary implements Serializable interface.
|
|
|
|
func (t *Transaction) DecodeBinary(br *io.BinReader) {
|
|
|
|
t.decodeHashableFields(br)
|
|
|
|
if br.Err != nil {
|
|
|
|
return
|
|
|
|
}
|
2019-11-14 07:50:03 +00:00
|
|
|
br.ReadArray(&t.Scripts)
|
2018-03-04 13:56:49 +00:00
|
|
|
|
2018-03-14 09:36:59 +00:00
|
|
|
// Create the hash of the transaction at decode, so we dont need
|
|
|
|
// to do it anymore.
|
2019-09-16 16:31:49 +00:00
|
|
|
if br.Err == nil {
|
|
|
|
br.Err = t.createHash()
|
2020-09-10 16:28:16 +00:00
|
|
|
_ = t.Size()
|
2019-09-16 16:31:49 +00:00
|
|
|
}
|
2018-03-04 13:56:49 +00:00
|
|
|
}
|
|
|
|
|
2019-09-16 16:31:49 +00:00
|
|
|
// EncodeBinary implements Serializable interface.
|
|
|
|
func (t *Transaction) EncodeBinary(bw *io.BinWriter) {
|
|
|
|
t.encodeHashableFields(bw)
|
2019-11-13 07:36:29 +00:00
|
|
|
bw.WriteArray(t.Scripts)
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
|
|
|
|
2019-10-22 14:56:03 +00:00
|
|
|
// encodeHashableFields encodes the fields that are not used for
|
2018-03-14 09:36:59 +00:00
|
|
|
// signing the transaction, which are all fields except the scripts.
|
2019-09-16 16:31:49 +00:00
|
|
|
func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
|
2020-06-05 13:07:04 +00:00
|
|
|
if len(t.Script) == 0 {
|
|
|
|
bw.Err = errors.New("transaction has no script")
|
2020-01-29 13:38:58 +00:00
|
|
|
return
|
|
|
|
}
|
2019-12-12 17:17:50 +00:00
|
|
|
bw.WriteB(byte(t.Version))
|
2020-04-10 10:41:49 +00:00
|
|
|
bw.WriteU32LE(t.Nonce)
|
2020-06-23 14:15:35 +00:00
|
|
|
bw.WriteU64LE(uint64(t.SystemFee))
|
|
|
|
bw.WriteU64LE(uint64(t.NetworkFee))
|
2020-04-15 06:50:13 +00:00
|
|
|
bw.WriteU32LE(t.ValidUntilBlock)
|
2020-07-29 16:57:38 +00:00
|
|
|
bw.WriteArray(t.Signers)
|
2019-11-13 07:36:29 +00:00
|
|
|
bw.WriteArray(t.Attributes)
|
2020-06-05 13:07:04 +00:00
|
|
|
bw.WriteVarBytes(t.Script)
|
2018-03-04 13:56:49 +00:00
|
|
|
}
|
|
|
|
|
2018-03-25 10:45:54 +00:00
|
|
|
// createHash creates the hash of the transaction.
|
|
|
|
func (t *Transaction) createHash() error {
|
2020-06-18 09:00:51 +00:00
|
|
|
b := t.GetSignedPart()
|
|
|
|
if b == nil {
|
|
|
|
return errors.New("failed to serialize hashable data")
|
2018-03-25 10:45:54 +00:00
|
|
|
}
|
2020-06-18 09:00:51 +00:00
|
|
|
t.updateHashes(b)
|
|
|
|
return nil
|
|
|
|
}
|
2018-03-25 10:45:54 +00:00
|
|
|
|
2020-06-18 09:00:51 +00:00
|
|
|
// updateHashes updates Transaction's hashes based on the given buffer which should
|
|
|
|
// be a signable data slice.
|
|
|
|
func (t *Transaction) updateHashes(b []byte) {
|
2019-09-23 17:13:44 +00:00
|
|
|
t.verificationHash = hash.Sha256(b)
|
2019-12-12 15:01:30 +00:00
|
|
|
t.hash = hash.Sha256(t.verificationHash.BytesBE())
|
2018-03-04 13:56:49 +00:00
|
|
|
}
|
2018-03-21 16:11:04 +00:00
|
|
|
|
2020-02-28 07:05:13 +00:00
|
|
|
// GetSignedPart returns a part of the transaction which must be signed.
|
|
|
|
func (t *Transaction) GetSignedPart() []byte {
|
|
|
|
buf := io.NewBufBinWriter()
|
2020-06-18 09:00:51 +00:00
|
|
|
buf.WriteU32LE(uint32(t.Network))
|
2020-02-28 07:05:13 +00:00
|
|
|
t.encodeHashableFields(buf.BinWriter)
|
|
|
|
if buf.Err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return buf.Bytes()
|
|
|
|
}
|
|
|
|
|
2020-06-18 08:39:47 +00:00
|
|
|
// DecodeSignedPart decodes a part of transaction from GetSignedPart data.
|
|
|
|
func (t *Transaction) DecodeSignedPart(buf []byte) error {
|
|
|
|
r := io.NewBinReaderFromBuf(buf)
|
2020-06-18 09:00:51 +00:00
|
|
|
t.Network = netmode.Magic(r.ReadU32LE())
|
2020-06-18 08:39:47 +00:00
|
|
|
t.decodeHashableFields(r)
|
|
|
|
if r.Err != nil {
|
|
|
|
return r.Err
|
|
|
|
}
|
|
|
|
// Ensure all the data was read.
|
|
|
|
_ = r.ReadB()
|
|
|
|
if r.Err == nil {
|
|
|
|
return errors.New("additional data after the signed part")
|
|
|
|
}
|
|
|
|
t.Scripts = make([]Witness, 0)
|
2020-06-18 09:00:51 +00:00
|
|
|
t.updateHashes(buf)
|
2020-06-18 08:39:47 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-22 14:56:03 +00:00
|
|
|
// Bytes converts the transaction to []byte
|
2019-02-20 17:39:32 +00:00
|
|
|
func (t *Transaction) Bytes() []byte {
|
2019-09-16 09:18:13 +00:00
|
|
|
buf := io.NewBufBinWriter()
|
2019-09-16 16:31:49 +00:00
|
|
|
t.EncodeBinary(buf.BinWriter)
|
|
|
|
if buf.Err != nil {
|
2019-02-20 17:39:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return buf.Bytes()
|
|
|
|
}
|
2020-03-23 14:31:28 +00:00
|
|
|
|
2020-04-13 09:02:18 +00:00
|
|
|
// NewTransactionFromBytes decodes byte array into *Transaction
|
2020-06-18 09:00:51 +00:00
|
|
|
func NewTransactionFromBytes(network netmode.Magic, b []byte) (*Transaction, error) {
|
|
|
|
tx := &Transaction{Network: network}
|
2020-04-13 09:02:18 +00:00
|
|
|
r := io.NewBinReaderFromBuf(b)
|
|
|
|
tx.DecodeBinary(r)
|
|
|
|
if r.Err != nil {
|
|
|
|
return nil, r.Err
|
|
|
|
}
|
2020-07-03 14:52:44 +00:00
|
|
|
_ = r.ReadB()
|
|
|
|
if r.Err == nil {
|
|
|
|
return nil, errors.New("additional data after the transaction")
|
|
|
|
}
|
2020-09-10 16:28:16 +00:00
|
|
|
tx.size = len(b)
|
2020-04-13 09:02:18 +00:00
|
|
|
return tx, nil
|
|
|
|
}
|
|
|
|
|
2020-05-08 17:54:24 +00:00
|
|
|
// FeePerByte returns NetworkFee of the transaction divided by
|
|
|
|
// its size
|
2020-06-23 14:15:35 +00:00
|
|
|
func (t *Transaction) FeePerByte() int64 {
|
2020-09-10 16:28:16 +00:00
|
|
|
return t.NetworkFee / int64(t.Size())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size returns size of the serialized transaction.
|
|
|
|
func (t *Transaction) Size() int {
|
|
|
|
if t.size == 0 {
|
|
|
|
t.size = io.GetVarSize(t)
|
2020-07-03 14:37:01 +00:00
|
|
|
}
|
2020-09-10 16:28:16 +00:00
|
|
|
return t.size
|
2020-05-08 17:54:24 +00:00
|
|
|
}
|
|
|
|
|
2020-07-29 16:57:38 +00:00
|
|
|
// Sender returns the sender of the transaction which is always on the first place
|
|
|
|
// in the transaction's signers list.
|
|
|
|
func (t *Transaction) Sender() util.Uint160 {
|
|
|
|
if len(t.Signers) == 0 {
|
|
|
|
panic("transaction does not have signers")
|
|
|
|
}
|
|
|
|
return t.Signers[0].Account
|
|
|
|
}
|
|
|
|
|
2020-03-23 14:31:28 +00:00
|
|
|
// transactionJSON is a wrapper for Transaction and
|
|
|
|
// used for correct marhalling of transaction.Data
|
|
|
|
type transactionJSON struct {
|
2021-02-09 08:16:18 +00:00
|
|
|
TxID util.Uint256 `json:"hash"`
|
|
|
|
Size int `json:"size"`
|
|
|
|
Version uint8 `json:"version"`
|
|
|
|
Nonce uint32 `json:"nonce"`
|
|
|
|
Sender string `json:"sender"`
|
|
|
|
SystemFee int64 `json:"sysfee,string"`
|
|
|
|
NetworkFee int64 `json:"netfee,string"`
|
|
|
|
ValidUntilBlock uint32 `json:"validuntilblock"`
|
|
|
|
Attributes []Attribute `json:"attributes"`
|
|
|
|
Signers []Signer `json:"signers"`
|
|
|
|
Script []byte `json:"script"`
|
|
|
|
Scripts []Witness `json:"witnesses"`
|
2020-03-23 14:31:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalJSON implements json.Marshaler interface.
|
|
|
|
func (t *Transaction) MarshalJSON() ([]byte, error) {
|
|
|
|
tx := transactionJSON{
|
2020-04-15 06:50:13 +00:00
|
|
|
TxID: t.Hash(),
|
2020-09-10 16:28:16 +00:00
|
|
|
Size: t.Size(),
|
2020-04-15 06:50:13 +00:00
|
|
|
Version: t.Version,
|
|
|
|
Nonce: t.Nonce,
|
2020-07-29 16:57:38 +00:00
|
|
|
Sender: address.Uint160ToString(t.Sender()),
|
2020-04-15 06:50:13 +00:00
|
|
|
ValidUntilBlock: t.ValidUntilBlock,
|
|
|
|
Attributes: t.Attributes,
|
2020-07-29 16:57:38 +00:00
|
|
|
Signers: t.Signers,
|
2020-06-05 12:50:26 +00:00
|
|
|
Script: t.Script,
|
2020-04-15 06:50:13 +00:00
|
|
|
Scripts: t.Scripts,
|
2021-02-09 08:16:18 +00:00
|
|
|
SystemFee: t.SystemFee,
|
|
|
|
NetworkFee: t.NetworkFee,
|
2020-03-23 14:31:28 +00:00
|
|
|
}
|
|
|
|
return json.Marshal(tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
|
|
|
func (t *Transaction) UnmarshalJSON(data []byte) error {
|
|
|
|
tx := new(transactionJSON)
|
|
|
|
if err := json.Unmarshal(data, tx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.Version = tx.Version
|
2020-04-10 10:41:49 +00:00
|
|
|
t.Nonce = tx.Nonce
|
2020-04-15 06:50:13 +00:00
|
|
|
t.ValidUntilBlock = tx.ValidUntilBlock
|
2020-03-23 14:31:28 +00:00
|
|
|
t.Attributes = tx.Attributes
|
2020-07-29 16:57:38 +00:00
|
|
|
t.Signers = tx.Signers
|
2020-03-23 14:31:28 +00:00
|
|
|
t.Scripts = tx.Scripts
|
2021-02-09 08:16:18 +00:00
|
|
|
t.SystemFee = tx.SystemFee
|
|
|
|
t.NetworkFee = tx.NetworkFee
|
2020-06-05 12:50:26 +00:00
|
|
|
t.Script = tx.Script
|
2020-03-23 14:31:28 +00:00
|
|
|
if t.Hash() != tx.TxID {
|
|
|
|
return errors.New("txid doesn't match transaction hash")
|
|
|
|
}
|
2020-09-10 16:28:16 +00:00
|
|
|
if t.Size() != tx.Size {
|
|
|
|
return errors.New("'size' doesn't match transaction size")
|
|
|
|
}
|
2020-03-23 14:31:28 +00:00
|
|
|
|
2020-07-30 13:34:27 +00:00
|
|
|
return t.isValid()
|
|
|
|
}
|
|
|
|
|
2020-08-19 13:39:49 +00:00
|
|
|
// Various errors for transaction validation.
|
|
|
|
var (
|
|
|
|
ErrInvalidVersion = errors.New("only version 0 is supported")
|
|
|
|
ErrNegativeSystemFee = errors.New("negative system fee")
|
|
|
|
ErrNegativeNetworkFee = errors.New("negative network fee")
|
|
|
|
ErrTooBigFees = errors.New("too big fees: int64 overflow")
|
|
|
|
ErrEmptySigners = errors.New("signers array should contain sender")
|
|
|
|
ErrNonUniqueSigners = errors.New("transaction signers should be unique")
|
|
|
|
ErrInvalidAttribute = errors.New("invalid attribute")
|
|
|
|
ErrEmptyScript = errors.New("no script")
|
|
|
|
)
|
|
|
|
|
2020-07-30 13:34:27 +00:00
|
|
|
// isValid checks whether decoded/unmarshalled transaction has all fields valid.
|
|
|
|
func (t *Transaction) isValid() error {
|
2020-10-15 11:45:29 +00:00
|
|
|
if t.Version > 0 && t.Version != DummyVersion {
|
2020-08-19 13:39:49 +00:00
|
|
|
return ErrInvalidVersion
|
2020-07-30 13:34:27 +00:00
|
|
|
}
|
|
|
|
if t.SystemFee < 0 {
|
2020-08-19 13:39:49 +00:00
|
|
|
return ErrNegativeSystemFee
|
2020-07-30 13:34:27 +00:00
|
|
|
}
|
|
|
|
if t.NetworkFee < 0 {
|
2020-08-19 13:39:49 +00:00
|
|
|
return ErrNegativeNetworkFee
|
2020-07-30 13:34:27 +00:00
|
|
|
}
|
|
|
|
if t.NetworkFee+t.SystemFee < t.SystemFee {
|
2020-08-19 13:39:49 +00:00
|
|
|
return ErrTooBigFees
|
2020-07-30 13:34:27 +00:00
|
|
|
}
|
|
|
|
if len(t.Signers) == 0 {
|
2020-08-19 13:39:49 +00:00
|
|
|
return ErrEmptySigners
|
2020-07-30 13:34:27 +00:00
|
|
|
}
|
|
|
|
for i := 0; i < len(t.Signers); i++ {
|
|
|
|
for j := i + 1; j < len(t.Signers); j++ {
|
|
|
|
if t.Signers[i].Account.Equals(t.Signers[j].Account) {
|
2020-08-19 13:39:49 +00:00
|
|
|
return ErrNonUniqueSigners
|
2020-07-30 13:34:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-16 11:50:31 +00:00
|
|
|
attrs := map[AttrType]bool{}
|
2020-08-19 13:20:48 +00:00
|
|
|
for i := range t.Attributes {
|
2020-09-16 11:50:31 +00:00
|
|
|
typ := t.Attributes[i].Type
|
|
|
|
if !typ.allowMultiple() {
|
|
|
|
if attrs[typ] {
|
|
|
|
return fmt.Errorf("%w: multiple '%s' attributes", ErrInvalidAttribute, typ.String())
|
2020-08-19 13:20:48 +00:00
|
|
|
}
|
2020-09-16 11:50:31 +00:00
|
|
|
attrs[typ] = true
|
2020-08-19 13:20:48 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-30 13:34:27 +00:00
|
|
|
if len(t.Script) == 0 {
|
2020-08-19 13:39:49 +00:00
|
|
|
return ErrEmptyScript
|
2020-07-30 13:34:27 +00:00
|
|
|
}
|
2020-03-23 14:31:28 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-10-23 12:12:36 +00:00
|
|
|
|
|
|
|
// HasSigner returns true in case if hash is present in the list of signers.
|
|
|
|
func (t *Transaction) HasSigner(hash util.Uint160) bool {
|
|
|
|
for _, h := range t.Signers {
|
|
|
|
if h.Account.Equals(hash) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|