neoneo-go/pkg/core/transaction/attribute.go
Ekaterina Pavlova 956fd08adb *: add Copy() to transaction.Transaction and payload.P2PNotaryRequest
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>
2024-04-28 00:59:15 +05:30

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
}