neo-go/pkg/network/payload/notary_request.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

150 lines
4.9 KiB
Go

package payload
import (
"bytes"
"errors"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
)
// P2PNotaryRequest contains main and fallback transactions for the Notary service.
type P2PNotaryRequest struct {
MainTransaction *transaction.Transaction `json:"maintx"`
FallbackTransaction *transaction.Transaction `json:"fallbacktx"`
Witness transaction.Witness
hash util.Uint256
}
// NewP2PNotaryRequestFromBytes decodes a P2PNotaryRequest from the given bytes.
func NewP2PNotaryRequestFromBytes(b []byte) (*P2PNotaryRequest, error) {
req := &P2PNotaryRequest{}
br := io.NewBinReaderFromBuf(b)
req.DecodeBinary(br)
if br.Err != nil {
return nil, br.Err
}
if br.Len() != 0 {
return nil, errors.New("additional data after the payload")
}
return req, nil
}
// Bytes returns serialized P2PNotaryRequest payload.
func (r *P2PNotaryRequest) Bytes() ([]byte, error) {
buf := io.NewBufBinWriter()
r.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return nil, buf.Err
}
return buf.Bytes(), nil
}
// Hash returns payload's hash.
func (r *P2PNotaryRequest) Hash() util.Uint256 {
if r.hash.Equals(util.Uint256{}) {
if r.createHash() != nil {
panic("failed to compute hash!")
}
}
return r.hash
}
// createHash creates hash of the payload.
func (r *P2PNotaryRequest) createHash() error {
buf := io.NewBufBinWriter()
r.encodeHashableFields(buf.BinWriter)
r.hash = hash.Sha256(buf.Bytes())
return nil
}
// DecodeBinaryUnsigned reads payload from the w excluding signature.
func (r *P2PNotaryRequest) decodeHashableFields(br *io.BinReader) {
r.MainTransaction = &transaction.Transaction{}
r.FallbackTransaction = &transaction.Transaction{}
r.MainTransaction.DecodeBinary(br)
r.FallbackTransaction.DecodeBinary(br)
if br.Err == nil {
br.Err = r.isValid()
}
if br.Err == nil {
br.Err = r.createHash()
}
}
// DecodeBinary implements the io.Serializable interface.
func (r *P2PNotaryRequest) DecodeBinary(br *io.BinReader) {
r.decodeHashableFields(br)
if br.Err == nil {
r.Witness.DecodeBinary(br)
}
}
// encodeHashableFields writes payload to the w excluding signature.
func (r *P2PNotaryRequest) encodeHashableFields(bw *io.BinWriter) {
r.MainTransaction.EncodeBinary(bw)
r.FallbackTransaction.EncodeBinary(bw)
}
// EncodeBinary implements the Serializable interface.
func (r *P2PNotaryRequest) EncodeBinary(bw *io.BinWriter) {
r.encodeHashableFields(bw)
r.Witness.EncodeBinary(bw)
}
func (r *P2PNotaryRequest) isValid() error {
nKeysMain := r.MainTransaction.GetAttributes(transaction.NotaryAssistedT)
if len(nKeysMain) == 0 {
return errors.New("main transaction should have NotaryAssisted attribute")
}
if nKeysMain[0].Value.(*transaction.NotaryAssisted).NKeys == 0 {
return errors.New("main transaction should have NKeys > 0")
}
if len(r.FallbackTransaction.Signers) != 2 {
return errors.New("fallback transaction should have two signers")
}
if len(r.FallbackTransaction.Scripts[0].InvocationScript) != 66 ||
len(r.FallbackTransaction.Scripts[0].VerificationScript) != 0 ||
!bytes.HasPrefix(r.FallbackTransaction.Scripts[0].InvocationScript, []byte{byte(opcode.PUSHDATA1), keys.SignatureLen}) {
return errors.New("fallback transaction has invalid dummy Notary witness")
}
if !r.FallbackTransaction.HasAttribute(transaction.NotValidBeforeT) {
return errors.New("fallback transactions should have NotValidBefore attribute")
}
conflicts := r.FallbackTransaction.GetAttributes(transaction.ConflictsT)
if len(conflicts) != 1 {
return errors.New("fallback transaction should have one Conflicts attribute")
}
if conflicts[0].Value.(*transaction.Conflicts).Hash != r.MainTransaction.Hash() {
return errors.New("fallback transaction does not conflict with the main transaction")
}
nKeysFallback := r.FallbackTransaction.GetAttributes(transaction.NotaryAssistedT)
if len(nKeysFallback) == 0 {
return errors.New("fallback transaction should have NotaryAssisted attribute")
}
if nKeysFallback[0].Value.(*transaction.NotaryAssisted).NKeys != 0 {
return errors.New("fallback transaction should have NKeys = 0")
}
if r.MainTransaction.ValidUntilBlock != r.FallbackTransaction.ValidUntilBlock {
return errors.New("both main and fallback transactions should have the same ValidUntil value")
}
return nil
}
// Copy creates a deep copy of P2PNotaryRequest. It creates deep copy of the MainTransaction,
// FallbackTransaction and Witness, including all slice fields. Cached values like
// 'hashed' and 'size' of the transactions are reset to ensure the copy can be modified
// independently of the original.
func (r *P2PNotaryRequest) Copy() *P2PNotaryRequest {
return &P2PNotaryRequest{
MainTransaction: r.MainTransaction.Copy(),
FallbackTransaction: r.FallbackTransaction.Copy(),
Witness: r.Witness.Copy(),
}
}