github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/network/payload/notary_request.go (about) 1 package payload 2 3 import ( 4 "bytes" 5 "errors" 6 7 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 8 "github.com/nspcc-dev/neo-go/pkg/crypto/hash" 9 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 10 "github.com/nspcc-dev/neo-go/pkg/io" 11 "github.com/nspcc-dev/neo-go/pkg/util" 12 "github.com/nspcc-dev/neo-go/pkg/vm/opcode" 13 ) 14 15 // P2PNotaryRequest contains main and fallback transactions for the Notary service. 16 type P2PNotaryRequest struct { 17 MainTransaction *transaction.Transaction `json:"maintx"` 18 FallbackTransaction *transaction.Transaction `json:"fallbacktx"` 19 20 Witness transaction.Witness 21 22 hash util.Uint256 23 } 24 25 // NewP2PNotaryRequestFromBytes decodes a P2PNotaryRequest from the given bytes. 26 func NewP2PNotaryRequestFromBytes(b []byte) (*P2PNotaryRequest, error) { 27 req := &P2PNotaryRequest{} 28 br := io.NewBinReaderFromBuf(b) 29 req.DecodeBinary(br) 30 if br.Err != nil { 31 return nil, br.Err 32 } 33 if br.Len() != 0 { 34 return nil, errors.New("additional data after the payload") 35 } 36 return req, nil 37 } 38 39 // Bytes returns serialized P2PNotaryRequest payload. 40 func (r *P2PNotaryRequest) Bytes() ([]byte, error) { 41 buf := io.NewBufBinWriter() 42 r.EncodeBinary(buf.BinWriter) 43 if buf.Err != nil { 44 return nil, buf.Err 45 } 46 return buf.Bytes(), nil 47 } 48 49 // Hash returns payload's hash. 50 func (r *P2PNotaryRequest) Hash() util.Uint256 { 51 if r.hash.Equals(util.Uint256{}) { 52 if r.createHash() != nil { 53 panic("failed to compute hash!") 54 } 55 } 56 return r.hash 57 } 58 59 // createHash creates hash of the payload. 60 func (r *P2PNotaryRequest) createHash() error { 61 buf := io.NewBufBinWriter() 62 r.encodeHashableFields(buf.BinWriter) 63 r.hash = hash.Sha256(buf.Bytes()) 64 return nil 65 } 66 67 // DecodeBinaryUnsigned reads payload from the w excluding signature. 68 func (r *P2PNotaryRequest) decodeHashableFields(br *io.BinReader) { 69 r.MainTransaction = &transaction.Transaction{} 70 r.FallbackTransaction = &transaction.Transaction{} 71 r.MainTransaction.DecodeBinary(br) 72 r.FallbackTransaction.DecodeBinary(br) 73 if br.Err == nil { 74 br.Err = r.isValid() 75 } 76 if br.Err == nil { 77 br.Err = r.createHash() 78 } 79 } 80 81 // DecodeBinary implements the io.Serializable interface. 82 func (r *P2PNotaryRequest) DecodeBinary(br *io.BinReader) { 83 r.decodeHashableFields(br) 84 if br.Err == nil { 85 r.Witness.DecodeBinary(br) 86 } 87 } 88 89 // encodeHashableFields writes payload to the w excluding signature. 90 func (r *P2PNotaryRequest) encodeHashableFields(bw *io.BinWriter) { 91 r.MainTransaction.EncodeBinary(bw) 92 r.FallbackTransaction.EncodeBinary(bw) 93 } 94 95 // EncodeBinary implements the Serializable interface. 96 func (r *P2PNotaryRequest) EncodeBinary(bw *io.BinWriter) { 97 r.encodeHashableFields(bw) 98 r.Witness.EncodeBinary(bw) 99 } 100 101 func (r *P2PNotaryRequest) isValid() error { 102 nKeysMain := r.MainTransaction.GetAttributes(transaction.NotaryAssistedT) 103 if len(nKeysMain) == 0 { 104 return errors.New("main transaction should have NotaryAssisted attribute") 105 } 106 if nKeysMain[0].Value.(*transaction.NotaryAssisted).NKeys == 0 { 107 return errors.New("main transaction should have NKeys > 0") 108 } 109 if len(r.FallbackTransaction.Signers) != 2 { 110 return errors.New("fallback transaction should have two signers") 111 } 112 if len(r.FallbackTransaction.Scripts[0].InvocationScript) != 66 || 113 len(r.FallbackTransaction.Scripts[0].VerificationScript) != 0 || 114 !bytes.HasPrefix(r.FallbackTransaction.Scripts[0].InvocationScript, []byte{byte(opcode.PUSHDATA1), keys.SignatureLen}) { 115 return errors.New("fallback transaction has invalid dummy Notary witness") 116 } 117 if !r.FallbackTransaction.HasAttribute(transaction.NotValidBeforeT) { 118 return errors.New("fallback transactions should have NotValidBefore attribute") 119 } 120 conflicts := r.FallbackTransaction.GetAttributes(transaction.ConflictsT) 121 if len(conflicts) != 1 { 122 return errors.New("fallback transaction should have one Conflicts attribute") 123 } 124 if conflicts[0].Value.(*transaction.Conflicts).Hash != r.MainTransaction.Hash() { 125 return errors.New("fallback transaction does not conflict with the main transaction") 126 } 127 nKeysFallback := r.FallbackTransaction.GetAttributes(transaction.NotaryAssistedT) 128 if len(nKeysFallback) == 0 { 129 return errors.New("fallback transaction should have NotaryAssisted attribute") 130 } 131 if nKeysFallback[0].Value.(*transaction.NotaryAssisted).NKeys != 0 { 132 return errors.New("fallback transaction should have NKeys = 0") 133 } 134 if r.MainTransaction.ValidUntilBlock != r.FallbackTransaction.ValidUntilBlock { 135 return errors.New("both main and fallback transactions should have the same ValidUntil value") 136 } 137 return nil 138 } 139 140 // Copy creates a deep copy of P2PNotaryRequest. It creates deep copy of the MainTransaction, 141 // FallbackTransaction and Witness, including all slice fields. Cached values like 142 // 'hashed' and 'size' of the transactions are reset to ensure the copy can be modified 143 // independently of the original. 144 func (r *P2PNotaryRequest) Copy() *P2PNotaryRequest { 145 return &P2PNotaryRequest{ 146 MainTransaction: r.MainTransaction.Copy(), 147 FallbackTransaction: r.FallbackTransaction.Copy(), 148 Witness: r.Witness.Copy(), 149 } 150 }