forked from TrueCloudLab/neoneo-go
956fd08adb
Add a method that makes a deep copy of all fields and resets size/hash caches. Close #3288 Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
128 lines
3.2 KiB
Go
128 lines
3.2 KiB
Go
package transaction
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
)
|
|
|
|
// AttrValue represents a Transaction Attribute value.
|
|
type AttrValue interface {
|
|
io.Serializable
|
|
// toJSONMap is used for embedded json struct marshalling.
|
|
// Anonymous interface fields are not considered anonymous by
|
|
// json lib and marshaling Value together with type makes code
|
|
// harder to follow.
|
|
toJSONMap(map[string]any)
|
|
// Copy returns a deep copy of the attribute value.
|
|
Copy() AttrValue
|
|
}
|
|
|
|
// Attribute represents a Transaction attribute.
|
|
type Attribute struct {
|
|
Type AttrType
|
|
Value AttrValue
|
|
}
|
|
|
|
// attrJSON is used for JSON I/O of Attribute.
|
|
type attrJSON struct {
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
// DecodeBinary implements the Serializable interface.
|
|
func (attr *Attribute) DecodeBinary(br *io.BinReader) {
|
|
attr.Type = AttrType(br.ReadB())
|
|
|
|
switch t := attr.Type; t {
|
|
case HighPriority:
|
|
return
|
|
case OracleResponseT:
|
|
attr.Value = new(OracleResponse)
|
|
case NotValidBeforeT:
|
|
attr.Value = new(NotValidBefore)
|
|
case ConflictsT:
|
|
attr.Value = new(Conflicts)
|
|
case NotaryAssistedT:
|
|
attr.Value = new(NotaryAssisted)
|
|
default:
|
|
if t >= ReservedLowerBound && t <= ReservedUpperBound {
|
|
attr.Value = new(Reserved)
|
|
break
|
|
}
|
|
br.Err = fmt.Errorf("failed decoding TX attribute usage: 0x%2x", int(attr.Type))
|
|
return
|
|
}
|
|
attr.Value.DecodeBinary(br)
|
|
}
|
|
|
|
// EncodeBinary implements the Serializable interface.
|
|
func (attr *Attribute) EncodeBinary(bw *io.BinWriter) {
|
|
bw.WriteB(byte(attr.Type))
|
|
switch t := attr.Type; t {
|
|
case HighPriority:
|
|
case OracleResponseT, NotValidBeforeT, ConflictsT, NotaryAssistedT:
|
|
attr.Value.EncodeBinary(bw)
|
|
default:
|
|
if t >= ReservedLowerBound && t <= ReservedUpperBound {
|
|
attr.Value.EncodeBinary(bw)
|
|
break
|
|
}
|
|
bw.Err = fmt.Errorf("failed encoding TX attribute usage: 0x%2x", attr.Type)
|
|
}
|
|
}
|
|
|
|
// MarshalJSON implements the json Marshaller interface.
|
|
func (attr *Attribute) MarshalJSON() ([]byte, error) {
|
|
m := map[string]any{"type": attr.Type.String()}
|
|
if attr.Value != nil {
|
|
attr.Value.toJSONMap(m)
|
|
}
|
|
return json.Marshal(m)
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaller interface.
|
|
func (attr *Attribute) UnmarshalJSON(data []byte) error {
|
|
aj := new(attrJSON)
|
|
err := json.Unmarshal(data, aj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch aj.Type {
|
|
case HighPriority.String():
|
|
attr.Type = HighPriority
|
|
return nil
|
|
case OracleResponseT.String():
|
|
attr.Type = OracleResponseT
|
|
// Note: because `type` field will not be present in any attribute
|
|
// value, we can unmarshal the same data. The overhead is minimal.
|
|
attr.Value = new(OracleResponse)
|
|
case NotValidBeforeT.String():
|
|
attr.Type = NotValidBeforeT
|
|
attr.Value = new(NotValidBefore)
|
|
case ConflictsT.String():
|
|
attr.Type = ConflictsT
|
|
attr.Value = new(Conflicts)
|
|
case NotaryAssistedT.String():
|
|
attr.Type = NotaryAssistedT
|
|
attr.Value = new(NotaryAssisted)
|
|
default:
|
|
return errors.New("wrong Type")
|
|
}
|
|
return json.Unmarshal(data, attr.Value)
|
|
}
|
|
|
|
// Copy creates a deep copy of the Attribute.
|
|
func (attr *Attribute) Copy() *Attribute {
|
|
if attr == nil {
|
|
return nil
|
|
}
|
|
cp := &Attribute{
|
|
Type: attr.Type,
|
|
}
|
|
if attr.Value != nil {
|
|
cp.Value = attr.Value.Copy()
|
|
}
|
|
return cp
|
|
}
|