diff --git a/api/service/transactionApiService.go b/api/service/transactionApiService.go index 79a921548..fc189514d 100644 --- a/api/service/transactionApiService.go +++ b/api/service/transactionApiService.go @@ -193,6 +193,7 @@ func (ts *TransactionService) GetTransactions( &tx.Signature, &tx.Version, &tx.TransactionIndex, + &tx.MultisigChild, ) if err != nil { if err != sql.ErrNoRows { diff --git a/api/service/transactionApiService_test.go b/api/service/transactionApiService_test.go index 7d1553980..7b770aa3a 100644 --- a/api/service/transactionApiService_test.go +++ b/api/service/transactionApiService_test.go @@ -591,6 +591,7 @@ func (*mockQueryGetTransactionsSuccess) ExecuteSelect(qStr string, tx bool, args []byte{0, 0, 0, 0, 0, 0, 0}, 1, 1, + false, ), ) } @@ -684,6 +685,7 @@ func TestTransactionService_GetTransactions(t *testing.T) { Signature: []byte{0, 0, 0, 0, 0, 0, 0}, Version: 1, TransactionIndex: 1, + MultisigChild: false, }, }, }, @@ -737,6 +739,7 @@ func (*mockQueryGetTransactionSuccess) ExecuteSelect( 8, []byte{1, 2, 3, 4, 5, 6, 7, 8}, []byte{0, 0, 0, 0, 0, 0, 0}, 1, 1, + false, ), ) return db.Query("") @@ -759,6 +762,7 @@ func (*mockQueryGetTransactionSuccess) ExecuteSelectRow(qstr string, tx bool, ar 8, []byte{1, 2, 3, 4, 5, 6, 7, 8}, []byte{0, 0, 0, 0, 0, 0, 0}, 1, 1, + false, ), ) return db.QueryRow(""), nil @@ -837,6 +841,7 @@ func TestTransactionService_GetTransaction(t *testing.T) { Signature: []byte{0, 0, 0, 0, 0, 0, 0}, Version: 1, TransactionIndex: 1, + MultisigChild: false, }, }, } diff --git a/cmd/main.go b/cmd/main.go index 518e95363..9fca5ec9b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -7,6 +7,8 @@ import ( "strings" "time" + "github.com/zoobc/zoobc-core/cmd/signature" + "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/zoobc/zoobc-core/cmd/account" @@ -73,6 +75,7 @@ func main() { rootCmd.AddCommand(genesisblock.Commands()) rootCmd.AddCommand(rollback.Commands(sqliteDB)) rootCmd.AddCommand(parserCmd) + rootCmd.AddCommand(signature.Commands()) generateCmd.AddCommand(account.Commands()) generateCmd.AddCommand(transaction.Commands(sqliteDB)) generateCmd.AddCommand(block.Commands()) diff --git a/cmd/readme.md b/cmd/readme.md index 2fed2bf37..fb24880f8 100644 --- a/cmd/readme.md +++ b/cmd/readme.md @@ -103,7 +103,7 @@ ofiness deviation starved" ### Account Generating multisig ```bash -go run main.go generate account multisig --addresses BCZnSfqpP5tqFQlMTYkDeBVFWnbyVK7vLr5ORFpTjgtN --addresses BCZD_VxfO2S9aziIL3cn_cXW7uPDVPOrnXuP98GEAUC7 --addresses BCZKLvgUYZ1KKx-jtF9KoJskjVPvB9jpIjfzzI6zDW0J —min-sigs 2 —nonce 3 +go run main.go generate account multisig --addresses BCZnSfqpP5tqFQlMTYkDeBVFWnbyVK7vLr5ORFpTjgtN --addresses BCZD_VxfO2S9aziIL3cn_cXW7uPDVPOrnXuP98GEAUC7 --addresses BCZKLvgUYZ1KKx-jtF9KoJskjVPvB9jpIjfzzI6zDW0J —-min-sigs=2 --nonce=3 ``` ### Account Generate with spesific signature type diff --git a/cmd/signature/cmd.go b/cmd/signature/cmd.go new file mode 100644 index 000000000..a4f06d133 --- /dev/null +++ b/cmd/signature/cmd.go @@ -0,0 +1,71 @@ +package signature + +import ( + "encoding/hex" + "fmt" + "strconv" + "strings" + + "github.com/zoobc/zoobc-core/common/model" + + "github.com/zoobc/zoobc-core/common/crypto" + "golang.org/x/crypto/sha3" + + "github.com/spf13/cobra" +) + +var ( + /* + Signer command line tools + */ + signerCmd = &cobra.Command{ + Use: "sign", + Short: "sign provided data", + Long: "sign any provided data by using the --seed parameter", + } +) + +func init() { + signerCmd.Flags().StringVar(&dataHex, "data-hex", "", "hex string of the data to sign") + signerCmd.Flags().StringVar(&dataBytes, "data-bytes", "", "data bytes separated by `, `. eg:"+ + "--data-bytes='1, 222, 54, 12, 32'") + signerCmd.Flags().StringVar(&seed, "seed", "", "your secret phrase") + signerCmd.Flags().BoolVar(&hash, "hash", false, "turn this flag on to hash the data before signing") +} + +func Commands() *cobra.Command { + signerCmd.Run = SignData + return signerCmd +} + +func SignData(*cobra.Command, []string) { + var ( + unsignedBytes []byte + hashedUnsignedBytes [32]byte + signature []byte + ) + if dataHex != "" { + unsignedBytes, _ = hex.DecodeString(dataHex) + } else { + txByteCharSlice := strings.Split(dataBytes, ", ") + for _, v := range txByteCharSlice { + byteValue, err := strconv.Atoi(v) + if err != nil { + panic("failed to parse transaction bytes") + } + unsignedBytes = append(unsignedBytes, byte(byteValue)) + } + } + if hash { + hashedUnsignedBytes = sha3.Sum256(unsignedBytes) + signature, _ = (&crypto.Signature{}).Sign(hashedUnsignedBytes[:], model.SignatureType_DefaultSignature, seed) + } else { + signature, _ = (&crypto.Signature{}).Sign(unsignedBytes, model.SignatureType_DefaultSignature, seed) + } + edUtil := crypto.NewEd25519Signature() + fmt.Printf("account-address:\t%v\n", edUtil.GetAddressFromSeed(seed)) + fmt.Printf("transaction-bytes:\t%v\n", unsignedBytes) + fmt.Printf("transaction-hash:\t%v\n", hex.EncodeToString(hashedUnsignedBytes[:])) + fmt.Printf("signature-bytes:\t%v\n", signature) + fmt.Printf("signature-hex:\t%v\n", hex.EncodeToString(signature)) +} diff --git a/cmd/signature/const.go b/cmd/signature/const.go new file mode 100644 index 000000000..a92680bab --- /dev/null +++ b/cmd/signature/const.go @@ -0,0 +1,8 @@ +package signature + +var ( + seed string + dataHex string + dataBytes string + hash bool +) diff --git a/cmd/transaction/cmd.go b/cmd/transaction/cmd.go index 05b9a7f57..26bfbc0f5 100644 --- a/cmd/transaction/cmd.go +++ b/cmd/transaction/cmd.go @@ -78,6 +78,7 @@ func init() { txCmd.PersistentFlags().Int64Var(&fee, "fee", 1, "defines the fee of the transaction") txCmd.PersistentFlags().BoolVar(&post, "post", false, "post generated bytes to [127.0.0.1:7000](default)") txCmd.PersistentFlags().StringVar(&postHost, "post-host", "127.0.0.1:7000", "destination of post action") + txCmd.PersistentFlags().StringVar(&senderAddress, "sender-address", "", "transaction's sender address") txCmd.PersistentFlags().Int32Var( &senderSignatureType, "sender-signature-type", @@ -150,8 +151,8 @@ func init() { "to be valid") multiSigCmd.Flags().StringVar(&unsignedTxHex, "unsigned-transaction", "", "hex string of the unsigned transaction bytes") multiSigCmd.Flags().StringVar(&txHash, "transaction-hash", "", "hash of transaction being signed by address-signature list (hex)") - multiSigCmd.Flags().StringSliceVar(&addressSignatures, "address-signatures", []string{}, "address-signature list "+ - "--address-signatures='address1-signature1,address2-signature2'") + multiSigCmd.Flags().StringToStringVar(&addressSignatures, "address-signatures", make(map[string]string), "address:signature list "+ + "--address1='signature1' --address2='signature2'") } // Commands set TXGeneratorCommandsInstance that will used by whole commands diff --git a/cmd/transaction/const.go b/cmd/transaction/const.go index cfd6163c2..dff3003ca 100644 --- a/cmd/transaction/const.go +++ b/cmd/transaction/const.go @@ -27,6 +27,7 @@ var ( fee int64 post bool postHost string + senderAddress string senderSignatureType int32 // Send money transaction @@ -55,7 +56,7 @@ var ( // multiSignature unsignedTxHex string - addressSignatures []string + addressSignatures map[string]string txHash string addresses []string nonce int64 diff --git a/cmd/transaction/generator.go b/cmd/transaction/generator.go index 7beb66e44..c6b9ec8b6 100644 --- a/cmd/transaction/generator.go +++ b/cmd/transaction/generator.go @@ -239,22 +239,28 @@ func GenerateBasicTransaction( timestamp, fee int64, recipientAccountAddress string, ) *model.Transaction { - var senderAccountAddress string - switch model.SignatureType(senderSignatureType) { - case model.SignatureType_DefaultSignature: - senderAccountAddress = crypto.NewEd25519Signature().GetAddressFromSeed(senderSeed) - case model.SignatureType_BitcoinSignature: - var ( - bitcoinSig = crypto.NewBitcoinSignature(crypto.DefaultBitcoinNetworkParams(), crypto.DefaultBitcoinCurve()) - pubKey = bitcoinSig.GetPublicKeyFromSeed(senderSeed, crypto.DefaultBitcoinPublicKeyFormat()) - err error - ) - senderAccountAddress, err = bitcoinSig.GetAddressPublicKey(pubKey) - if err != nil { - fmt.Println("GenerateBasicTransaction-BitcoinSignature-Failed GetPublicKey") + var ( + senderAccountAddress string + ) + if senderSeed == "" { + senderAccountAddress = senderAddress + } else { + switch model.SignatureType(senderSignatureType) { + case model.SignatureType_DefaultSignature: + senderAccountAddress = crypto.NewEd25519Signature().GetAddressFromSeed(senderSeed) + case model.SignatureType_BitcoinSignature: + var ( + bitcoinSig = crypto.NewBitcoinSignature(crypto.DefaultBitcoinNetworkParams(), crypto.DefaultBitcoinCurve()) + pubKey = bitcoinSig.GetPublicKeyFromSeed(senderSeed, crypto.DefaultBitcoinPublicKeyFormat()) + err error + ) + senderAccountAddress, err = bitcoinSig.GetAddressPublicKey(pubKey) + if err != nil { + fmt.Println("GenerateBasicTransaction-BitcoinSignature-Failed GetPublicKey") + } + default: + panic("GenerateBasicTransaction-Invalid Signature Type") } - default: - panic("GenerateBasicTransaction-Invalid Signature Type") } if timestamp <= 0 { @@ -318,6 +324,9 @@ func GenerateSignedTxBytes(tx *model.Transaction, senderSeed string, signatureTy tx.Fee += minimumFee unsignedTxBytes, _ := transactionUtil.GetTransactionBytes(tx, false) + if senderSeed == "" { + return unsignedTxBytes + } tx.Signature, _ = signature.Sign( unsignedTxBytes, model.SignatureType(signatureType), @@ -388,7 +397,7 @@ func GeneratedMultiSignatureTransaction( minSignature uint32, nonce int64, unsignedTxHex, txHash string, - addressSignatures, addresses []string, + addressSignatures map[string]string, addresses []string, ) *model.Transaction { var ( signatures = make(map[string][]byte) @@ -410,29 +419,28 @@ func GeneratedMultiSignatureTransaction( return nil } } - if txHash != "" { transactionHash, err := hex.DecodeString(txHash) if err != nil { return nil } - for _, v := range addressSignatures { - asig := strings.Split(v, "-") - if len(asig) < 2 { - return nil + for k, v := range addressSignatures { + if k == "" { + sigType := util.ConvertUint32ToBytes(2) + signatures[k] = sigType + } else { + signature, err := hex.DecodeString(v) + if err != nil { + return nil + } + signatures[k] = signature } - signature, err := hex.DecodeString(asig[1]) - if err != nil { - return nil - } - signatures[asig[0]] = signature } signatureInfo = &model.SignatureInfo{ TransactionHash: transactionHash, Signatures: signatures, } } - tx.TransactionType = util.ConvertBytesToUint32(txTypeMap["multiSignature"]) txBody := &model.MultiSignatureTransactionBody{ MultiSignatureInfo: multiSigInfo, diff --git a/cmd/zoomd b/cmd/zoomd new file mode 100755 index 000000000..7fecc46bc Binary files /dev/null and b/cmd/zoomd differ diff --git a/common/crypto/signature.go b/common/crypto/signature.go index aaecf05b8..0a8fd5a0f 100644 --- a/common/crypto/signature.go +++ b/common/crypto/signature.go @@ -129,6 +129,8 @@ func (*Signature) VerifySignature(payload, signature []byte, accountAddress stri ) } return nil + case model.SignatureType_MultisigSignature: // multisig validation-only + return nil default: return blocker.NewBlocker( blocker.ValidationErr, diff --git a/common/database/migration.go b/common/database/migration.go index 671dc436f..cabf6af21 100644 --- a/common/database/migration.go +++ b/common/database/migration.go @@ -278,20 +278,23 @@ func (m *Migration) Init() error { `, ` CREATE TABLE IF NOT EXISTS "pending_transaction" ( + "sender_address" TEXT, -- sender of transaction "transaction_hash" BLOB, -- transaction hash of pending transaction "transaction_bytes" BLOB, -- full transaction bytes of the pending transaction "status" INTEGER, -- execution status of the pending transaction "block_height" INTEGER, -- height when pending transaction inserted/updated + "latest" INTEGER, -- latest flag for pending transaction PRIMARY KEY("transaction_hash", "block_height") ) `, ` CREATE TABLE IF NOT EXISTS "pending_signature" ( - "transaction_hash" INTEGER, -- transaction hash of pending transaction being signed + "transaction_hash" BLOB, -- transaction hash of pending transaction being signed "account_address" TEXT, -- account address of the respective signature "signature" BLOB, -- full transaction bytes of the pending transaction "block_height" INTEGER, -- height when pending signature inserted/updated - PRIMARY KEY("account_address", "transaction_hash") + "latest" INTEGER, -- latest flag for pending signature + PRIMARY KEY("account_address", "transaction_hash", "block_height") ) `, ` @@ -301,10 +304,15 @@ func (m *Migration) Init() error { "nonce" INTEGER, -- full transaction bytes of the pending transaction "addresses" TEXT, -- list of addresses / participants of the multisig account "block_height" INTEGER, -- height when multisignature_info inserted / updated + "latest" INTEGER, -- latest flag for pending signature PRIMARY KEY("multisig_address", "block_height") ) `, ` + ALTER TABLE "transaction" + ADD COLUMN "multisig_child" INTEGER DEFAULT 0 + `, + ` CREATE INDEX "node_registry_height_idx" ON "node_registry" ("height") `, ` diff --git a/common/model/multiSignature.pb.go b/common/model/multiSignature.pb.go index fe07f8b22..c9e88dec1 100644 --- a/common/model/multiSignature.pb.go +++ b/common/model/multiSignature.pb.go @@ -57,6 +57,7 @@ type MultiSignatureInfo struct { Addresses []string `protobuf:"bytes,3,rep,name=Addresses,proto3" json:"Addresses,omitempty"` MultisigAddress string `protobuf:"bytes,4,opt,name=MultisigAddress,proto3" json:"MultisigAddress,omitempty"` BlockHeight uint32 `protobuf:"varint,5,opt,name=BlockHeight,proto3" json:"BlockHeight,omitempty"` + Latest bool `protobuf:"varint,6,opt,name=Latest,proto3" json:"Latest,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -122,6 +123,13 @@ func (m *MultiSignatureInfo) GetBlockHeight() uint32 { return 0 } +func (m *MultiSignatureInfo) GetLatest() bool { + if m != nil { + return m.Latest + } + return false +} + // represent the signature posted by account type SignatureInfo struct { TransactionHash []byte `protobuf:"bytes,1,opt,name=TransactionHash,proto3" json:"TransactionHash,omitempty"` @@ -176,6 +184,7 @@ type PendingSignature struct { AccountAddress string `protobuf:"bytes,2,opt,name=AccountAddress,proto3" json:"AccountAddress,omitempty"` Signature []byte `protobuf:"bytes,3,opt,name=Signature,proto3" json:"Signature,omitempty"` BlockHeight uint32 `protobuf:"varint,4,opt,name=BlockHeight,proto3" json:"BlockHeight,omitempty"` + Latest bool `protobuf:"varint,5,opt,name=Latest,proto3" json:"Latest,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -234,12 +243,21 @@ func (m *PendingSignature) GetBlockHeight() uint32 { return 0 } +func (m *PendingSignature) GetLatest() bool { + if m != nil { + return m.Latest + } + return false +} + // represent transaction inside multisig body type PendingTransaction struct { - TransactionHash []byte `protobuf:"bytes,1,opt,name=TransactionHash,proto3" json:"TransactionHash,omitempty"` - TransactionBytes []byte `protobuf:"bytes,2,opt,name=TransactionBytes,proto3" json:"TransactionBytes,omitempty"` - Status PendingTransactionStatus `protobuf:"varint,3,opt,name=Status,proto3,enum=model.PendingTransactionStatus" json:"Status,omitempty"` - BlockHeight uint32 `protobuf:"varint,4,opt,name=BlockHeight,proto3" json:"BlockHeight,omitempty"` + SenderAddress string `protobuf:"bytes,1,opt,name=SenderAddress,proto3" json:"SenderAddress,omitempty"` + TransactionHash []byte `protobuf:"bytes,2,opt,name=TransactionHash,proto3" json:"TransactionHash,omitempty"` + TransactionBytes []byte `protobuf:"bytes,3,opt,name=TransactionBytes,proto3" json:"TransactionBytes,omitempty"` + Status PendingTransactionStatus `protobuf:"varint,4,opt,name=Status,proto3,enum=model.PendingTransactionStatus" json:"Status,omitempty"` + BlockHeight uint32 `protobuf:"varint,5,opt,name=BlockHeight,proto3" json:"BlockHeight,omitempty"` + Latest bool `protobuf:"varint,6,opt,name=Latest,proto3" json:"Latest,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -270,6 +288,13 @@ func (m *PendingTransaction) XXX_DiscardUnknown() { var xxx_messageInfo_PendingTransaction proto.InternalMessageInfo +func (m *PendingTransaction) GetSenderAddress() string { + if m != nil { + return m.SenderAddress + } + return "" +} + func (m *PendingTransaction) GetTransactionHash() []byte { if m != nil { return m.TransactionHash @@ -298,6 +323,13 @@ func (m *PendingTransaction) GetBlockHeight() uint32 { return 0 } +func (m *PendingTransaction) GetLatest() bool { + if m != nil { + return m.Latest + } + return false +} + func init() { proto.RegisterEnum("model.PendingTransactionStatus", PendingTransactionStatus_name, PendingTransactionStatus_value) proto.RegisterType((*MultiSignatureInfo)(nil), "model.MultiSignatureInfo") @@ -310,34 +342,36 @@ func init() { func init() { proto.RegisterFile("model/multiSignature.proto", fileDescriptor_136af44c597c17ae) } var fileDescriptor_136af44c597c17ae = []byte{ - // 454 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0xdd, 0x6e, 0xd3, 0x30, - 0x1c, 0xc5, 0x71, 0xb2, 0x4e, 0xea, 0x7f, 0x1f, 0x0d, 0x16, 0x42, 0xa6, 0xe2, 0x23, 0xaa, 0x10, - 0xb2, 0x2a, 0x68, 0xd1, 0xb8, 0x00, 0x21, 0x71, 0xb1, 0x8a, 0x4a, 0xe3, 0x62, 0x03, 0x79, 0x5c, - 0x71, 0x97, 0x3a, 0x26, 0xb5, 0xd6, 0xd8, 0x55, 0x6c, 0xa3, 0x95, 0xe7, 0xe0, 0x11, 0x78, 0x0e, - 0xee, 0xe0, 0xb9, 0xd0, 0x9c, 0xd0, 0xa6, 0x09, 0x08, 0xf5, 0x26, 0x8a, 0x7f, 0xc7, 0x39, 0x3e, - 0xff, 0x63, 0x05, 0xfa, 0xb9, 0x4e, 0xc5, 0x62, 0x9c, 0xbb, 0x85, 0x95, 0x97, 0x32, 0x53, 0x89, - 0x75, 0x85, 0x18, 0x2d, 0x0b, 0x6d, 0x35, 0xee, 0x78, 0x6d, 0xf0, 0x0b, 0x01, 0x3e, 0xdf, 0xd2, - 0xdf, 0xa9, 0xcf, 0x1a, 0x3f, 0x85, 0xdb, 0xe7, 0x52, 0xc9, 0xdc, 0xe5, 0x6b, 0x6e, 0x08, 0x8a, - 0x11, 0x3d, 0x62, 0x6d, 0x01, 0x13, 0xe8, 0x5c, 0x68, 0xc5, 0x05, 0x09, 0x62, 0x44, 0xc3, 0x49, - 0xf0, 0x1c, 0xb1, 0x12, 0xe0, 0xfb, 0xd0, 0x3d, 0x4d, 0xd3, 0x42, 0x18, 0x23, 0x0c, 0x09, 0xe3, - 0x90, 0x76, 0xd9, 0x06, 0x60, 0x0a, 0x3d, 0x7f, 0xb6, 0x91, 0x59, 0x05, 0xc9, 0x5e, 0x8c, 0x68, - 0x97, 0x35, 0x31, 0x8e, 0xe1, 0x60, 0xb2, 0xd0, 0xfc, 0xea, 0x4c, 0xc8, 0x6c, 0x6e, 0x49, 0xc7, - 0x27, 0xa9, 0xa3, 0xc1, 0x0f, 0x04, 0x47, 0xdb, 0x33, 0x50, 0xe8, 0x7d, 0x2c, 0x12, 0x65, 0x12, - 0x6e, 0xa5, 0x56, 0x67, 0x89, 0x99, 0xfb, 0x09, 0x0e, 0x59, 0x13, 0xe3, 0xb7, 0x00, 0xb5, 0x31, - 0x83, 0x38, 0xa4, 0x07, 0x27, 0x8f, 0x47, 0xbe, 0xa0, 0xd1, 0x96, 0xe7, 0x66, 0x65, 0xa6, 0xca, - 0x16, 0x2b, 0x56, 0xfb, 0xae, 0xff, 0x06, 0x7a, 0x0d, 0x19, 0x47, 0x10, 0x5e, 0x89, 0x95, 0x3f, - 0xb6, 0xcb, 0x6e, 0x5e, 0xf1, 0x1d, 0xe8, 0x7c, 0x49, 0x16, 0xae, 0xac, 0xea, 0x90, 0x95, 0x8b, - 0xd7, 0xc1, 0x2b, 0x34, 0xf8, 0x8e, 0x20, 0xfa, 0x20, 0x54, 0x2a, 0x55, 0xb6, 0xb6, 0xd9, 0x61, - 0x86, 0x27, 0x70, 0x7c, 0xca, 0xb9, 0x76, 0xca, 0xfe, 0xa9, 0x32, 0xf0, 0xa7, 0x36, 0xe8, 0xcd, - 0x8d, 0xac, 0xed, 0x49, 0xe8, 0xbd, 0x36, 0xa0, 0xd9, 0xf3, 0x5e, 0xbb, 0xe7, 0x9f, 0x08, 0x70, - 0x15, 0xb3, 0x16, 0x61, 0x87, 0xa0, 0x43, 0x88, 0x6a, 0x68, 0xb2, 0xb2, 0xc2, 0x54, 0x65, 0xb4, - 0x38, 0x7e, 0x09, 0xfb, 0x97, 0x36, 0xb1, 0xce, 0xf8, 0xa4, 0xc7, 0x27, 0x8f, 0xaa, 0x4b, 0x69, - 0x07, 0x28, 0xb7, 0xb1, 0x6a, 0xfb, 0xff, 0xe7, 0x18, 0x7e, 0x43, 0x40, 0xfe, 0x65, 0x83, 0x1f, - 0xc0, 0xbd, 0xb6, 0x56, 0x91, 0xe8, 0x16, 0x7e, 0x08, 0xfd, 0xb6, 0x3c, 0xbd, 0x16, 0xdc, 0x59, - 0x91, 0x46, 0x08, 0xf7, 0xe1, 0x6e, 0x5b, 0xbf, 0xd0, 0xef, 0x97, 0x51, 0xf0, 0x77, 0xeb, 0xe9, - 0xf5, 0x52, 0x16, 0x22, 0x8d, 0xc2, 0xc9, 0xf0, 0x13, 0xcd, 0xa4, 0x9d, 0xbb, 0xd9, 0x88, 0xeb, - 0x7c, 0xfc, 0x55, 0xeb, 0x19, 0x2f, 0x9f, 0xcf, 0xb8, 0x2e, 0xc4, 0x98, 0xeb, 0x3c, 0xd7, 0x6a, - 0xec, 0x5b, 0x98, 0xed, 0xfb, 0x3f, 0xf9, 0xc5, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4c, 0x15, - 0x8a, 0xed, 0xe7, 0x03, 0x00, 0x00, + // 491 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xdf, 0x8a, 0xd3, 0x40, + 0x14, 0xc6, 0x9d, 0x64, 0x53, 0xec, 0xd9, 0xed, 0x36, 0x0e, 0xb2, 0xc4, 0xe2, 0x9f, 0x50, 0x16, + 0x09, 0x45, 0x5b, 0x59, 0x2f, 0x14, 0xc1, 0x8b, 0x2d, 0x16, 0x56, 0x70, 0x57, 0x99, 0x7a, 0xe5, + 0x5d, 0x3a, 0x19, 0xd3, 0x61, 0x9b, 0x99, 0x92, 0x99, 0xc8, 0xd6, 0x5b, 0x5f, 0xc1, 0xc7, 0x11, + 0x1f, 0xc5, 0x67, 0x91, 0x4e, 0xd2, 0x36, 0x7f, 0xaa, 0xa0, 0x37, 0x21, 0xf3, 0xfb, 0x26, 0x67, + 0xce, 0xf7, 0xcd, 0x21, 0xd0, 0x4b, 0x64, 0xc4, 0x16, 0xa3, 0x24, 0x5b, 0x68, 0x3e, 0xe5, 0xb1, + 0x08, 0x75, 0x96, 0xb2, 0xe1, 0x32, 0x95, 0x5a, 0x62, 0xc7, 0x68, 0xfd, 0x5f, 0x08, 0xf0, 0x65, + 0x45, 0x7f, 0x2b, 0x3e, 0x4b, 0xfc, 0x04, 0xee, 0x5c, 0x72, 0xc1, 0x93, 0x2c, 0xd9, 0x72, 0xe5, + 0x21, 0x1f, 0x05, 0x1d, 0xd2, 0x14, 0xb0, 0x07, 0xce, 0x95, 0x14, 0x94, 0x79, 0x96, 0x8f, 0x02, + 0x7b, 0x6c, 0x3d, 0x43, 0x24, 0x07, 0xf8, 0x3e, 0xb4, 0xcf, 0xa3, 0x28, 0x65, 0x4a, 0x31, 0xe5, + 0xd9, 0xbe, 0x1d, 0xb4, 0xc9, 0x0e, 0xe0, 0x00, 0xba, 0xe6, 0x6c, 0xc5, 0xe3, 0x02, 0x7a, 0x07, + 0x3e, 0x0a, 0xda, 0xa4, 0x8e, 0xb1, 0x0f, 0x87, 0xe3, 0x85, 0xa4, 0xd7, 0x17, 0x8c, 0xc7, 0x73, + 0xed, 0x39, 0xa6, 0x93, 0x32, 0xc2, 0x27, 0xd0, 0x7a, 0x17, 0x6a, 0xa6, 0xb4, 0xd7, 0xf2, 0x51, + 0x70, 0x9b, 0x14, 0xab, 0xfe, 0x4f, 0x04, 0x9d, 0xaa, 0xb7, 0x00, 0xba, 0x1f, 0xd3, 0x50, 0xa8, + 0x90, 0x6a, 0x2e, 0xc5, 0x45, 0xa8, 0xe6, 0xc6, 0xd9, 0x11, 0xa9, 0x63, 0xfc, 0x06, 0xa0, 0x64, + 0xdf, 0xf2, 0xed, 0xe0, 0xf0, 0xec, 0x74, 0x68, 0x82, 0x1b, 0x56, 0x6a, 0xee, 0x56, 0x6a, 0x22, + 0x74, 0xba, 0x22, 0xa5, 0xef, 0x7a, 0xaf, 0xa1, 0x5b, 0x93, 0xb1, 0x0b, 0xf6, 0x35, 0x5b, 0x99, + 0x63, 0xdb, 0x64, 0xfd, 0x8a, 0xef, 0x82, 0xf3, 0x25, 0x5c, 0x64, 0x79, 0x84, 0x47, 0x24, 0x5f, + 0xbc, 0xb2, 0x5e, 0xa2, 0xfe, 0x0f, 0x04, 0xee, 0x07, 0x26, 0x22, 0x2e, 0xe2, 0x6d, 0x99, 0x7f, + 0xf0, 0xf0, 0x18, 0x8e, 0xcf, 0x29, 0x95, 0x99, 0xd0, 0x9b, 0x88, 0x2d, 0x73, 0x6a, 0x8d, 0xae, + 0x6f, 0x6a, 0x5b, 0xde, 0xb3, 0x4d, 0xad, 0x1d, 0xa8, 0xe7, 0x7f, 0xf0, 0xb7, 0xfc, 0x9d, 0x4a, + 0xfe, 0xdf, 0x2c, 0xc0, 0x45, 0xfb, 0xa5, 0xd6, 0xf0, 0x29, 0x74, 0xa6, 0x4c, 0x44, 0x2c, 0xdd, + 0x74, 0x95, 0x67, 0x51, 0x85, 0xfb, 0x6c, 0x5a, 0xfb, 0x6d, 0x0e, 0xc0, 0x2d, 0xa1, 0xf1, 0x4a, + 0x9b, 0x79, 0x5b, 0x6f, 0x6d, 0x70, 0xfc, 0x02, 0x5a, 0x53, 0x1d, 0xea, 0x2c, 0x9f, 0xb6, 0xe3, + 0xb3, 0x47, 0xc5, 0x95, 0x36, 0xdb, 0xcc, 0xb7, 0x91, 0x62, 0xfb, 0xff, 0x4f, 0xe1, 0xe0, 0x3b, + 0x02, 0xef, 0x4f, 0xe5, 0xf1, 0x03, 0xb8, 0xd7, 0xd4, 0x0a, 0xe2, 0xde, 0xc2, 0x0f, 0xa1, 0xd7, + 0x94, 0x27, 0x37, 0x8c, 0x66, 0x9a, 0x45, 0x2e, 0xc2, 0x3d, 0x38, 0x69, 0xea, 0x57, 0xf2, 0xfd, + 0xd2, 0xb5, 0xf6, 0x97, 0x9e, 0xdc, 0x2c, 0x79, 0xca, 0x22, 0xd7, 0x1e, 0x0f, 0x3e, 0x05, 0x31, + 0xd7, 0xf3, 0x6c, 0x36, 0xa4, 0x32, 0x19, 0x7d, 0x95, 0x72, 0x46, 0xf3, 0xe7, 0x53, 0x2a, 0x53, + 0x36, 0xa2, 0x32, 0x49, 0xa4, 0x18, 0x99, 0x74, 0x66, 0x2d, 0xf3, 0xdf, 0x78, 0xfe, 0x3b, 0x00, + 0x00, 0xff, 0xff, 0x25, 0x40, 0xb8, 0x4b, 0x55, 0x04, 0x00, 0x00, } diff --git a/common/model/signature.pb.go b/common/model/signature.pb.go index 10aad9c0a..b9b488b19 100644 --- a/common/model/signature.pb.go +++ b/common/model/signature.pb.go @@ -28,16 +28,20 @@ const ( // in bytes: []byte{1,0,0,0}, bitcoin uses a specific Koblitz curve secp256k1 // Koblitz curves are a type of Elliptic Curve Digital Signature Algorithm SignatureType_BitcoinSignature SignatureType = 1 + // in bytes: []byte{2,0,0,0} for multisig validation purpose only + SignatureType_MultisigSignature SignatureType = 2 ) var SignatureType_name = map[int32]string{ 0: "DefaultSignature", 1: "BitcoinSignature", + 2: "MultisigSignature", } var SignatureType_value = map[string]int32{ - "DefaultSignature": 0, - "BitcoinSignature": 1, + "DefaultSignature": 0, + "BitcoinSignature": 1, + "MultisigSignature": 2, } func (x SignatureType) String() string { @@ -55,14 +59,15 @@ func init() { func init() { proto.RegisterFile("model/signature.proto", fileDescriptor_a69ee5fbbdd37ed5) } var fileDescriptor_a69ee5fbbdd37ed5 = []byte{ - // 130 bytes of a gzipped FileDescriptorProto + // 145 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xcd, 0xcd, 0x4f, 0x49, 0xcd, 0xd1, 0x2f, 0xce, 0x4c, 0xcf, 0x4b, 0x2c, 0x29, 0x2d, 0x4a, 0xd5, 0x2b, 0x28, 0xca, 0x2f, - 0xc9, 0x17, 0x62, 0x05, 0x0b, 0x6b, 0x59, 0x73, 0xf1, 0x06, 0xc3, 0x64, 0x42, 0x2a, 0x0b, 0x52, + 0xc9, 0x17, 0x62, 0x05, 0x0b, 0x6b, 0x05, 0x71, 0xf1, 0x06, 0xc3, 0x64, 0x42, 0x2a, 0x0b, 0x52, 0x85, 0x44, 0xb8, 0x04, 0x5c, 0x52, 0xd3, 0x12, 0x4b, 0x73, 0x4a, 0xe0, 0xe2, 0x02, 0x0c, 0x20, - 0x51, 0xa7, 0xcc, 0x92, 0xe4, 0xfc, 0xcc, 0x3c, 0x84, 0x28, 0xa3, 0x93, 0x56, 0x94, 0x46, 0x7a, - 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x55, 0x7e, 0x7e, 0x52, 0x32, 0x84, - 0xd4, 0x4d, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0xce, 0xcf, 0xcd, 0xcd, 0xcf, 0xd3, 0x07, 0x5b, 0x94, - 0xc4, 0x06, 0xb6, 0xd6, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x0d, 0x8c, 0x04, 0xb0, 0x8f, 0x00, - 0x00, 0x00, + 0x51, 0xa7, 0xcc, 0x92, 0xe4, 0xfc, 0xcc, 0x3c, 0x84, 0x28, 0xa3, 0x90, 0x28, 0x97, 0xa0, 0x6f, + 0x69, 0x4e, 0x49, 0x66, 0x71, 0x66, 0x3a, 0x42, 0x98, 0xc9, 0x49, 0x2b, 0x4a, 0x23, 0x3d, 0xb3, + 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0xbf, 0x2a, 0x3f, 0x3f, 0x29, 0x19, 0x42, 0xea, + 0x26, 0xe7, 0x17, 0xa5, 0xea, 0x27, 0xe7, 0xe7, 0xe6, 0xe6, 0xe7, 0xe9, 0x83, 0xed, 0x4f, 0x62, + 0x03, 0xbb, 0xc6, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x4b, 0x53, 0x54, 0x61, 0xa6, 0x00, 0x00, + 0x00, } diff --git a/common/model/transaction.pb.go b/common/model/transaction.pb.go index 80519d09a..5233955b1 100644 --- a/common/model/transaction.pb.go +++ b/common/model/transaction.pb.go @@ -94,7 +94,8 @@ type Transaction struct { TransactionBodyLength uint32 `protobuf:"varint,11,opt,name=TransactionBodyLength,proto3" json:"TransactionBodyLength,omitempty"` TransactionBodyBytes []byte `protobuf:"bytes,12,opt,name=TransactionBodyBytes,proto3" json:"TransactionBodyBytes,omitempty"` TransactionIndex uint32 `protobuf:"varint,13,opt,name=TransactionIndex,proto3" json:"TransactionIndex,omitempty"` - // TransactionBody + MultisigChild bool `protobuf:"varint,14,opt,name=MultisigChild,proto3" json:"MultisigChild,omitempty"` + // transactionbody // // Types that are valid to be assigned to TransactionBody: // *Transaction_EmptyTransactionBody @@ -108,9 +109,9 @@ type Transaction struct { // *Transaction_ApprovalEscrowTransactionBody // *Transaction_MultiSignatureTransactionBody TransactionBody isTransaction_TransactionBody `protobuf_oneof:"TransactionBody"` - Signature []byte `protobuf:"bytes,24,opt,name=Signature,proto3" json:"Signature,omitempty"` + Signature []byte `protobuf:"bytes,25,opt,name=Signature,proto3" json:"Signature,omitempty"` // nullable - Escrow *Escrow `protobuf:"bytes,25,opt,name=Escrow,proto3" json:"Escrow,omitempty"` + Escrow *Escrow `protobuf:"bytes,26,opt,name=Escrow,proto3" json:"Escrow,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -232,48 +233,55 @@ func (m *Transaction) GetTransactionIndex() uint32 { return 0 } +func (m *Transaction) GetMultisigChild() bool { + if m != nil { + return m.MultisigChild + } + return false +} + type isTransaction_TransactionBody interface { isTransaction_TransactionBody() } type Transaction_EmptyTransactionBody struct { - EmptyTransactionBody *EmptyTransactionBody `protobuf:"bytes,14,opt,name=emptyTransactionBody,proto3,oneof"` + EmptyTransactionBody *EmptyTransactionBody `protobuf:"bytes,15,opt,name=emptyTransactionBody,proto3,oneof"` } type Transaction_SendMoneyTransactionBody struct { - SendMoneyTransactionBody *SendMoneyTransactionBody `protobuf:"bytes,15,opt,name=sendMoneyTransactionBody,proto3,oneof"` + SendMoneyTransactionBody *SendMoneyTransactionBody `protobuf:"bytes,16,opt,name=sendMoneyTransactionBody,proto3,oneof"` } type Transaction_NodeRegistrationTransactionBody struct { - NodeRegistrationTransactionBody *NodeRegistrationTransactionBody `protobuf:"bytes,16,opt,name=nodeRegistrationTransactionBody,proto3,oneof"` + NodeRegistrationTransactionBody *NodeRegistrationTransactionBody `protobuf:"bytes,17,opt,name=nodeRegistrationTransactionBody,proto3,oneof"` } type Transaction_UpdateNodeRegistrationTransactionBody struct { - UpdateNodeRegistrationTransactionBody *UpdateNodeRegistrationTransactionBody `protobuf:"bytes,17,opt,name=updateNodeRegistrationTransactionBody,proto3,oneof"` + UpdateNodeRegistrationTransactionBody *UpdateNodeRegistrationTransactionBody `protobuf:"bytes,18,opt,name=updateNodeRegistrationTransactionBody,proto3,oneof"` } type Transaction_RemoveNodeRegistrationTransactionBody struct { - RemoveNodeRegistrationTransactionBody *RemoveNodeRegistrationTransactionBody `protobuf:"bytes,18,opt,name=removeNodeRegistrationTransactionBody,proto3,oneof"` + RemoveNodeRegistrationTransactionBody *RemoveNodeRegistrationTransactionBody `protobuf:"bytes,19,opt,name=removeNodeRegistrationTransactionBody,proto3,oneof"` } type Transaction_ClaimNodeRegistrationTransactionBody struct { - ClaimNodeRegistrationTransactionBody *ClaimNodeRegistrationTransactionBody `protobuf:"bytes,19,opt,name=claimNodeRegistrationTransactionBody,proto3,oneof"` + ClaimNodeRegistrationTransactionBody *ClaimNodeRegistrationTransactionBody `protobuf:"bytes,20,opt,name=claimNodeRegistrationTransactionBody,proto3,oneof"` } type Transaction_SetupAccountDatasetTransactionBody struct { - SetupAccountDatasetTransactionBody *SetupAccountDatasetTransactionBody `protobuf:"bytes,20,opt,name=setupAccountDatasetTransactionBody,proto3,oneof"` + SetupAccountDatasetTransactionBody *SetupAccountDatasetTransactionBody `protobuf:"bytes,21,opt,name=setupAccountDatasetTransactionBody,proto3,oneof"` } type Transaction_RemoveAccountDatasetTransactionBody struct { - RemoveAccountDatasetTransactionBody *RemoveAccountDatasetTransactionBody `protobuf:"bytes,21,opt,name=removeAccountDatasetTransactionBody,proto3,oneof"` + RemoveAccountDatasetTransactionBody *RemoveAccountDatasetTransactionBody `protobuf:"bytes,22,opt,name=removeAccountDatasetTransactionBody,proto3,oneof"` } type Transaction_ApprovalEscrowTransactionBody struct { - ApprovalEscrowTransactionBody *ApprovalEscrowTransactionBody `protobuf:"bytes,22,opt,name=approvalEscrowTransactionBody,proto3,oneof"` + ApprovalEscrowTransactionBody *ApprovalEscrowTransactionBody `protobuf:"bytes,23,opt,name=approvalEscrowTransactionBody,proto3,oneof"` } type Transaction_MultiSignatureTransactionBody struct { - MultiSignatureTransactionBody *MultiSignatureTransactionBody `protobuf:"bytes,23,opt,name=multiSignatureTransactionBody,proto3,oneof"` + MultiSignatureTransactionBody *MultiSignatureTransactionBody `protobuf:"bytes,24,opt,name=multiSignatureTransactionBody,proto3,oneof"` } func (*Transaction_EmptyTransactionBody) isTransaction_TransactionBody() {} @@ -1554,96 +1562,97 @@ func init() { func init() { proto.RegisterFile("model/transaction.proto", fileDescriptor_8333001f09b34082) } var fileDescriptor_8333001f09b34082 = []byte{ - // 1449 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4f, 0x6f, 0xdb, 0xc6, - 0x12, 0x17, 0x29, 0xcb, 0xb1, 0x47, 0x92, 0x23, 0x6f, 0x64, 0x89, 0x71, 0xec, 0x58, 0x8f, 0xb1, - 0x0d, 0x3d, 0x27, 0xb1, 0x5f, 0xfc, 0x82, 0x34, 0xcd, 0xcd, 0x8a, 0x93, 0xca, 0x68, 0xdc, 0xb8, - 0x6b, 0x27, 0x87, 0x00, 0x3d, 0xd0, 0xd4, 0x46, 0x22, 0x22, 0x71, 0x59, 0x72, 0x95, 0xd4, 0x4d, - 0x51, 0xa0, 0x69, 0x7b, 0x28, 0xd0, 0x63, 0xbf, 0x52, 0xbf, 0x45, 0x2f, 0x2d, 0x7a, 0xea, 0x87, - 0x28, 0x0a, 0x2e, 0x57, 0x12, 0x97, 0xa2, 0x48, 0x36, 0xbd, 0xf4, 0x22, 0x40, 0xf3, 0x9b, 0x9d, - 0x3f, 0xbb, 0xc3, 0xdf, 0xce, 0x2c, 0xd4, 0x07, 0xb4, 0x43, 0xfa, 0x7b, 0xcc, 0x35, 0x6c, 0xcf, - 0x30, 0x99, 0x45, 0xed, 0x5d, 0xc7, 0xa5, 0x8c, 0xa2, 0x02, 0x07, 0x56, 0xd7, 0x02, 0xdc, 0x71, - 0x29, 0x7d, 0xf9, 0xf4, 0xe5, 0xd3, 0x37, 0x36, 0x71, 0xbd, 0x9e, 0xe5, 0x04, 0x4a, 0xab, 0x35, - 0x81, 0x1a, 0x5d, 0xcb, 0x36, 0x26, 0x8b, 0x57, 0xb5, 0x40, 0x7e, 0x6e, 0x30, 0xb3, 0x87, 0x89, - 0x49, 0x2c, 0x87, 0x09, 0x44, 0xd8, 0xb3, 0x69, 0x87, 0x60, 0xd2, 0xb5, 0x3c, 0xe6, 0x86, 0xd7, - 0xa1, 0x00, 0x25, 0x9e, 0xe9, 0xd2, 0x37, 0x42, 0xb6, 0x1a, 0xc8, 0x06, 0xc3, 0x3e, 0xb3, 0x4e, - 0xad, 0xae, 0x6d, 0xb0, 0xa1, 0x4b, 0x02, 0x4c, 0xff, 0xa3, 0x0c, 0xc5, 0xb3, 0x49, 0xe8, 0x48, - 0x83, 0x4b, 0xcf, 0x89, 0xeb, 0x59, 0xd4, 0xd6, 0x94, 0x86, 0xd2, 0x2c, 0xe3, 0xd1, 0x5f, 0x84, - 0x40, 0x3d, 0x3a, 0xd4, 0xd4, 0x86, 0xd2, 0xcc, 0xb7, 0xd4, 0xff, 0x29, 0x58, 0x3d, 0x3a, 0x44, - 0x6b, 0x70, 0xa9, 0xd5, 0xa7, 0xe6, 0xab, 0xa3, 0x43, 0x2d, 0x3f, 0x06, 0x46, 0x22, 0x54, 0x83, - 0xf9, 0x36, 0xb1, 0xba, 0x3d, 0xa6, 0xcd, 0x71, 0x53, 0xe2, 0x1f, 0xda, 0x87, 0xea, 0x29, 0xb1, - 0x3b, 0xc4, 0x3d, 0x30, 0x4d, 0x3a, 0xb4, 0xd9, 0x41, 0xa7, 0xe3, 0x12, 0xcf, 0xd3, 0x0a, 0x0d, - 0xa5, 0xb9, 0x88, 0x63, 0x31, 0x74, 0x1f, 0xea, 0x98, 0x98, 0x96, 0x63, 0x11, 0x9b, 0x45, 0x96, - 0xcd, 0xf3, 0x65, 0xb3, 0x60, 0xd4, 0x84, 0xcb, 0xa1, 0x04, 0xcf, 0x2e, 0x1c, 0xa2, 0x5d, 0xe2, - 0xe1, 0x44, 0xc5, 0xa8, 0x0a, 0xf9, 0xc7, 0x84, 0x68, 0x0b, 0xe3, 0x4c, 0xfc, 0xbf, 0xa8, 0x01, - 0x8b, 0x67, 0xd6, 0x80, 0x78, 0xcc, 0x18, 0x38, 0xda, 0xe2, 0x18, 0x9b, 0x08, 0x23, 0x1e, 0xda, - 0x86, 0xd7, 0xd3, 0xa0, 0xa1, 0x34, 0x4b, 0x38, 0x2a, 0x46, 0x77, 0x61, 0x25, 0x24, 0x6a, 0xd1, - 0xce, 0xc5, 0x13, 0x62, 0x77, 0x59, 0x4f, 0x2b, 0xf2, 0x88, 0xe2, 0x41, 0x7f, 0xbf, 0x22, 0x40, - 0xeb, 0x82, 0x11, 0x4f, 0x2b, 0x71, 0x27, 0xb1, 0x18, 0xda, 0x81, 0x4a, 0x48, 0x7e, 0x64, 0x77, - 0xc8, 0x17, 0x5a, 0x99, 0x3b, 0x99, 0x92, 0xa3, 0x4f, 0xa1, 0x4a, 0x06, 0x0e, 0xbb, 0x88, 0x18, - 0xd2, 0x96, 0x1a, 0x4a, 0xb3, 0xb8, 0x7f, 0x6d, 0x97, 0x97, 0xcf, 0xee, 0xa3, 0x18, 0x95, 0x76, - 0x0e, 0xc7, 0x2e, 0x45, 0x9f, 0x81, 0xe6, 0x11, 0xbb, 0x73, 0x4c, 0x6d, 0x32, 0x65, 0xf6, 0x32, - 0x37, 0xbb, 0x21, 0xcc, 0x9e, 0xce, 0x50, 0x6b, 0xe7, 0xf0, 0x4c, 0x13, 0xc8, 0x85, 0x8d, 0x68, - 0xfd, 0x47, 0xbd, 0x54, 0xb8, 0x97, 0x6d, 0xe1, 0xe5, 0x93, 0x64, 0xed, 0x76, 0x0e, 0xa7, 0x19, - 0x44, 0xdf, 0x29, 0xb0, 0x35, 0x74, 0x3a, 0x06, 0x23, 0x29, 0xc6, 0xb4, 0x65, 0xee, 0xfa, 0x96, - 0x70, 0xfd, 0x2c, 0xcb, 0x9a, 0x76, 0x0e, 0x67, 0x33, 0xce, 0xc3, 0x70, 0xc9, 0x80, 0xbe, 0x4e, - 0x0d, 0x03, 0x49, 0x61, 0xe0, 0x2c, 0x6b, 0xfc, 0x30, 0x32, 0x19, 0x47, 0xdf, 0x28, 0xb0, 0x69, - 0xf6, 0x0d, 0x6b, 0x90, 0x16, 0xc5, 0x15, 0x1e, 0xc5, 0x4d, 0x11, 0xc5, 0xc3, 0x0c, 0x4b, 0xda, - 0x39, 0x9c, 0xc9, 0x34, 0x7a, 0x0b, 0xba, 0x47, 0xd8, 0xd0, 0x11, 0x1f, 0xfc, 0xa1, 0xc1, 0x0c, - 0x8f, 0xb0, 0x68, 0x00, 0x55, 0x1e, 0xc0, 0x7f, 0xc7, 0xe5, 0x96, 0xb6, 0xa0, 0x9d, 0xc3, 0x19, - 0xcc, 0xa2, 0xaf, 0xe1, 0x46, 0xb0, 0x53, 0xc9, 0xde, 0x57, 0xb8, 0xf7, 0x1d, 0xe9, 0x10, 0xd2, - 0xdc, 0x67, 0x31, 0x8c, 0xfa, 0xb0, 0x6e, 0x38, 0x8e, 0x4b, 0x5f, 0x1b, 0xfd, 0x47, 0x9c, 0xec, - 0xa3, 0x9e, 0x6b, 0xdc, 0xf3, 0xa6, 0xf0, 0x7c, 0x90, 0xa4, 0xdb, 0xce, 0xe1, 0x64, 0x63, 0xbe, - 0x37, 0xf9, 0xfa, 0x88, 0x7a, 0xab, 0x4b, 0xde, 0x8e, 0x93, 0x74, 0x7d, 0x6f, 0x89, 0xc6, 0xd0, - 0x1a, 0x2c, 0x8e, 0x31, 0x4d, 0xe3, 0x2c, 0x37, 0x11, 0xa0, 0x2d, 0x98, 0x0f, 0x82, 0xd4, 0xae, - 0x72, 0xa7, 0xe5, 0x11, 0x41, 0x71, 0x21, 0x16, 0x60, 0x6b, 0x59, 0x62, 0x65, 0xdf, 0xae, 0x5e, - 0x83, 0x6a, 0x1c, 0x8b, 0xe9, 0xf7, 0x40, 0x9b, 0x45, 0x43, 0x68, 0x15, 0xe6, 0x0f, 0x06, 0xfe, - 0x39, 0xf0, 0xfb, 0x30, 0xe0, 0x7e, 0x21, 0xd1, 0xff, 0x54, 0x60, 0x23, 0xad, 0x48, 0x37, 0xa1, - 0xec, 0xab, 0x9c, 0x0c, 0xcf, 0xfb, 0x96, 0xf9, 0x31, 0xb9, 0xe0, 0x66, 0x4a, 0x58, 0x16, 0xa2, - 0x6d, 0x58, 0x8a, 0xdc, 0x6a, 0x2a, 0xbf, 0xd5, 0x22, 0x52, 0x74, 0x17, 0x8a, 0xfe, 0xc2, 0x91, - 0x52, 0x9e, 0x6f, 0x00, 0x0a, 0x91, 0x9c, 0x40, 0x70, 0x58, 0x0d, 0x35, 0xa1, 0xfc, 0x84, 0x9a, - 0xaf, 0x48, 0xa7, 0x65, 0xf4, 0x0d, 0xdb, 0x24, 0xfc, 0x3e, 0x0e, 0x52, 0x91, 0x01, 0x74, 0x1b, - 0x0a, 0x27, 0x94, 0xbe, 0xb1, 0xf9, 0x5d, 0x5c, 0xdc, 0xaf, 0x0b, 0xcb, 0x27, 0x91, 0xe6, 0x05, - 0x07, 0x5a, 0xfa, 0x2f, 0x0a, 0x6c, 0x65, 0xe2, 0xb7, 0x8c, 0xdb, 0x10, 0x49, 0x4f, 0x7d, 0xcf, - 0xf4, 0xf2, 0xa9, 0xe9, 0xcd, 0x65, 0x4a, 0xef, 0x18, 0xb6, 0x32, 0xd1, 0x66, 0xb6, 0xec, 0xf4, - 0xb7, 0xb0, 0x99, 0x85, 0xff, 0x32, 0xee, 0xd5, 0x38, 0x17, 0x35, 0x53, 0x2e, 0xbf, 0x2b, 0xa0, - 0xa7, 0x93, 0x5f, 0xd0, 0x9b, 0x31, 0x36, 0xd5, 0x9b, 0x29, 0xa3, 0xde, 0x6c, 0x1a, 0x4b, 0xea, - 0xcd, 0xd4, 0xe4, 0xde, 0x6c, 0x15, 0x16, 0x4e, 0x5c, 0xea, 0x10, 0x97, 0x5d, 0xf0, 0x43, 0x5b, - 0xc4, 0xe3, 0xff, 0xa8, 0x0a, 0x85, 0xe7, 0x46, 0x7f, 0x18, 0x14, 0xeb, 0x22, 0x0e, 0xfe, 0xa0, - 0xeb, 0xb0, 0x70, 0x3c, 0x34, 0x7b, 0x7e, 0xf3, 0xc5, 0x6b, 0x74, 0x8e, 0x1f, 0xf3, 0x58, 0xa6, - 0xff, 0xac, 0xc0, 0x8d, 0x0c, 0x2c, 0xfb, 0x6f, 0xcf, 0x53, 0xff, 0x0a, 0xd6, 0x13, 0x29, 0x1b, - 0xdd, 0x81, 0x85, 0x91, 0x02, 0x0f, 0x7a, 0x69, 0x7f, 0x45, 0xe2, 0xc1, 0x11, 0x88, 0xc7, 0x6a, - 0xfe, 0x77, 0x12, 0xee, 0xfd, 0xc2, 0xcd, 0xbc, 0x0c, 0xe8, 0xbf, 0x29, 0xb0, 0x9e, 0xc8, 0xe1, - 0xe8, 0x08, 0x90, 0xac, 0x70, 0x64, 0xbf, 0xa4, 0x3c, 0x90, 0xe2, 0xfe, 0xd5, 0xd8, 0x5b, 0xc0, - 0x57, 0xc0, 0x31, 0x8b, 0xd0, 0x03, 0xd0, 0x9e, 0xd9, 0x9e, 0xd5, 0xb5, 0x49, 0x27, 0xec, 0x85, - 0xb7, 0xb8, 0x2a, 0xaf, 0xfc, 0x99, 0x38, 0x7a, 0x00, 0x65, 0x39, 0x82, 0x80, 0x11, 0xab, 0xa3, - 0xdb, 0x5e, 0x72, 0x2e, 0xab, 0xea, 0x37, 0x61, 0xe5, 0x23, 0xa9, 0x30, 0x30, 0xf9, 0x7c, 0x48, - 0x3c, 0x26, 0x26, 0x1d, 0x25, 0x3c, 0xe9, 0xe8, 0x3f, 0xa8, 0x50, 0x93, 0xb5, 0xbd, 0x91, 0xfa, - 0x34, 0x77, 0x2b, 0xb1, 0xdc, 0x3d, 0x19, 0x87, 0x54, 0x69, 0x1c, 0xda, 0x81, 0xa5, 0xf1, 0x2c, - 0x71, 0xca, 0x0c, 0x97, 0x85, 0xf8, 0x2b, 0x82, 0xa0, 0x6d, 0x28, 0x8d, 0x25, 0x8f, 0xec, 0x4e, - 0x88, 0xc8, 0x25, 0x79, 0xdc, 0xd0, 0x53, 0x88, 0x1f, 0x7a, 0xee, 0x00, 0x9c, 0x8c, 0x87, 0x4f, - 0x3e, 0x4b, 0x15, 0xf7, 0x97, 0x47, 0x5c, 0x32, 0x06, 0x70, 0x48, 0x49, 0x7f, 0x05, 0xf5, 0xa9, - 0xad, 0xf0, 0x1c, 0x6a, 0x7b, 0x04, 0x69, 0x50, 0x38, 0xa3, 0x4c, 0x94, 0x64, 0xf0, 0x6d, 0x06, - 0x02, 0x74, 0x0f, 0x4a, 0xe1, 0x15, 0x9a, 0xda, 0xc8, 0x87, 0xb8, 0x3d, 0x7c, 0x0a, 0x92, 0x9e, - 0x7e, 0x08, 0xb5, 0x13, 0xea, 0xc5, 0x1d, 0x93, 0x3c, 0xe2, 0x04, 0xf5, 0x12, 0x30, 0xe5, 0x94, - 0x5c, 0x7f, 0x0a, 0xf5, 0x29, 0x2b, 0x22, 0xe4, 0xbb, 0xd2, 0x00, 0x2c, 0x4a, 0x38, 0x2e, 0xae, - 0xb0, 0x9a, 0xfe, 0xa3, 0x02, 0x35, 0xbf, 0x67, 0xf8, 0x67, 0x71, 0xf9, 0x9d, 0xce, 0xc3, 0x9e, - 0x61, 0x05, 0x27, 0xe4, 0x97, 0x45, 0x01, 0x4f, 0x04, 0xfe, 0x29, 0x06, 0xc3, 0xf0, 0xe4, 0x2a, - 0xc8, 0x07, 0x83, 0x65, 0x44, 0xac, 0x63, 0xa8, 0x4f, 0x45, 0x23, 0xf2, 0xfb, 0x00, 0x4a, 0xad, - 0xd0, 0x2b, 0x82, 0x48, 0xf0, 0x8a, 0x48, 0x30, 0x0c, 0x61, 0x49, 0x51, 0xff, 0x5e, 0x81, 0x0d, - 0x91, 0x13, 0x9f, 0xe8, 0x67, 0xd4, 0xbe, 0xc4, 0x1c, 0x7e, 0xa6, 0xf9, 0x66, 0x1e, 0x47, 0xa4, - 0x29, 0x79, 0x26, 0x3e, 0x23, 0xe8, 0x3f, 0x29, 0xb0, 0xe6, 0x27, 0x37, 0x33, 0x08, 0xc9, 0xb8, - 0x12, 0x35, 0x7e, 0x0b, 0x96, 0xc3, 0x8b, 0x46, 0xbc, 0x92, 0x6f, 0x96, 0xf0, 0x34, 0xf0, 0x37, - 0xb6, 0xfc, 0x05, 0xac, 0xcf, 0x88, 0x4a, 0x6c, 0xfc, 0x87, 0x50, 0x0e, 0xef, 0x67, 0xb0, 0x35, - 0x33, 0x76, 0x5e, 0xd6, 0xd4, 0x8f, 0x61, 0x43, 0xfe, 0xc2, 0x8e, 0x2d, 0xdb, 0x1a, 0x0c, 0x07, - 0x8f, 0x09, 0x79, 0x9f, 0xea, 0xbf, 0x0f, 0x8d, 0xd9, 0xe6, 0x44, 0xb4, 0xe2, 0xf1, 0x43, 0x91, - 0x1e, 0x3f, 0x76, 0x7e, 0x55, 0x21, 0xe6, 0x99, 0xa4, 0x12, 0xed, 0xa2, 0x2b, 0x39, 0xa4, 0x05, - 0x8f, 0x3a, 0xd1, 0x1e, 0xba, 0xa2, 0xa0, 0x0d, 0xb8, 0x96, 0xd0, 0xf1, 0x54, 0x54, 0xb4, 0x0d, - 0xff, 0x49, 0x6d, 0x22, 0x2b, 0xef, 0xb8, 0x5e, 0x6a, 0x3b, 0x56, 0x79, 0x37, 0x87, 0xb6, 0xa0, - 0x91, 0xd6, 0x67, 0x55, 0xde, 0xcd, 0x23, 0x1d, 0xae, 0x27, 0x37, 0x44, 0x95, 0x3c, 0xda, 0xf4, - 0x3f, 0x81, 0xc4, 0x6e, 0xa2, 0xf2, 0xad, 0x8a, 0xd6, 0xe1, 0xea, 0xcc, 0xcb, 0xba, 0x32, 0xe7, - 0xc3, 0x33, 0x2f, 0xd3, 0x4a, 0xa1, 0xb5, 0xf3, 0xa2, 0xd9, 0xb5, 0x58, 0x6f, 0x78, 0xbe, 0x6b, - 0xd2, 0xc1, 0xde, 0x97, 0x94, 0x9e, 0x9b, 0xc1, 0xef, 0x6d, 0x93, 0xba, 0x64, 0xcf, 0xa4, 0x83, - 0x01, 0xb5, 0xf7, 0x78, 0xd1, 0x9c, 0xcf, 0xf3, 0x57, 0xbb, 0xff, 0xff, 0x15, 0x00, 0x00, 0xff, - 0xff, 0x5e, 0x24, 0x3a, 0xdd, 0x75, 0x14, 0x00, 0x00, + // 1468 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0xcd, 0x6e, 0xdb, 0xc6, + 0x16, 0x16, 0x29, 0xcb, 0xb1, 0x8f, 0x24, 0x47, 0x9e, 0xc8, 0x12, 0xa3, 0xd8, 0xb1, 0x2e, 0x63, + 0x1b, 0xba, 0x4e, 0x62, 0xdf, 0xf8, 0x06, 0x69, 0x9a, 0x9d, 0x15, 0x27, 0x95, 0xd1, 0xa8, 0x71, + 0xc7, 0x4e, 0x16, 0x01, 0xba, 0xa0, 0xa9, 0x89, 0x44, 0x44, 0xe2, 0xb0, 0xe4, 0x28, 0xa9, 0x9b, + 0xa2, 0x40, 0xd3, 0x76, 0x51, 0xa0, 0x8b, 0x2e, 0xfa, 0x4a, 0x7d, 0x8b, 0x6e, 0x5a, 0xf4, 0x39, + 0x8a, 0x82, 0xc3, 0x91, 0xc4, 0xa1, 0x28, 0x8a, 0x4d, 0x37, 0xdd, 0x08, 0xd0, 0xf9, 0xce, 0xef, + 0xcc, 0xe1, 0xf9, 0x19, 0xa8, 0x0e, 0x68, 0x87, 0xf4, 0xf7, 0x99, 0x6b, 0xd8, 0x9e, 0x61, 0x32, + 0x8b, 0xda, 0x7b, 0x8e, 0x4b, 0x19, 0x45, 0x39, 0x0e, 0xd4, 0xd6, 0x03, 0xdc, 0x71, 0x29, 0x7d, + 0xf9, 0xf4, 0xe5, 0xd3, 0x37, 0x36, 0x71, 0xbd, 0x9e, 0xe5, 0x04, 0x4c, 0xb5, 0x8a, 0x40, 0x8d, + 0xae, 0x65, 0x1b, 0x13, 0xe1, 0x9a, 0x16, 0xd0, 0xcf, 0x0d, 0x66, 0xf6, 0x30, 0x31, 0x89, 0xe5, + 0x30, 0x81, 0x08, 0x7d, 0x36, 0xed, 0x10, 0x4c, 0xba, 0x96, 0xc7, 0xdc, 0xb0, 0x1c, 0x0a, 0x50, + 0xe2, 0x99, 0x2e, 0x7d, 0x23, 0x68, 0xb5, 0x80, 0x36, 0x18, 0xf6, 0x99, 0x75, 0x6a, 0x75, 0x6d, + 0x83, 0x0d, 0x5d, 0x12, 0x60, 0xfa, 0x4f, 0x2b, 0x90, 0x3f, 0x9b, 0xb8, 0x8e, 0x34, 0xb8, 0xf4, + 0x9c, 0xb8, 0x9e, 0x45, 0x6d, 0x4d, 0xa9, 0x2b, 0x8d, 0x22, 0x1e, 0xfd, 0x45, 0x08, 0xd4, 0xe3, + 0x23, 0x4d, 0xad, 0x2b, 0x8d, 0x6c, 0x53, 0xfd, 0x9f, 0x82, 0xd5, 0xe3, 0x23, 0xb4, 0x0e, 0x97, + 0x9a, 0x7d, 0x6a, 0xbe, 0x3a, 0x3e, 0xd2, 0xb2, 0x63, 0x60, 0x44, 0x42, 0x15, 0x58, 0x6c, 0x11, + 0xab, 0xdb, 0x63, 0xda, 0x02, 0x57, 0x25, 0xfe, 0xa1, 0x03, 0x28, 0x9f, 0x12, 0xbb, 0x43, 0xdc, + 0x43, 0xd3, 0xa4, 0x43, 0x9b, 0x1d, 0x76, 0x3a, 0x2e, 0xf1, 0x3c, 0x2d, 0x57, 0x57, 0x1a, 0xcb, + 0x38, 0x16, 0x43, 0xf7, 0xa1, 0x8a, 0x89, 0x69, 0x39, 0x16, 0xb1, 0x59, 0x44, 0x6c, 0x91, 0x8b, + 0xcd, 0x82, 0x51, 0x03, 0x2e, 0x87, 0x02, 0x3c, 0xbb, 0x70, 0x88, 0x76, 0x89, 0xbb, 0x13, 0x25, + 0xa3, 0x32, 0x64, 0x1f, 0x13, 0xa2, 0x2d, 0x8d, 0x23, 0xf1, 0xff, 0xa2, 0x3a, 0x2c, 0x9f, 0x59, + 0x03, 0xe2, 0x31, 0x63, 0xe0, 0x68, 0xcb, 0x63, 0x6c, 0x42, 0x8c, 0x58, 0x68, 0x19, 0x5e, 0x4f, + 0x83, 0xba, 0xd2, 0x28, 0xe0, 0x28, 0x19, 0xdd, 0x85, 0xb5, 0x10, 0xa9, 0x49, 0x3b, 0x17, 0x4f, + 0x88, 0xdd, 0x65, 0x3d, 0x2d, 0xcf, 0x3d, 0x8a, 0x07, 0xfd, 0xf3, 0x8a, 0x00, 0xcd, 0x0b, 0x46, + 0x3c, 0xad, 0xc0, 0x8d, 0xc4, 0x62, 0x68, 0x17, 0x4a, 0x21, 0xfa, 0xb1, 0xdd, 0x21, 0x5f, 0x68, + 0x45, 0x6e, 0x64, 0x8a, 0x8e, 0xb6, 0xa0, 0xd8, 0xf6, 0x73, 0xc3, 0xb3, 0xba, 0x0f, 0x7b, 0x56, + 0xbf, 0xa3, 0xad, 0xd4, 0x95, 0xc6, 0x12, 0x96, 0x89, 0xe8, 0x53, 0x28, 0x93, 0x81, 0xc3, 0x2e, + 0x22, 0xe6, 0xb4, 0xcb, 0x75, 0xa5, 0x91, 0x3f, 0xb8, 0xb6, 0xc7, 0x93, 0x6c, 0xef, 0x51, 0x0c, + 0x4b, 0x2b, 0x83, 0x63, 0x45, 0xd1, 0x67, 0xa0, 0x79, 0xc4, 0xee, 0xb4, 0xa9, 0x4d, 0xa6, 0xd4, + 0x96, 0xb8, 0xda, 0x4d, 0xa1, 0xf6, 0x74, 0x06, 0x5b, 0x2b, 0x83, 0x67, 0xaa, 0x40, 0x2e, 0x6c, + 0x46, 0xbf, 0x92, 0xa8, 0x95, 0x55, 0x6e, 0x65, 0x47, 0x58, 0xf9, 0x24, 0x99, 0xbb, 0x95, 0xc1, + 0xf3, 0x14, 0xa2, 0xef, 0x14, 0xd8, 0x1e, 0x3a, 0x1d, 0x83, 0x91, 0x39, 0xca, 0x34, 0xc4, 0x4d, + 0xdf, 0x12, 0xa6, 0x9f, 0xa5, 0x91, 0x69, 0x65, 0x70, 0x3a, 0xe5, 0xdc, 0x0d, 0x97, 0x0c, 0xe8, + 0xeb, 0xb9, 0x6e, 0x5c, 0x91, 0xdc, 0xc0, 0x69, 0x64, 0x7c, 0x37, 0x52, 0x29, 0x47, 0xdf, 0x28, + 0xb0, 0x65, 0xf6, 0x0d, 0x6b, 0x30, 0xcf, 0x8b, 0x32, 0xf7, 0xe2, 0xa6, 0xf0, 0xe2, 0x61, 0x0a, + 0x91, 0x56, 0x06, 0xa7, 0x52, 0x8d, 0xde, 0x82, 0xee, 0x11, 0x36, 0x74, 0x44, 0x59, 0x38, 0x32, + 0x98, 0xe1, 0x11, 0x16, 0x75, 0x60, 0x8d, 0x3b, 0xf0, 0xdf, 0x71, 0xba, 0xcd, 0x13, 0x68, 0x65, + 0x70, 0x0a, 0xb5, 0xe8, 0x6b, 0xb8, 0x11, 0x9c, 0x54, 0xb2, 0xf5, 0x0a, 0xb7, 0xbe, 0x2b, 0x5d, + 0xc2, 0x3c, 0xf3, 0x69, 0x14, 0xa3, 0x3e, 0x6c, 0x18, 0x8e, 0xe3, 0xd2, 0xd7, 0x46, 0xff, 0x11, + 0x6f, 0x09, 0x51, 0xcb, 0x55, 0x6e, 0x79, 0x4b, 0x58, 0x3e, 0x4c, 0xe2, 0x6d, 0x65, 0x70, 0xb2, + 0x32, 0xdf, 0x9a, 0xdc, 0x64, 0xa2, 0xd6, 0x34, 0xc9, 0x5a, 0x3b, 0x89, 0xd7, 0xb7, 0x96, 0xa8, + 0x0c, 0xad, 0xc3, 0xf2, 0x18, 0xd3, 0xae, 0xf2, 0x5a, 0x38, 0x21, 0xa0, 0x6d, 0x58, 0x0c, 0x9c, + 0xd4, 0x6a, 0xdc, 0x68, 0x71, 0x54, 0xa0, 0x38, 0x11, 0x0b, 0xb0, 0xb9, 0x2a, 0xd5, 0x6e, 0x5f, + 0xaf, 0x5e, 0x81, 0x72, 0x5c, 0x15, 0xd3, 0xef, 0x81, 0x36, 0xab, 0x0c, 0xa1, 0x1a, 0x2c, 0x1e, + 0x0e, 0xfc, 0x7b, 0xe0, 0x5d, 0x33, 0xe8, 0x10, 0x82, 0xa2, 0xff, 0xa9, 0xc0, 0xe6, 0xbc, 0x24, + 0xdd, 0x82, 0xa2, 0xcf, 0x72, 0x32, 0x3c, 0xef, 0x5b, 0xe6, 0xc7, 0xe4, 0x82, 0xab, 0x29, 0x60, + 0x99, 0x88, 0x76, 0x60, 0x25, 0xd2, 0xfb, 0x54, 0xde, 0xfb, 0x22, 0x54, 0x74, 0x17, 0xf2, 0xbe, + 0xe0, 0x88, 0x29, 0xcb, 0x0f, 0x00, 0x85, 0x8a, 0x9c, 0x40, 0x70, 0x98, 0x0d, 0x35, 0xa0, 0xf8, + 0x84, 0x9a, 0xaf, 0x48, 0xa7, 0x69, 0xf4, 0x0d, 0xdb, 0x24, 0xbc, 0x6b, 0x07, 0xa1, 0xc8, 0x00, + 0xba, 0x0d, 0xb9, 0x13, 0x4a, 0xdf, 0xd8, 0xbc, 0x63, 0xe7, 0x0f, 0xaa, 0x42, 0xf3, 0x49, 0x64, + 0xc4, 0xc1, 0x01, 0x97, 0xfe, 0xab, 0x02, 0xdb, 0xa9, 0xea, 0x5b, 0xca, 0x63, 0x88, 0x84, 0xa7, + 0xbe, 0x67, 0x78, 0xd9, 0xb9, 0xe1, 0x2d, 0xa4, 0x0a, 0xaf, 0x0d, 0xdb, 0xa9, 0xca, 0x66, 0xba, + 0xe8, 0xf4, 0xb7, 0xb0, 0x95, 0xa6, 0xfe, 0xa5, 0x3c, 0xab, 0x71, 0x2c, 0x6a, 0xaa, 0x58, 0xfe, + 0x50, 0x40, 0x9f, 0x5f, 0xfc, 0x82, 0x09, 0x8e, 0xb1, 0xa9, 0x09, 0x4e, 0x19, 0x4d, 0x70, 0xd3, + 0x58, 0xd2, 0x04, 0xa7, 0x26, 0x4f, 0x70, 0x35, 0x58, 0x3a, 0x71, 0xa9, 0x43, 0x5c, 0x76, 0xc1, + 0x2f, 0x6d, 0x19, 0x8f, 0xff, 0xa3, 0x32, 0xe4, 0x9e, 0x1b, 0xfd, 0x61, 0x90, 0xac, 0xcb, 0x38, + 0xf8, 0x83, 0xae, 0xc3, 0x52, 0x7b, 0x68, 0xf6, 0xfc, 0x11, 0x8d, 0xe7, 0xe8, 0x02, 0xbf, 0xe6, + 0x31, 0x4d, 0xff, 0x45, 0x81, 0x1b, 0x29, 0xaa, 0xec, 0xbf, 0x3d, 0x4e, 0xfd, 0x2b, 0xd8, 0x48, + 0x2c, 0xd9, 0xe8, 0x0e, 0x2c, 0x8d, 0x18, 0xb8, 0xd3, 0x2b, 0x07, 0x6b, 0x52, 0x1d, 0x1c, 0x81, + 0x78, 0xcc, 0xe6, 0x7f, 0x27, 0xe1, 0x09, 0x31, 0x3c, 0xf2, 0xcb, 0x80, 0xfe, 0xbb, 0x02, 0x1b, + 0x89, 0x35, 0x1c, 0x1d, 0x03, 0x92, 0x19, 0x8e, 0xed, 0x97, 0x94, 0x3b, 0x92, 0x3f, 0xb8, 0x1a, + 0xdb, 0x05, 0x7c, 0x06, 0x1c, 0x23, 0x84, 0x1e, 0x80, 0xf6, 0xcc, 0xf6, 0xac, 0xae, 0x4d, 0x3a, + 0x61, 0x2b, 0x7c, 0x10, 0x56, 0x79, 0xe6, 0xcf, 0xc4, 0xd1, 0x03, 0x28, 0xca, 0x1e, 0x04, 0x15, + 0xb1, 0x3c, 0xea, 0xf6, 0x92, 0x71, 0x99, 0x55, 0xbf, 0x09, 0x6b, 0x1f, 0x49, 0x89, 0x81, 0xc9, + 0xe7, 0x43, 0xe2, 0x31, 0xb1, 0x0f, 0x29, 0xe1, 0x7d, 0x48, 0xff, 0x41, 0x85, 0x8a, 0xcc, 0xed, + 0x8d, 0xd8, 0xa7, 0x6b, 0xb7, 0x12, 0x5b, 0xbb, 0x27, 0x4b, 0x93, 0x2a, 0x2d, 0x4d, 0xbb, 0xb0, + 0x32, 0xde, 0x38, 0x4e, 0x99, 0xe1, 0xb2, 0x50, 0xfd, 0x8a, 0x20, 0x68, 0x07, 0x0a, 0x63, 0xca, + 0x23, 0xbb, 0x13, 0x2a, 0xe4, 0x12, 0x3d, 0x6e, 0x35, 0xca, 0xc5, 0xaf, 0x46, 0x77, 0x00, 0x4e, + 0xc6, 0x2b, 0x2a, 0xdf, 0xb8, 0xf2, 0x07, 0xab, 0xa3, 0x5a, 0x32, 0x06, 0x70, 0x88, 0x49, 0x7f, + 0x05, 0xd5, 0xa9, 0xa3, 0xf0, 0x1c, 0x6a, 0x7b, 0x04, 0x69, 0x90, 0x3b, 0xa3, 0x4c, 0xa4, 0x64, + 0xf0, 0x6d, 0x06, 0x04, 0x74, 0x0f, 0x0a, 0x61, 0x09, 0x4d, 0xad, 0x67, 0x43, 0xb5, 0x3d, 0x7c, + 0x0b, 0x12, 0x9f, 0x7e, 0x04, 0x95, 0x13, 0xea, 0xc5, 0x5d, 0x93, 0xbc, 0x08, 0x05, 0xf9, 0x12, + 0x54, 0xca, 0x29, 0xba, 0xfe, 0x14, 0xaa, 0x53, 0x5a, 0x84, 0xcb, 0x77, 0xa5, 0x35, 0x59, 0xa4, + 0x70, 0x9c, 0x5f, 0x61, 0x36, 0xfd, 0x47, 0x05, 0x2a, 0xfe, 0xcc, 0xf0, 0xcf, 0xfc, 0xf2, 0x27, + 0x9d, 0x87, 0x3d, 0xc3, 0x0a, 0x6e, 0xc8, 0x4f, 0x8b, 0x1c, 0x9e, 0x10, 0xfc, 0x5b, 0x0c, 0x56, + 0xe6, 0x49, 0x2b, 0xc8, 0x06, 0xeb, 0x67, 0x84, 0xac, 0x63, 0xa8, 0x4e, 0x79, 0x23, 0xe2, 0xfb, + 0x00, 0x0a, 0xcd, 0xd0, 0x5b, 0x83, 0x08, 0xf0, 0x8a, 0x08, 0x30, 0x0c, 0x61, 0x89, 0x51, 0xff, + 0x5e, 0x81, 0x4d, 0x11, 0x13, 0xdf, 0xfb, 0x67, 0xe4, 0xbe, 0x54, 0x39, 0xfc, 0x48, 0xb3, 0x8d, + 0x2c, 0x8e, 0x50, 0xe7, 0xc4, 0x99, 0xf8, 0xd8, 0xa0, 0xff, 0xac, 0xc0, 0xba, 0x1f, 0xdc, 0x4c, + 0x27, 0x24, 0xe5, 0x4a, 0x54, 0xf9, 0x2d, 0x58, 0x0d, 0x0b, 0x8d, 0xea, 0x4a, 0xb6, 0x51, 0xc0, + 0xd3, 0xc0, 0xdf, 0x38, 0xf2, 0x17, 0xb0, 0x31, 0xc3, 0x2b, 0x71, 0xf0, 0x1f, 0x42, 0x31, 0x7c, + 0x9e, 0xc1, 0xd1, 0xcc, 0x38, 0x79, 0x99, 0x53, 0x6f, 0xc3, 0xa6, 0xfc, 0x85, 0xb5, 0x2d, 0xdb, + 0x1a, 0x0c, 0x07, 0x8f, 0x09, 0x79, 0x9f, 0xec, 0xbf, 0x0f, 0xf5, 0xd9, 0xea, 0x84, 0xb7, 0xe2, + 0x89, 0x44, 0x91, 0x9e, 0x48, 0x76, 0x7f, 0x53, 0x21, 0xe6, 0x31, 0xa5, 0x14, 0x9d, 0xa2, 0x4b, + 0x19, 0xa4, 0x05, 0x4f, 0x3f, 0xd1, 0x19, 0xba, 0xa4, 0xa0, 0x4d, 0xb8, 0x96, 0x30, 0xf1, 0x94, + 0x54, 0xb4, 0x03, 0xff, 0x99, 0x3b, 0x44, 0x96, 0xde, 0x71, 0xbe, 0xb9, 0xe3, 0x58, 0xe9, 0xdd, + 0x02, 0xda, 0x86, 0xfa, 0xbc, 0x39, 0xab, 0xf4, 0x6e, 0x11, 0xe9, 0x70, 0x3d, 0x79, 0x20, 0x2a, + 0x65, 0xd1, 0x96, 0xff, 0x09, 0x24, 0x4e, 0x13, 0xa5, 0x6f, 0x55, 0xb4, 0x01, 0x57, 0x67, 0x36, + 0xeb, 0xd2, 0x82, 0x0f, 0xcf, 0x6c, 0xa6, 0xa5, 0x5c, 0x73, 0xf7, 0x45, 0xa3, 0x6b, 0xb1, 0xde, + 0xf0, 0x7c, 0xcf, 0xa4, 0x83, 0xfd, 0x2f, 0x29, 0x3d, 0x37, 0x83, 0xdf, 0xdb, 0x26, 0x75, 0xc9, + 0xbe, 0x49, 0x07, 0x03, 0x6a, 0xef, 0xf3, 0xa4, 0x39, 0x5f, 0xe4, 0x6f, 0x7b, 0xff, 0xff, 0x2b, + 0x00, 0x00, 0xff, 0xff, 0x7b, 0xa6, 0x4b, 0xbd, 0x9b, 0x14, 0x00, 0x00, } diff --git a/common/query/multisignatureInfoQuery.go b/common/query/multisignatureInfoQuery.go index 0ae6664fa..84605cb47 100644 --- a/common/query/multisignatureInfoQuery.go +++ b/common/query/multisignatureInfoQuery.go @@ -10,8 +10,11 @@ import ( type ( MultisignatureInfoQueryInterface interface { - GetMultisignatureInfoByAddress(multisigAddress string) (str string, args []interface{}) - InsertMultisignatureInfo(multisigInfo *model.MultiSignatureInfo) (str string, args []interface{}) + GetMultisignatureInfoByAddress( + multisigAddress string, + currentHeight, limit uint32, + ) (str string, args []interface{}) + InsertMultisignatureInfo(multisigInfo *model.MultiSignatureInfo) [][]interface{} Scan(multisigInfo *model.MultiSignatureInfo, row *sql.Row) error ExtractModel(multisigInfo *model.MultiSignatureInfo) []interface{} BuildModel(multisigInfos []*model.MultiSignatureInfo, rows *sql.Rows) ([]*model.MultiSignatureInfo, error) @@ -32,6 +35,7 @@ func NewMultisignatureInfoQuery() *MultisignatureInfoQuery { "nonce", "addresses", "block_height", + "latest", }, TableName: "multisignature_info", } @@ -41,21 +45,44 @@ func (msi *MultisignatureInfoQuery) getTableName() string { return msi.TableName } -func (msi *MultisignatureInfoQuery) GetMultisignatureInfoByAddress(multisigAddress string) (str string, args []interface{}) { - query := fmt.Sprintf("SELECT %s FROM %s WHERE multisig_address = ?", strings.Join(msi.Fields, ", "), msi.getTableName()) +func (msi *MultisignatureInfoQuery) GetMultisignatureInfoByAddress( + multisigAddress string, + currentHeight, limit uint32, +) (str string, args []interface{}) { + var ( + blockHeight uint32 + ) + if currentHeight > limit { + blockHeight = currentHeight - limit + } + query := fmt.Sprintf("SELECT %s FROM %s WHERE multisig_address = ? AND block_height >= ? AND latest = true", + strings.Join(msi.Fields, ", "), msi.getTableName()) return query, []interface{}{ multisigAddress, + blockHeight, } } // InsertPendingSignature inserts a new pending transaction into DB -func (msi *MultisignatureInfoQuery) InsertMultisignatureInfo(multisigInfo *model.MultiSignatureInfo) (str string, args []interface{}) { - return fmt.Sprintf( - "INSERT INTO %s (%s) VALUES(%s)", +func (msi *MultisignatureInfoQuery) InsertMultisignatureInfo(multisigInfo *model.MultiSignatureInfo) [][]interface{} { + var queries [][]interface{} + insertQuery := fmt.Sprintf("INSERT OR REPLACE INTO %s (%s) VALUES(%s)", msi.getTableName(), strings.Join(msi.Fields, ", "), fmt.Sprintf("? %s", strings.Repeat(", ? ", len(msi.Fields)-1)), - ), msi.ExtractModel(multisigInfo) + ) + updateQuery := fmt.Sprintf("UPDATE %s SET latest = false WHERE multisig_address = ? "+ + "AND block_height != %d AND latest = true", + msi.getTableName(), + multisigInfo.BlockHeight, + ) + queries = append(queries, + append([]interface{}{insertQuery}, msi.ExtractModel(multisigInfo)...), + []interface{}{ + updateQuery, multisigInfo.MultisigAddress, + }, + ) + return queries } func (*MultisignatureInfoQuery) Scan(multisigInfo *model.MultiSignatureInfo, row *sql.Row) error { @@ -66,6 +93,7 @@ func (*MultisignatureInfoQuery) Scan(multisigInfo *model.MultiSignatureInfo, row &multisigInfo.Nonce, &addresses, &multisigInfo.BlockHeight, + &multisigInfo.Latest, ) multisigInfo.Addresses = strings.Split(addresses, ", ") return err @@ -79,6 +107,7 @@ func (*MultisignatureInfoQuery) ExtractModel(multisigInfo *model.MultiSignatureI &multisigInfo.Nonce, addresses, &multisigInfo.BlockHeight, + &multisigInfo.Latest, } } @@ -96,6 +125,7 @@ func (msi *MultisignatureInfoQuery) BuildModel( &multisigInfo.Nonce, &addresses, &multisigInfo.BlockHeight, + &multisigInfo.Latest, ) multisigInfo.Addresses = strings.Split(addresses, ", ") if err != nil { @@ -113,5 +143,14 @@ func (msi *MultisignatureInfoQuery) Rollback(height uint32) (multiQueries [][]in fmt.Sprintf("DELETE FROM %s WHERE block_height > ?", msi.getTableName()), height, }, + { + fmt.Sprintf("UPDATE %s SET latest = ? WHERE latest = ? AND (block_height || '_' || "+ + "multisig_address) IN (SELECT (MAX(block_height) || '_' || multisig_address) as con "+ + "FROM %s GROUP BY multisig_address)", + msi.getTableName(), + msi.getTableName(), + ), + 1, 0, + }, } } diff --git a/common/query/multisignatureInfoQuery_test.go b/common/query/multisignatureInfoQuery_test.go index 4ced18a9e..88870a0fe 100644 --- a/common/query/multisignatureInfoQuery_test.go +++ b/common/query/multisignatureInfoQuery_test.go @@ -2,11 +2,12 @@ package query import ( "database/sql" - "fmt" "reflect" "strings" "testing" + "github.com/zoobc/zoobc-core/common/constant" + "github.com/DATA-DOG/go-sqlmock" "github.com/zoobc/zoobc-core/common/model" @@ -37,6 +38,7 @@ func getBuildModelSuccessMockRows() *sql.Rows { int64(10), "addresses", uint32(12), + true, ) mock.ExpectQuery("").WillReturnRows(mockRow) rows, _ := db.Query("") @@ -89,6 +91,7 @@ func TestMultisignatureInfoQuery_BuildModel(t *testing.T) { Nonce: 10, Addresses: []string{"addresses"}, BlockHeight: 12, + Latest: true, }, }, wantErr: false, @@ -120,6 +123,7 @@ var ( Addresses: []string{"A", "B"}, MultisigAddress: "", BlockHeight: 0, + Latest: true, } // Extract mocks ) @@ -153,6 +157,7 @@ func TestMultisignatureInfoQuery_ExtractModel(t *testing.T) { &mockExtractMultisignatureInfoMultisig.Nonce, strings.Join(mockExtractMultisignatureInfoMultisig.Addresses, ", "), &mockExtractMultisignatureInfoMultisig.BlockHeight, + &mockExtractMultisignatureInfoMultisig.Latest, }, }, } @@ -175,7 +180,8 @@ func TestMultisignatureInfoQuery_GetMultisignatureInfoByAddress(t *testing.T) { TableName string } type args struct { - multisigAddress string + multisigAddress string + currentHeight, limit uint32 } tests := []struct { name string @@ -192,10 +198,12 @@ func TestMultisignatureInfoQuery_GetMultisignatureInfoByAddress(t *testing.T) { }, args: args{ multisigAddress: "A", + currentHeight: 0, + limit: constant.MinRollbackBlocks, }, - wantStr: "SELECT multisig_address, minimum_signatures, nonce, addresses, block_height FROM " + - "multisignature_info WHERE multisig_address = ?", - wantArgs: []interface{}{"A"}, + wantStr: "SELECT multisig_address, minimum_signatures, nonce, addresses, block_height, latest FROM " + + "multisignature_info WHERE multisig_address = ? AND block_height >= ? AND latest = true", + wantArgs: []interface{}{"A", uint32(0)}, }, } for _, tt := range tests { @@ -204,7 +212,11 @@ func TestMultisignatureInfoQuery_GetMultisignatureInfoByAddress(t *testing.T) { Fields: tt.fields.Fields, TableName: tt.fields.TableName, } - gotStr, gotArgs := msi.GetMultisignatureInfoByAddress(tt.args.multisigAddress) + gotStr, gotArgs := msi.GetMultisignatureInfoByAddress( + tt.args.multisigAddress, + tt.args.currentHeight, + tt.args.limit, + ) if gotStr != tt.wantStr { t.Errorf("GetMultisignatureInfoByAddress() gotStr = %v, want %v", gotStr, tt.wantStr) } @@ -223,6 +235,7 @@ var ( Addresses: nil, MultisigAddress: "", BlockHeight: 0, + Latest: true, } // InsertMultisignatureInfo mocks ) @@ -236,11 +249,10 @@ func TestMultisignatureInfoQuery_InsertMultisignatureInfo(t *testing.T) { multisigInfo *model.MultiSignatureInfo } tests := []struct { - name string - fields fields - args args - wantStr string - wantArgs []interface{} + name string + fields fields + args args + want [][]interface{} }{ { name: "InsertMultisigInfo-Success", @@ -251,9 +263,16 @@ func TestMultisignatureInfoQuery_InsertMultisignatureInfo(t *testing.T) { args: args{ multisigInfo: mockInsertMultisignatureInfoMultisig, }, - wantStr: "INSERT INTO multisignature_info (multisig_address, minimum_signatures, " + - "nonce, addresses, block_height) VALUES(? , ? , ? , ? , ? )", - wantArgs: mockMultisigInfoQueryInstance.ExtractModel(mockInsertMultisignatureInfoMultisig), + want: [][]interface{}{ + append([]interface{}{ + "INSERT OR REPLACE INTO multisignature_info (multisig_address, minimum_signatures, " + + "nonce, addresses, block_height, latest) VALUES(? , ? , ? , ? , ? , ? )", + }, mockMultisigInfoQueryInstance.ExtractModel( + mockInsertMultisignatureInfoMultisig)...), + { + "UPDATE multisignature_info SET latest = false WHERE multisig_address = ? AND " + + "block_height != 0 AND latest = true", mockInsertMultisignatureInfoMultisig.MultisigAddress, + }}, }, } for _, tt := range tests { @@ -262,12 +281,9 @@ func TestMultisignatureInfoQuery_InsertMultisignatureInfo(t *testing.T) { Fields: tt.fields.Fields, TableName: tt.fields.TableName, } - gotStr, gotArgs := msi.InsertMultisignatureInfo(tt.args.multisigInfo) - if gotStr != tt.wantStr { - t.Errorf("InsertMultisignatureInfo() gotStr = %v, want %v", gotStr, tt.wantStr) - } - if !reflect.DeepEqual(gotArgs, tt.wantArgs) { - t.Errorf("InsertMultisignatureInfo() gotArgs = %v, want %v", gotArgs, tt.wantArgs) + got := msi.InsertMultisignatureInfo(tt.args.multisigInfo) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("InsertMultisignatureInfo() got = %v, want %v", got, tt.want) } }) } @@ -298,9 +314,15 @@ func TestMultisignatureInfoQuery_Rollback(t *testing.T) { }, wantMultiQueries: [][]interface{}{ { - fmt.Sprintf("DELETE FROM %s WHERE block_height > ?", mockMultisigInfoQueryInstance.TableName), + "DELETE FROM multisignature_info WHERE block_height > ?", uint32(10), }, + { + "UPDATE multisignature_info SET latest = ? WHERE latest = ? AND (block_height || '_' || " + + "multisig_address) IN (SELECT (MAX(block_height) || '_' || multisig_address) as con " + + "FROM multisignature_info GROUP BY multisig_address)", + 1, 0, + }, }, }, } @@ -337,6 +359,7 @@ func getNumberScanSuccessMockRow() *sql.Row { int64(10), "addresses", uint32(12), + true, ) mock.ExpectQuery("").WillReturnRows(mockRow) return db.QueryRow("") @@ -438,6 +461,7 @@ var ( "nonce", "addresses", "block_height", + "latest", }, TableName: "multisignature_info", } diff --git a/common/query/pendingSignatureQuery.go b/common/query/pendingSignatureQuery.go index 8aeb90807..4a0a590d1 100644 --- a/common/query/pendingSignatureQuery.go +++ b/common/query/pendingSignatureQuery.go @@ -10,8 +10,11 @@ import ( type ( PendingSignatureQueryInterface interface { - GetPendingSignatureByHash(txHash []byte) (str string, args []interface{}) - InsertPendingSignature(pendingSig *model.PendingSignature) (str string, args []interface{}) + GetPendingSignatureByHash( + txHash []byte, + currentHeight, limit uint32, + ) (str string, args []interface{}) + InsertPendingSignature(pendingSig *model.PendingSignature) [][]interface{} Scan(pendingSig *model.PendingSignature, row *sql.Row) error ExtractModel(pendingSig *model.PendingSignature) []interface{} BuildModel(pendingSigs []*model.PendingSignature, rows *sql.Rows) ([]*model.PendingSignature, error) @@ -31,6 +34,7 @@ func NewPendingSignatureQuery() *PendingSignatureQuery { "account_address", "signature", "block_height", + "latest", }, TableName: "pending_signature", } @@ -40,21 +44,44 @@ func (psq *PendingSignatureQuery) getTableName() string { return psq.TableName } -func (psq *PendingSignatureQuery) GetPendingSignatureByHash(txHash []byte) (str string, args []interface{}) { - query := fmt.Sprintf("SELECT %s FROM %s WHERE transaction_hash = ?", strings.Join(psq.Fields, ", "), psq.getTableName()) +func (psq *PendingSignatureQuery) GetPendingSignatureByHash( + txHash []byte, + currentHeight, limit uint32, +) (str string, args []interface{}) { + var ( + blockHeight uint32 + ) + if currentHeight > limit { + blockHeight = currentHeight - limit + } + query := fmt.Sprintf("SELECT %s FROM %s WHERE transaction_hash = ? AND block_height >= ? AND latest = true", + strings.Join(psq.Fields, ", "), psq.getTableName()) return query, []interface{}{ txHash, + blockHeight, } } // InsertPendingSignature inserts a new pending transaction into DB -func (psq *PendingSignatureQuery) InsertPendingSignature(pendingSig *model.PendingSignature) (str string, args []interface{}) { - return fmt.Sprintf( - "INSERT INTO %s (%s) VALUES(%s)", +func (psq *PendingSignatureQuery) InsertPendingSignature(pendingSig *model.PendingSignature) [][]interface{} { + var queries [][]interface{} + insertQuery := fmt.Sprintf("INSERT OR REPLACE INTO %s (%s) VALUES(%s)", psq.getTableName(), strings.Join(psq.Fields, ", "), fmt.Sprintf("? %s", strings.Repeat(", ? ", len(psq.Fields)-1)), - ), psq.ExtractModel(pendingSig) + ) + updateQuery := fmt.Sprintf("UPDATE %s SET latest = false WHERE account_address = ? AND transaction_hash = ? "+ + "AND block_height != %d AND latest = true", + psq.getTableName(), + pendingSig.BlockHeight, + ) + queries = append(queries, + append([]interface{}{insertQuery}, psq.ExtractModel(pendingSig)...), + []interface{}{ + updateQuery, pendingSig.AccountAddress, pendingSig.TransactionHash, + }, + ) + return queries } func (*PendingSignatureQuery) Scan(pendingSig *model.PendingSignature, row *sql.Row) error { @@ -63,6 +90,7 @@ func (*PendingSignatureQuery) Scan(pendingSig *model.PendingSignature, row *sql. &pendingSig.AccountAddress, &pendingSig.Signature, &pendingSig.BlockHeight, + &pendingSig.Latest, ) return err } @@ -73,6 +101,7 @@ func (*PendingSignatureQuery) ExtractModel(pendingSig *model.PendingSignature) [ &pendingSig.AccountAddress, &pendingSig.Signature, &pendingSig.BlockHeight, + &pendingSig.Latest, } } @@ -86,6 +115,7 @@ func (psq *PendingSignatureQuery) BuildModel( &pendingSig.AccountAddress, &pendingSig.Signature, &pendingSig.BlockHeight, + &pendingSig.Latest, ) if err != nil { return nil, err @@ -99,8 +129,18 @@ func (psq *PendingSignatureQuery) BuildModel( func (psq *PendingSignatureQuery) Rollback(height uint32) (multiQueries [][]interface{}) { return [][]interface{}{ { - fmt.Sprintf("DELETE FROM %s WHERE block_height > ?", psq.getTableName()), + fmt.Sprintf("DELETE FROM %s WHERE height > ?", psq.TableName), height, }, + { + fmt.Sprintf("UPDATE %s SET latest = ? WHERE latest = ? AND (block_height || '_' || "+ + "account_address || '_' || transaction_hash) IN (SELECT (MAX(block_height) || '_' || "+ + "account_address || '_' || transaction_hash) as con FROM %s GROUP BY account_address "+ + "|| '_' || transaction_hash)", + psq.TableName, + psq.TableName, + ), + 1, 0, + }, } } diff --git a/common/query/pendingSignatureQuery_test.go b/common/query/pendingSignatureQuery_test.go index 625acfbdd..d36e729f7 100644 --- a/common/query/pendingSignatureQuery_test.go +++ b/common/query/pendingSignatureQuery_test.go @@ -2,10 +2,11 @@ package query import ( "database/sql" - "fmt" "reflect" "testing" + "github.com/zoobc/zoobc-core/common/constant" + "github.com/DATA-DOG/go-sqlmock" "github.com/zoobc/zoobc-core/common/model" @@ -28,6 +29,7 @@ func TestNewPendingSignatureQuery(t *testing.T) { "account_address", "signature", "block_height", + "latest", }, TableName: "pending_signature", }, @@ -61,6 +63,7 @@ func getPendingSignatureQueryBuildModelRowsSuccess() *sql.Rows { "account_address", make([]byte, 64), uint32(10), + true, ) mock.ExpectQuery("").WillReturnRows(mockRow) rows, _ := db.Query("") @@ -114,6 +117,7 @@ func TestPendingSignatureQuery_BuildModel(t *testing.T) { AccountAddress: "account_address", Signature: make([]byte, 64), BlockHeight: 10, + Latest: true, }, }, wantErr: false, @@ -174,6 +178,7 @@ func TestPendingSignatureQuery_ExtractModel(t *testing.T) { &mockExtractModelPendingSig.AccountAddress, &mockExtractModelPendingSig.Signature, &mockExtractModelPendingSig.BlockHeight, + &mockExtractModelPendingSig.Latest, }, }, } @@ -196,7 +201,8 @@ func TestPendingSignatureQuery_GetPendingSignatureByHash(t *testing.T) { TableName string } type args struct { - txHash []byte + txHash []byte + currentHeight, limit uint32 } tests := []struct { name string @@ -212,12 +218,15 @@ func TestPendingSignatureQuery_GetPendingSignatureByHash(t *testing.T) { TableName: mockPendingSignatureQueryIntance.TableName, }, args: args{ - txHash: make([]byte, 32), + txHash: make([]byte, 32), + currentHeight: 0, + limit: constant.MinRollbackBlocks, }, - wantStr: "SELECT transaction_hash, account_address, signature, block_height FROM pending_signature " + - "WHERE transaction_hash = ?", + wantStr: "SELECT transaction_hash, account_address, signature, block_height, latest FROM " + + "pending_signature WHERE transaction_hash = ? AND block_height >= ? AND latest = true", wantArgs: []interface{}{ make([]byte, 32), + uint32(0), }, }, } @@ -227,7 +236,11 @@ func TestPendingSignatureQuery_GetPendingSignatureByHash(t *testing.T) { Fields: tt.fields.Fields, TableName: tt.fields.TableName, } - gotStr, gotArgs := psq.GetPendingSignatureByHash(tt.args.txHash) + gotStr, gotArgs := psq.GetPendingSignatureByHash( + tt.args.txHash, + tt.args.currentHeight, + tt.args.limit, + ) if gotStr != tt.wantStr { t.Errorf("GetPendingSignatureByHash() gotStr = %v, want %v", gotStr, tt.wantStr) } @@ -256,11 +269,10 @@ func TestPendingSignatureQuery_InsertPendingSignature(t *testing.T) { pendingSig *model.PendingSignature } tests := []struct { - name string - fields fields - args args - wantStr string - wantArgs []interface{} + name string + fields fields + args args + want [][]interface{} }{ { name: "InsertPendingSignature-Success", @@ -271,9 +283,16 @@ func TestPendingSignatureQuery_InsertPendingSignature(t *testing.T) { args: args{ pendingSig: mockInsertPendingSignaturePendingSig, }, - wantStr: "INSERT INTO pending_signature (transaction_hash, account_address, signature, " + - "block_height) VALUES(? , ? , ? , ? )", - wantArgs: mockPendingSignatureQueryIntance.ExtractModel(mockInsertPendingSignaturePendingSig), + want: [][]interface{}{ + append([]interface{}{"INSERT OR REPLACE INTO pending_signature (transaction_hash, account_address, " + + "signature, block_height, latest) VALUES(? , ? , ? , ? , ? )"}, + mockPendingSignatureQueryIntance.ExtractModel(mockInsertPendingSignaturePendingSig)...), + { + "UPDATE pending_signature SET latest = false WHERE account_address = ? AND transaction_hash = " + + "? AND block_height != 0 AND latest = true", + mockInsertPendingSignaturePendingSig.AccountAddress, mockInsertPendingSignaturePendingSig.TransactionHash, + }, + }, }, } for _, tt := range tests { @@ -282,12 +301,9 @@ func TestPendingSignatureQuery_InsertPendingSignature(t *testing.T) { Fields: tt.fields.Fields, TableName: tt.fields.TableName, } - gotStr, gotArgs := psq.InsertPendingSignature(tt.args.pendingSig) - if gotStr != tt.wantStr { - t.Errorf("InsertPendingSignature() gotStr = %v, want %v", gotStr, tt.wantStr) - } - if !reflect.DeepEqual(gotArgs, tt.wantArgs) { - t.Errorf("InsertPendingSignature() gotArgs = %v, want %v", gotArgs, tt.wantArgs) + got := psq.InsertPendingSignature(tt.args.pendingSig) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("InsertPendingSignature() gotArgs = %v, want %v", got, tt.want) } }) } @@ -318,9 +334,16 @@ func TestPendingSignatureQuery_Rollback(t *testing.T) { }, wantMultiQueries: [][]interface{}{ { - fmt.Sprintf("DELETE FROM %s WHERE block_height > ?", mockPendingSignatureQueryIntance.TableName), + "DELETE FROM pending_signature WHERE height > ?", uint32(10), }, + { + "UPDATE pending_signature SET latest = ? WHERE latest = ? AND (block_height || '_' || " + + "account_address || '_' || transaction_hash) IN (SELECT (MAX(block_height) || '_' || " + + "account_address || '_' || transaction_hash) as con FROM pending_signature GROUP BY " + + "account_address || '_' || transaction_hash)", + 1, 0, + }, }, }, } @@ -356,6 +379,7 @@ func getMockScanRowSuccess() *sql.Row { "account_address", make([]byte, 64), uint32(10), + true, ) mock.ExpectQuery("").WillReturnRows(mockRow) return db.QueryRow("") diff --git a/common/query/pendingTransactionQuery.go b/common/query/pendingTransactionQuery.go index bebc406b5..e7f084dbc 100644 --- a/common/query/pendingTransactionQuery.go +++ b/common/query/pendingTransactionQuery.go @@ -10,8 +10,19 @@ import ( type ( PendingTransactionQueryInterface interface { - GetPendingTransactionByHash(txHash []byte) (str string, args []interface{}) - InsertPendingTransaction(pendingTx *model.PendingTransaction) (str string, args []interface{}) + GetPendingTransactionByHash( + txHash []byte, + status model.PendingTransactionStatus, + currentHeight, limit uint32, + ) (str string, args []interface{}) + GetPendingTransactionsBySenderAddress( + multisigAddress string, + status model.PendingTransactionStatus, + currentHeight, limit uint32, + ) ( + str string, args []interface{}, + ) + InsertPendingTransaction(pendingTx *model.PendingTransaction) [][]interface{} Scan(pendingTx *model.PendingTransaction, row *sql.Row) error ExtractModel(pendingTx *model.PendingTransaction) []interface{} BuildModel(pendingTxs []*model.PendingTransaction, rows *sql.Rows) ([]*model.PendingTransaction, error) @@ -27,10 +38,12 @@ type ( func NewPendingTransactionQuery() *PendingTransactionQuery { return &PendingTransactionQuery{ Fields: []string{ + "sender_address", "transaction_hash", "transaction_bytes", "status", "block_height", + "latest", }, TableName: "pending_transaction", } @@ -40,39 +53,88 @@ func (ptq *PendingTransactionQuery) getTableName() string { return ptq.TableName } -func (ptq *PendingTransactionQuery) GetPendingTransactionByHash(txHash []byte) (str string, args []interface{}) { - query := fmt.Sprintf("SELECT %s FROM %s WHERE transaction_hash = ?", strings.Join(ptq.Fields, ", "), ptq.getTableName()) +func (ptq *PendingTransactionQuery) GetPendingTransactionByHash( + txHash []byte, + status model.PendingTransactionStatus, + currentHeight, limit uint32, +) (str string, args []interface{}) { + var ( + blockHeight uint32 + ) + if currentHeight > limit { + blockHeight = currentHeight - limit + } + query := fmt.Sprintf("SELECT %s FROM %s WHERE transaction_hash = ? AND status = ? AND block_height >= ? "+ + "AND latest = true", strings.Join(ptq.Fields, ", "), ptq.getTableName()) return query, []interface{}{ txHash, + status, + blockHeight, + } +} + +func (ptq *PendingTransactionQuery) GetPendingTransactionsBySenderAddress( + multisigAddress string, + status model.PendingTransactionStatus, + currentHeight, limit uint32, +) (str string, args []interface{}) { + var ( + blockHeight uint32 + ) + if currentHeight > limit { + blockHeight = currentHeight - limit + } + query := fmt.Sprintf("SELECT %s FROM %s WHERE sender_address = ? AND status = ? AND block_height >= ? "+ + "AND latest = true ORDER BY block_height ASC", + strings.Join(ptq.Fields, ", "), ptq.getTableName()) + return query, []interface{}{ + multisigAddress, + status, + blockHeight, } } // InsertPendingTransaction inserts a new pending transaction into DB -func (ptq *PendingTransactionQuery) InsertPendingTransaction(pendingTx *model.PendingTransaction) (str string, args []interface{}) { - return fmt.Sprintf( - "INSERT INTO %s (%s) VALUES(%s)", +func (ptq *PendingTransactionQuery) InsertPendingTransaction(pendingTx *model.PendingTransaction) [][]interface{} { + var queries [][]interface{} + insertQuery := fmt.Sprintf("INSERT OR REPLACE INTO %s (%s) VALUES(%s)", ptq.getTableName(), strings.Join(ptq.Fields, ", "), fmt.Sprintf("? %s", strings.Repeat(", ? ", len(ptq.Fields)-1)), - ), ptq.ExtractModel(pendingTx) + ) + updateQuery := fmt.Sprintf("UPDATE %s SET latest = false WHERE transaction_hash = ? AND block_height != %d AND latest = true", + ptq.getTableName(), + pendingTx.BlockHeight, + ) + queries = append(queries, + append([]interface{}{insertQuery}, ptq.ExtractModel(pendingTx)...), + []interface{}{ + updateQuery, pendingTx.TransactionHash, + }, + ) + return queries } func (*PendingTransactionQuery) Scan(pendingTx *model.PendingTransaction, row *sql.Row) error { err := row.Scan( + &pendingTx.SenderAddress, &pendingTx.TransactionHash, &pendingTx.TransactionBytes, &pendingTx.Status, &pendingTx.BlockHeight, + &pendingTx.Latest, ) return err } func (*PendingTransactionQuery) ExtractModel(pendingTx *model.PendingTransaction) []interface{} { return []interface{}{ + &pendingTx.SenderAddress, &pendingTx.TransactionHash, &pendingTx.TransactionBytes, &pendingTx.Status, &pendingTx.BlockHeight, + &pendingTx.Latest, } } @@ -82,10 +144,12 @@ func (ptq *PendingTransactionQuery) BuildModel( for rows.Next() { var pendingTx model.PendingTransaction err := rows.Scan( + &pendingTx.SenderAddress, &pendingTx.TransactionHash, &pendingTx.TransactionBytes, &pendingTx.Status, &pendingTx.BlockHeight, + &pendingTx.Latest, ) if err != nil { return nil, err @@ -99,8 +163,17 @@ func (ptq *PendingTransactionQuery) BuildModel( func (ptq *PendingTransactionQuery) Rollback(height uint32) (multiQueries [][]interface{}) { return [][]interface{}{ { - fmt.Sprintf("DELETE FROM %s WHERE block_height > ?", ptq.getTableName()), + fmt.Sprintf("DELETE FROM %s WHERE block_height > ?", ptq.TableName), height, }, + { + fmt.Sprintf("UPDATE %s SET latest = ? WHERE latest = ? AND (block_height || '_' || "+ + "transaction_hash) IN (SELECT (MAX(block_height) || '_' || transaction_hash) as con "+ + "FROM %s GROUP BY transaction_hash)", + ptq.getTableName(), + ptq.getTableName(), + ), + 1, 0, + }, } } diff --git a/common/query/pendingTransactionQuery_test.go b/common/query/pendingTransactionQuery_test.go index f50e34b80..081a1345c 100644 --- a/common/query/pendingTransactionQuery_test.go +++ b/common/query/pendingTransactionQuery_test.go @@ -2,10 +2,11 @@ package query import ( "database/sql" - "fmt" "reflect" "testing" + "github.com/zoobc/zoobc-core/common/constant" + "github.com/DATA-DOG/go-sqlmock" "github.com/zoobc/zoobc-core/common/model" @@ -24,10 +25,12 @@ func TestNewPendingTransactionQuery(t *testing.T) { name: "NewPendingTransactionQuery-Success", want: &PendingTransactionQuery{ Fields: []string{ + "sender_address", "transaction_hash", "transaction_bytes", "status", "block_height", + "latest", }, TableName: "pending_transaction", }, @@ -57,10 +60,12 @@ func getPendingTransactionQueryBuildModelSuccessRow() *sql.Rows { db, mock, _ := sqlmock.New() mockRow := sqlmock.NewRows(mockPendingTransactionQueryInstance.Fields) mockRow.AddRow( + "", make([]byte, 32), make([]byte, 100), model.PendingTransactionStatus_PendingTransactionExecuted, uint32(10), + true, ) mock.ExpectQuery("").WillReturnRows(mockRow) rows, _ := db.Query("") @@ -110,10 +115,12 @@ func TestPendingTransactionQuery_BuildModel(t *testing.T) { }, want: []*model.PendingTransaction{ { + SenderAddress: "", TransactionHash: make([]byte, 32), TransactionBytes: make([]byte, 100), Status: model.PendingTransactionStatus_PendingTransactionExecuted, BlockHeight: 10, + Latest: true, }, }, wantErr: false, @@ -170,10 +177,12 @@ func TestPendingTransactionQuery_ExtractModel(t *testing.T) { pendingTx: mockPendingTransactionExtractModel, }, want: []interface{}{ + &mockPendingTransactionExtractModel.SenderAddress, &mockPendingTransactionExtractModel.TransactionHash, &mockPendingTransactionExtractModel.TransactionBytes, &mockPendingTransactionExtractModel.Status, &mockPendingTransactionExtractModel.BlockHeight, + &mockPendingTransactionExtractModel.Latest, }, }, } @@ -196,7 +205,9 @@ func TestPendingTransactionQuery_GetPendingTransactionByHash(t *testing.T) { TableName string } type args struct { - txHash []byte + txHash []byte + status model.PendingTransactionStatus + currentHeight, limit uint32 } tests := []struct { name string @@ -212,12 +223,17 @@ func TestPendingTransactionQuery_GetPendingTransactionByHash(t *testing.T) { TableName: mockPendingTransactionQueryInstance.TableName, }, args: args{ - txHash: make([]byte, 32), + txHash: make([]byte, 32), + status: model.PendingTransactionStatus_PendingTransactionPending, + currentHeight: 0, + limit: constant.MinRollbackBlocks, }, - wantStr: "SELECT transaction_hash, transaction_bytes, status, block_height FROM pending_transaction " + - "WHERE transaction_hash = ?", + wantStr: "SELECT sender_address, transaction_hash, transaction_bytes, status, block_height, latest FROM pending_transaction " + + "WHERE transaction_hash = ? AND status = ? AND block_height >= ? AND latest = true", wantArgs: []interface{}{ make([]byte, 32), + model.PendingTransactionStatus_PendingTransactionPending, + uint32(0), }, }, } @@ -227,7 +243,12 @@ func TestPendingTransactionQuery_GetPendingTransactionByHash(t *testing.T) { Fields: tt.fields.Fields, TableName: tt.fields.TableName, } - gotStr, gotArgs := ptq.GetPendingTransactionByHash(tt.args.txHash) + gotStr, gotArgs := ptq.GetPendingTransactionByHash( + tt.args.txHash, + tt.args.status, + tt.args.currentHeight, + tt.args.limit, + ) if gotStr != tt.wantStr { t.Errorf("GetPendingTransactionByHash() gotStr = %v, want %v", gotStr, tt.wantStr) } @@ -240,6 +261,7 @@ func TestPendingTransactionQuery_GetPendingTransactionByHash(t *testing.T) { var ( mockInsertPendingTransaction = &model.PendingTransaction{ + SenderAddress: "", TransactionHash: make([]byte, 32), TransactionBytes: make([]byte, 100), Status: model.PendingTransactionStatus_PendingTransactionExecuted, @@ -256,11 +278,10 @@ func TestPendingTransactionQuery_InsertPendingTransaction(t *testing.T) { pendingTx *model.PendingTransaction } tests := []struct { - name string - fields fields - args args - wantStr string - wantArgs []interface{} + name string + fields fields + args args + want [][]interface{} }{ { name: "InsertPendingTransaction-Success", @@ -271,9 +292,17 @@ func TestPendingTransactionQuery_InsertPendingTransaction(t *testing.T) { args: args{ pendingTx: mockInsertPendingTransaction, }, - wantStr: "INSERT INTO pending_transaction (transaction_hash, transaction_bytes, " + - "status, block_height) VALUES(? , ? , ? , ? )", - wantArgs: mockPendingTransactionQueryInstance.ExtractModel(mockInsertPendingTransaction), + want: [][]interface{}{ + append([]interface{}{ + "INSERT OR REPLACE INTO pending_transaction (sender_address, transaction_hash, " + + "transaction_bytes, status, block_height, latest) VALUES(? , ? , ? , ? , ? , ? )", + }, mockPendingTransactionQueryInstance.ExtractModel(mockInsertPendingTransaction)...), + { + "UPDATE pending_transaction SET latest = false WHERE transaction_hash = ? AND block_height " + + "!= 10 AND latest = true", + mockInsertPendingTransaction.TransactionHash, + }, + }, }, } for _, tt := range tests { @@ -282,12 +311,9 @@ func TestPendingTransactionQuery_InsertPendingTransaction(t *testing.T) { Fields: tt.fields.Fields, TableName: tt.fields.TableName, } - gotStr, gotArgs := ptq.InsertPendingTransaction(tt.args.pendingTx) - if gotStr != tt.wantStr { - t.Errorf("InsertPendingTransaction() gotStr = %v, want %v", gotStr, tt.wantStr) - } - if !reflect.DeepEqual(gotArgs, tt.wantArgs) { - t.Errorf("InsertPendingTransaction() gotArgs = %v, want %v", gotArgs, tt.wantArgs) + got := ptq.InsertPendingTransaction(tt.args.pendingTx) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("InsertPendingTransaction() gotArgs = %v, want %v", got, tt.want) } }) } @@ -318,9 +344,15 @@ func TestPendingTransactionQuery_Rollback(t *testing.T) { }, wantMultiQueries: [][]interface{}{ { - fmt.Sprintf("DELETE FROM %s WHERE block_height > ?", mockPendingTransactionQueryInstance.TableName), + "DELETE FROM pending_transaction WHERE block_height > ?", uint32(10), }, + { + "UPDATE pending_transaction SET latest = ? WHERE latest = ? AND (block_height || '_' || " + + "transaction_hash) IN (SELECT (MAX(block_height) || '_' || transaction_hash) as con " + + "FROM pending_transaction GROUP BY transaction_hash)", + 1, 0, + }, }, }, } @@ -351,10 +383,12 @@ func getPendingTransactionQueryScanSuccessRow() *sql.Row { db, mock, _ := sqlmock.New() mockRow := sqlmock.NewRows(mockPendingTransactionQueryInstance.Fields) mockRow.AddRow( + "", make([]byte, 32), make([]byte, 100), uint32(0), uint32(10), + true, ) mock.ExpectQuery("").WillReturnRows(mockRow) return db.QueryRow("") diff --git a/common/query/transactionQuery.go b/common/query/transactionQuery.go index 82bb5f29c..54c65c381 100644 --- a/common/query/transactionQuery.go +++ b/common/query/transactionQuery.go @@ -13,7 +13,6 @@ type ( TransactionQueryInterface interface { InsertTransaction(tx *model.Transaction) (str string, args []interface{}) GetTransaction(id int64) string - GetTransactions(limit uint32, offset uint64) string GetTransactionsByIds(txIds []int64) (str string, args []interface{}) GetTransactionsByBlockID(blockID int64) (str string, args []interface{}) ExtractModel(tx *model.Transaction) []interface{} @@ -47,6 +46,7 @@ func NewTransactionQuery(chaintype chaintype.ChainType) *TransactionQuery { "signature", "version", "transaction_index", + "multisig_child", }, TableName: "\"transaction\"", ChainType: chaintype, @@ -60,7 +60,7 @@ func (tq *TransactionQuery) getTableName() string { // GetTransaction get a single transaction from DB func (tq *TransactionQuery) GetTransaction(id int64) string { query := fmt.Sprintf("SELECT %s from %s", strings.Join(tq.Fields, ", "), tq.getTableName()) - var queryParam []string + var queryParam = []string{"multisig_child = false"} if id != 0 { queryParam = append(queryParam, fmt.Sprintf("id = %d", id)) } @@ -70,19 +70,6 @@ func (tq *TransactionQuery) GetTransaction(id int64) string { return query } -// GetTransactions get a set of transaction that satisfies the params from DB -func (tq *TransactionQuery) GetTransactions(limit uint32, offset uint64) string { - query := fmt.Sprintf("SELECT %s from %s", strings.Join(tq.Fields, ", "), tq.getTableName()) - - newLimit := limit - if limit == 0 { - newLimit = uint32(10) - } - - query = query + " ORDER BY block_height, timestamp" + fmt.Sprintf(" LIMIT %d,%d", offset, newLimit) - return query -} - // InsertTransaction inserts a new transaction into DB func (tq *TransactionQuery) InsertTransaction(tx *model.Transaction) (str string, args []interface{}) { var value = fmt.Sprintf("?%s", strings.Repeat(", ?", len(tq.Fields)-1)) @@ -92,7 +79,7 @@ func (tq *TransactionQuery) InsertTransaction(tx *model.Transaction) (str string } func (tq *TransactionQuery) GetTransactionsByBlockID(blockID int64) (str string, args []interface{}) { - query := fmt.Sprintf("SELECT %s FROM %s WHERE block_id = ? "+ + query := fmt.Sprintf("SELECT %s FROM %s WHERE block_id = ? AND multisig_child = false "+ "ORDER BY transaction_index ASC", strings.Join(tq.Fields, ", "), tq.getTableName()) return query, []interface{}{blockID} } @@ -103,7 +90,8 @@ func (tq *TransactionQuery) GetTransactionsByIds(txIds []int64) (str string, arg for _, txID := range txIds { txIdsStr = append(txIdsStr, fmt.Sprintf("%d", txID)) } - query := fmt.Sprintf("SELECT %s FROM %s WHERE id in (%s)", strings.Join(tq.Fields, ", "), tq.getTableName(), strings.Join(txIdsStr, ",")) + query := fmt.Sprintf("SELECT %s FROM %s WHERE multisig_child = false AND id in (%s)", + strings.Join(tq.Fields, ", "), tq.getTableName(), strings.Join(txIdsStr, ",")) return query, []interface{}{} } @@ -129,6 +117,7 @@ func (*TransactionQuery) ExtractModel(tx *model.Transaction) []interface{} { &tx.Signature, &tx.Version, &tx.TransactionIndex, + &tx.MultisigChild, } } @@ -153,6 +142,7 @@ func (*TransactionQuery) BuildModel(txs []*model.Transaction, rows *sql.Rows) ([ &tx.Signature, &tx.Version, &tx.TransactionIndex, + &tx.MultisigChild, ) if err != nil { return nil, err @@ -178,6 +168,7 @@ func (*TransactionQuery) Scan(tx *model.Transaction, row *sql.Row) error { &tx.Signature, &tx.Version, &tx.TransactionIndex, + &tx.MultisigChild, ) return err } diff --git a/common/query/transactionQuery_test.go b/common/query/transactionQuery_test.go index aaa763428..d4e94d3e3 100644 --- a/common/query/transactionQuery_test.go +++ b/common/query/transactionQuery_test.go @@ -71,7 +71,7 @@ func TestGetTransaction(t *testing.T) { want: "SELECT id, block_id, block_height, sender_account_address, " + "recipient_account_address, transaction_type, fee, timestamp, " + "transaction_hash, transaction_body_length, transaction_body_bytes, signature, version, " + - "transaction_index from \"transaction\"", + "transaction_index, multisig_child from \"transaction\" WHERE multisig_child = false", }, { name: "transaction query with ID param only", @@ -81,8 +81,8 @@ func TestGetTransaction(t *testing.T) { want: "SELECT id, block_id, block_height, sender_account_address, " + "recipient_account_address, transaction_type, fee, timestamp, " + "transaction_hash, transaction_body_length, transaction_body_bytes, signature, version, " + - "transaction_index from \"transaction\" " + - "WHERE id = 1", + "transaction_index, multisig_child from \"transaction\" " + + "WHERE multisig_child = false AND id = 1", }, } for _, tt := range tests { @@ -96,74 +96,6 @@ func TestGetTransaction(t *testing.T) { } } -func TestGetTransactions(t *testing.T) { - transactionQuery := NewTransactionQuery(chaintype.GetChainType(0)) - - type paramsStruct struct { - Limit uint32 - Offset uint64 - } - - tests := []struct { - name string - params *paramsStruct - want string - }{ - { - name: "transactions query without condition", - params: ¶msStruct{}, - want: "SELECT id, block_id, block_height, sender_account_address, " + - "recipient_account_address, transaction_type, fee, timestamp, " + - "transaction_hash, transaction_body_length, transaction_body_bytes, signature, version," + - " transaction_index from " + - "\"transaction\" ORDER BY block_height, timestamp LIMIT 0,10", - }, - { - name: "transactions query with limit", - params: ¶msStruct{ - Limit: 10, - }, - want: "SELECT id, block_id, block_height, sender_account_address, " + - "recipient_account_address, transaction_type, fee, timestamp, " + - "transaction_hash, transaction_body_length, transaction_body_bytes, signature, version, " + - "transaction_index from " + - "\"transaction\" ORDER BY block_height, timestamp LIMIT 0,10", - }, - { - name: "transactions query with offset", - params: ¶msStruct{ - Offset: 20, - }, - want: "SELECT id, block_id, block_height, sender_account_address, " + - "recipient_account_address, transaction_type, fee, timestamp, " + - "transaction_hash, transaction_body_length, transaction_body_bytes, signature, version, " + - "transaction_index from " + - "\"transaction\" ORDER BY block_height, timestamp LIMIT 20,10", - }, - { - name: "transactions query with all the params", - params: ¶msStruct{ - Limit: 10, - Offset: 20, - }, - want: "SELECT id, block_id, block_height, sender_account_address, " + - "recipient_account_address, transaction_type, fee, timestamp, " + - "transaction_hash, transaction_body_length, transaction_body_bytes, signature, version, " + - "transaction_index from " + - "\"transaction\" ORDER BY block_height, timestamp LIMIT 20,10", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - query := transactionQuery.GetTransactions(tt.params.Limit, tt.params.Offset) - if query != tt.want { - t.Errorf("GetTransactionError() \ngot = %v \nwant = %v", query, tt.want) - return - } - }) - } -} - func TestTransactionQuery_Rollback(t *testing.T) { type fields struct { Fields []string @@ -273,8 +205,8 @@ func TestTransactionQuery_GetTransactionsByBlockID(t *testing.T) { name: "wantSuccess", fields: fields(*mockTransactionQuery), args: args{blockID: int64(1)}, - wantStr: fmt.Sprintf("SELECT %s FROM \"transaction\" WHERE block_id = ? ORDER BY "+ - "transaction_index ASC", + wantStr: fmt.Sprintf("SELECT %s FROM \"transaction\" WHERE block_id = ? AND multisig_child = false"+ + " ORDER BY transaction_index ASC", strings.Join(mockTransactionQuery.Fields, ", "), ), wantArgs: []interface{}{int64(1)}, @@ -326,7 +258,7 @@ func TestTransactionQuery_GetTransactionsByIds(t *testing.T) { name: "wantSuccess", fields: fields(*mockTransactionQuery), args: args{txIds: txIds}, - wantStr: fmt.Sprintf("SELECT %s FROM \"transaction\" WHERE id in (%s)", + wantStr: fmt.Sprintf("SELECT %s FROM \"transaction\" WHERE multisig_child = false AND id in (%s)", strings.Join(mockTransactionQuery.Fields, ", "), strings.Join(txIdsStr, ","), ), @@ -375,6 +307,7 @@ func (*mockQueryExecutorBuildModel) ExecuteSelect(query string, tx bool, args .. make([]byte, 68), 1, 1, + false, ), ) return db.Query("") @@ -446,6 +379,7 @@ func (*mockRowTransactionQueryScan) ExecuteSelectRow(qStr string, args ...interf make([]byte, 68), 1, 1, + false, ), ) return db.QueryRow("") diff --git a/common/schema b/common/schema index 5ef391404..bb1388bf8 160000 --- a/common/schema +++ b/common/schema @@ -1 +1 @@ -Subproject commit 5ef39140434481b2ce5cb8f9632477a38756864d +Subproject commit bb1388bf800324a3132839cac42dce0c82c7239d diff --git a/common/transaction/multiSignature.go b/common/transaction/multiSignature.go index a2ccd29bf..4d5f0c6e6 100644 --- a/common/transaction/multiSignature.go +++ b/common/transaction/multiSignature.go @@ -2,6 +2,11 @@ package transaction import ( "bytes" + "database/sql" + + "golang.org/x/crypto/sha3" + + "github.com/zoobc/zoobc-core/common/query" "github.com/zoobc/zoobc-core/common/crypto" @@ -17,23 +22,213 @@ type ( // MultiSignatureTransaction represent wrapper transaction type that require multiple signer to approve the transcaction // wrapped MultiSignatureTransaction struct { - Body *model.MultiSignatureTransactionBody - NormalFee fee.FeeModelInterface - TransactionUtil UtilInterface - TypeSwitcher TypeActionSwitcher - Signature crypto.SignatureInterface + SenderAddress string + Fee int64 + QueryExecutor query.ExecutorInterface + AccountBalanceQuery query.AccountBalanceQueryInterface + Body *model.MultiSignatureTransactionBody + NormalFee fee.FeeModelInterface + TransactionUtil UtilInterface + TypeSwitcher TypeActionSwitcher + Signature crypto.SignatureInterface + Height uint32 + BlockID int64 + MultisigUtil MultisigTransactionUtilInterface + // pending services + MultisignatureInfoQuery query.MultisignatureInfoQueryInterface + PendingTransactionQuery query.PendingTransactionQueryInterface + PendingSignatureQuery query.PendingSignatureQueryInterface + TransactionQuery query.TransactionQueryInterface } ) -func (*MultiSignatureTransaction) ApplyConfirmed(blockTimestamp int64) error { +func (tx *MultiSignatureTransaction) ApplyConfirmed(blockTimestamp int64) error { + var ( + err error + ) + // if have multisig info, MultisigInfoService.AddMultisigInfo() -> noop duplicate + if tx.Body.MultiSignatureInfo != nil { + address, err := tx.TransactionUtil.GenerateMultiSigAddress(tx.Body.MultiSignatureInfo) + if err != nil { + return err + } + tx.Body.MultiSignatureInfo.MultisigAddress = address + tx.Body.MultiSignatureInfo.BlockHeight = tx.Height + tx.Body.MultiSignatureInfo.Latest = true + insertMultisigInfoQ := tx.MultisignatureInfoQuery.InsertMultisignatureInfo(tx.Body.MultiSignatureInfo) + err = tx.QueryExecutor.ExecuteTransactions(insertMultisigInfoQ) + if err != nil { + return err + } + } + // if have transaction bytes, PendingTransactionService.AddPendingTransaction() -> noop duplicate + if len(tx.Body.UnsignedTransactionBytes) > 0 { + var ( + pendingTx model.PendingTransaction + ) + innerTx, err := tx.TransactionUtil.ParseTransactionBytes(tx.Body.UnsignedTransactionBytes, false) + if err != nil { + return blocker.NewBlocker( + blocker.ValidationErr, + "FailToParseTransactionBytes", + ) + } + txHash := sha3.Sum256(tx.Body.UnsignedTransactionBytes) + q, args := tx.PendingTransactionQuery.GetPendingTransactionByHash( + txHash[:], model.PendingTransactionStatus_PendingTransactionExecuted, + tx.Height, constant.MinRollbackBlocks, + ) + row, _ := tx.QueryExecutor.ExecuteSelectRow(q, false, args...) + err = tx.PendingTransactionQuery.Scan(&pendingTx, row) + if err == sql.ErrNoRows { + pendingTxInsertQ := tx.PendingTransactionQuery.InsertPendingTransaction(&model.PendingTransaction{ + SenderAddress: innerTx.SenderAccountAddress, + TransactionHash: txHash[:], + TransactionBytes: tx.Body.UnsignedTransactionBytes, + Status: model.PendingTransactionStatus_PendingTransactionPending, + BlockHeight: tx.Height, + Latest: true, + }) + err = tx.QueryExecutor.ExecuteTransactions(pendingTxInsertQ) + if err != nil { + return blocker.NewBlocker(blocker.DBErr, err.Error()) + } + } else { + return blocker.NewBlocker(blocker.ValidationErr, "PendingTransactionAlreadyExecuted") + } + + } + // if have signature, PendingSignature.AddPendingSignature -> noop duplicate + if tx.Body.SignatureInfo != nil { + for addr, sig := range tx.Body.SignatureInfo.Signatures { + insertPendingSigQ := tx.PendingSignatureQuery.InsertPendingSignature(&model.PendingSignature{ + TransactionHash: tx.Body.SignatureInfo.TransactionHash, + AccountAddress: addr, + Signature: sig, + BlockHeight: tx.Height, + Latest: true, + }) + err = tx.QueryExecutor.ExecuteTransactions(insertPendingSigQ) + if err != nil { + return blocker.NewBlocker(blocker.DBErr, err.Error()) + } + + } + } + // checks for completion, if musigInfo && txBytes && signatureInfo exist, check if signature info complete + txs, err := tx.MultisigUtil.CheckMultisigComplete(tx.Body, tx.Height) + if err != nil { + return err + } + // every element in txs will have all three optional field filled, to avoid infinite recursive calls. + for _, v := range txs { + cpTx := tx + cpTx.Body = v + // parse the UnsignedTransactionBytes + utx, err := tx.TransactionUtil.ParseTransactionBytes(cpTx.Body.UnsignedTransactionBytes, false) + if err != nil { + return err + } + utx.Height = tx.Height + utxAct, err := tx.TypeSwitcher.GetTransactionType(utx) + if err != nil { + return err + } + err = utxAct.UndoApplyUnconfirmed() + if err != nil { + return blocker.NewBlocker( + blocker.ValidationErr, + "FailToApplyUndoUnconfirmedInnerTx", + ) + } + // call ApplyConfirmed() to inner transaction + err = utxAct.ApplyConfirmed(blockTimestamp) + if err != nil { + return err + } + // update pending transaction status + pendingTx := &model.PendingTransaction{ + SenderAddress: v.MultiSignatureInfo.MultisigAddress, + TransactionHash: v.SignatureInfo.TransactionHash, + TransactionBytes: v.UnsignedTransactionBytes, + Status: model.PendingTransactionStatus_PendingTransactionExecuted, + BlockHeight: tx.Height, + Latest: true, + } + updateQueries := tx.PendingTransactionQuery.InsertPendingTransaction(pendingTx) + err = tx.QueryExecutor.ExecuteTransactions(updateQueries) + + if err != nil { + return err + } + + // save multisig_child transaction + utx.MultisigChild = true + utx.BlockID = tx.BlockID + insertMultisigChildQ, args := tx.TransactionQuery.InsertTransaction(utx) + err = tx.QueryExecutor.ExecuteTransaction(insertMultisigChildQ, args...) + if err != nil { + return err + } + } return nil } -func (*MultiSignatureTransaction) ApplyUnconfirmed() error { +func (tx *MultiSignatureTransaction) ApplyUnconfirmed() error { + var ( + err error + ) + // reduce fee from sender + accountBalanceSenderQ, accountBalanceSenderQArgs := tx.AccountBalanceQuery.AddAccountSpendableBalance( + -(tx.Fee), + map[string]interface{}{ + "account_address": tx.SenderAddress, + }, + ) + err = tx.QueryExecutor.ExecuteTransaction(accountBalanceSenderQ, accountBalanceSenderQArgs...) + if err != nil { + return err + } + // Run ApplyUnconfirmed of inner transaction + if len(tx.Body.UnsignedTransactionBytes) > 0 { + // parse and apply unconfirmed + innerTx, err := tx.TransactionUtil.ParseTransactionBytes(tx.Body.UnsignedTransactionBytes, false) + if err != nil { + return blocker.NewBlocker( + blocker.ValidationErr, + "FailToParseTransactionBytes", + ) + } + innerTa, err := tx.TypeSwitcher.GetTransactionType(innerTx) + if err != nil { + return blocker.NewBlocker( + blocker.ValidationErr, + "FailToCastInnerTransaction", + ) + } + err = innerTa.ApplyUnconfirmed() + if err != nil { + return blocker.NewBlocker( + blocker.ValidationErr, + "FailToApplyUnconfirmedInnerTx", + ) + } + } return nil } -func (*MultiSignatureTransaction) UndoApplyUnconfirmed() error { +func (tx *MultiSignatureTransaction) UndoApplyUnconfirmed() error { + // recover fee + accountBalanceSenderQ, accountBalanceSenderQArgs := tx.AccountBalanceQuery.AddAccountSpendableBalance( + +(tx.Fee), + map[string]interface{}{ + "account_address": tx.SenderAddress, + }, + ) + err := tx.QueryExecutor.ExecuteTransaction(accountBalanceSenderQ, accountBalanceSenderQArgs...) + if err != nil { + return err + } return nil } @@ -59,6 +254,9 @@ func (tx *MultiSignatureTransaction) Validate(dbTx bool) error { } } if len(body.UnsignedTransactionBytes) > 0 { + var ( + pendingTx model.PendingTransaction + ) innerTx, err := tx.TransactionUtil.ParseTransactionBytes(tx.Body.UnsignedTransactionBytes, false) if err != nil { return blocker.NewBlocker( @@ -80,7 +278,17 @@ func (tx *MultiSignatureTransaction) Validate(dbTx bool) error { "FailToValidateInnerTa", ) } + txHash := sha3.Sum256(tx.Body.UnsignedTransactionBytes) + q, args := tx.PendingTransactionQuery.GetPendingTransactionByHash( + txHash[:], model.PendingTransactionStatus_PendingTransactionExecuted, + tx.Height, constant.MinRollbackBlocks, + ) + row, _ := tx.QueryExecutor.ExecuteSelectRow(q, false, args...) + err = tx.PendingTransactionQuery.Scan(&pendingTx, row) + if err != sql.ErrNoRows { + return blocker.NewBlocker(blocker.ValidationErr, "PendingTransactionAlreadyExecuted") + } } if body.SignatureInfo != nil { if body.SignatureInfo.TransactionHash == nil { // transaction hash has to come with at least one signature diff --git a/common/transaction/multiSignature_test.go b/common/transaction/multiSignature_test.go index b6f60e53e..0ac431cc9 100644 --- a/common/transaction/multiSignature_test.go +++ b/common/transaction/multiSignature_test.go @@ -1,11 +1,14 @@ package transaction import ( + "database/sql" "errors" "math/rand" "reflect" "testing" + "github.com/zoobc/zoobc-core/common/query" + "github.com/zoobc/zoobc-core/common/crypto" "github.com/zoobc/zoobc-core/common/constant" @@ -278,9 +281,29 @@ type ( validateSignatureValidateSuccess struct { crypto.Signature } + validateNoExecutedPendingTxExecutor struct { + query.Executor + } + validateNoExecutedPendingTxQuery struct { + query.PendingTransactionQuery + } + validateDuplicateExecutedPendingTxQuery struct { + query.PendingTransactionQuery + } // MultiSignatureTransactionValidate mocks ) +func (*validateNoExecutedPendingTxExecutor) ExecuteSelectRow(string, bool, ...interface{}) (*sql.Row, error) { + return nil, nil +} +func (*validateNoExecutedPendingTxQuery) Scan(*model.PendingTransaction, *sql.Row) error { + return sql.ErrNoRows +} + +func (*validateDuplicateExecutedPendingTxQuery) Scan(*model.PendingTransaction, *sql.Row) error { + return nil +} + func (*validateSignatureValidateFail) VerifySignature(payload, signature []byte, accountAddress string) error { return errors.New("mockedError") } @@ -314,11 +337,13 @@ func (*validateTypeSwitcheGetTxTypeSuccessInnerValidateFail) GetTransactionType( } func TestMultiSignatureTransaction_Validate(t *testing.T) { type fields struct { - Body *model.MultiSignatureTransactionBody - NormalFee fee.FeeModelInterface - TransactionUtil UtilInterface - TypeSwitcher TypeActionSwitcher - Signature crypto.SignatureInterface + Body *model.MultiSignatureTransactionBody + NormalFee fee.FeeModelInterface + TransactionUtil UtilInterface + PendingTransactionQuery query.PendingTransactionQueryInterface + Executor query.ExecutorInterface + TypeSwitcher TypeActionSwitcher + Signature crypto.SignatureInterface } type args struct { dbTx bool @@ -473,6 +498,26 @@ func TestMultiSignatureTransaction_Validate(t *testing.T) { }, wantErr: true, }, + { + name: "MultisignatureTransaction_Validate-Success-TransactionBytesExist-ExecutedTxExist", + fields: fields{ + Body: &model.MultiSignatureTransactionBody{ + MultiSignatureInfo: nil, + UnsignedTransactionBytes: make([]byte, 100), + SignatureInfo: nil, + }, + NormalFee: nil, + PendingTransactionQuery: &validateDuplicateExecutedPendingTxQuery{}, + Executor: &validateNoExecutedPendingTxExecutor{}, + TransactionUtil: &validateTransactionUtilParseSuccess{}, + TypeSwitcher: &validateTypeSwitcheGetTxTypeSuccessInnerValidateSuccess{}, + Signature: nil, + }, + args: args{ + dbTx: false, + }, + wantErr: true, + }, { name: "MultisignatureTransaction_Validate-Success-TransactionBytesExist-InnerValidateSuccess", fields: fields{ @@ -481,10 +526,12 @@ func TestMultiSignatureTransaction_Validate(t *testing.T) { UnsignedTransactionBytes: make([]byte, 100), SignatureInfo: nil, }, - NormalFee: nil, - TransactionUtil: &validateTransactionUtilParseSuccess{}, - TypeSwitcher: &validateTypeSwitcheGetTxTypeSuccessInnerValidateSuccess{}, - Signature: nil, + NormalFee: nil, + PendingTransactionQuery: &validateNoExecutedPendingTxQuery{}, + Executor: &validateNoExecutedPendingTxExecutor{}, + TransactionUtil: &validateTransactionUtilParseSuccess{}, + TypeSwitcher: &validateTypeSwitcheGetTxTypeSuccessInnerValidateSuccess{}, + Signature: nil, }, args: args{ dbTx: false, @@ -606,11 +653,13 @@ func TestMultiSignatureTransaction_Validate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tx := &MultiSignatureTransaction{ - Body: tt.fields.Body, - NormalFee: tt.fields.NormalFee, - TransactionUtil: tt.fields.TransactionUtil, - TypeSwitcher: tt.fields.TypeSwitcher, - Signature: tt.fields.Signature, + Body: tt.fields.Body, + NormalFee: tt.fields.NormalFee, + TransactionUtil: tt.fields.TransactionUtil, + TypeSwitcher: tt.fields.TypeSwitcher, + PendingTransactionQuery: tt.fields.PendingTransactionQuery, + QueryExecutor: tt.fields.Executor, + Signature: tt.fields.Signature, } if err := tx.Validate(tt.args.dbTx); (err != nil) != tt.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) diff --git a/common/transaction/transaction.go b/common/transaction/transaction.go index e2ef1fc10..21f891930 100644 --- a/common/transaction/transaction.go +++ b/common/transaction/transaction.go @@ -228,6 +228,14 @@ func (ts *TypeSwitcher) GetTransactionType(tx *model.Transaction) (TypeAction, e case 5: switch buf[1] { case 0: + // initialize service for pending_tx, pending_sig and multisig_info + multisigUtil := NewMultisigTransactionUtil( + ts.Executor, + query.NewPendingTransactionQuery(), + query.NewPendingSignatureQuery(), + query.NewMultisignatureInfoQuery(), + &Util{}, + ) multiSigTransactionBody, err := new(MultiSignatureTransaction).ParseBodyBytes(tx.GetTransactionBodyBytes()) if err != nil { return nil, err @@ -239,7 +247,16 @@ func (ts *TypeSwitcher) GetTransactionType(tx *model.Transaction) (TypeAction, e TypeSwitcher: &TypeSwitcher{ Executor: ts.Executor, }, - Signature: &crypto.Signature{}, + Signature: &crypto.Signature{}, + Height: tx.Height, + BlockID: tx.BlockID, + MultisigUtil: multisigUtil, + QueryExecutor: ts.Executor, + AccountBalanceQuery: query.NewAccountBalanceQuery(), + MultisignatureInfoQuery: query.NewMultisignatureInfoQuery(), + PendingTransactionQuery: query.NewPendingTransactionQuery(), + PendingSignatureQuery: query.NewPendingSignatureQuery(), + TransactionQuery: query.NewTransactionQuery(&chaintype.MainChain{}), }, nil default: return nil, nil diff --git a/common/transaction/transactionGeneral.go b/common/transaction/transactionGeneral.go index ab02289b5..b4a7e8876 100644 --- a/common/transaction/transactionGeneral.go +++ b/common/transaction/transactionGeneral.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "sort" "time" "github.com/zoobc/zoobc-core/common/blocker" @@ -32,6 +33,19 @@ type ( } Util struct{} + + MultisigTransactionUtilInterface interface { + CheckMultisigComplete( + tx *model.MultiSignatureTransactionBody, txHeight uint32, + ) ([]*model.MultiSignatureTransactionBody, error) + } + MultisigTransactionUtil struct { + QueryExecutor query.ExecutorInterface + PendingTransactionQuery query.PendingTransactionQueryInterface + PendingSignatureQuery query.PendingSignatureQueryInterface + MultisigInfoQuery query.MultisignatureInfoQueryInterface + TransactionUtil UtilInterface + } ) // GetTransactionBytes translate transaction model to its byte representation @@ -346,6 +360,7 @@ func (tu *Util) GenerateMultiSigAddress(info *model.MultiSignatureInfo) (string, if info == nil { return "", fmt.Errorf("params cannot be nil") } + sort.Strings(info.Addresses) var ( buff = bytes.NewBuffer([]byte{}) sig = crypto.NewEd25519Signature() @@ -361,3 +376,281 @@ func (tu *Util) GenerateMultiSigAddress(info *model.MultiSignatureInfo) (string, return sig.GetAddressFromPublicKey(hashed[:]) } + +func NewMultisigTransactionUtil( + queryExecutor query.ExecutorInterface, + pendingTransactionQuery query.PendingTransactionQueryInterface, + pendingSignatureQuery query.PendingSignatureQueryInterface, + multisigInfoQuery query.MultisignatureInfoQueryInterface, + transactionUtil UtilInterface, +) *MultisigTransactionUtil { + return &MultisigTransactionUtil{ + QueryExecutor: queryExecutor, + PendingTransactionQuery: pendingTransactionQuery, + PendingSignatureQuery: pendingSignatureQuery, + MultisigInfoQuery: multisigInfoQuery, + TransactionUtil: transactionUtil, + } +} + +func (mtu *MultisigTransactionUtil) CheckMultisigComplete( + body *model.MultiSignatureTransactionBody, txHeight uint32, +) ([]*model.MultiSignatureTransactionBody, error) { + if body.MultiSignatureInfo != nil { + var ( + pendingTxs []*model.PendingTransaction + dbPendingTxs []*model.PendingTransaction + ) + multisigAddress := body.MultiSignatureInfo.MultisigAddress + if len(body.UnsignedTransactionBytes) > 0 { + txHash := sha3.Sum256(body.UnsignedTransactionBytes) + pendingTxs = append(pendingTxs, &model.PendingTransaction{ + TransactionHash: txHash[:], + TransactionBytes: body.UnsignedTransactionBytes, + Status: model.PendingTransactionStatus_PendingTransactionPending, + BlockHeight: txHeight, + }) + } + q, args := mtu.PendingTransactionQuery.GetPendingTransactionsBySenderAddress( + multisigAddress, model.PendingTransactionStatus_PendingTransactionPending, + txHeight, constant.MinRollbackBlocks, + ) + pendingTxRows, err := mtu.QueryExecutor.ExecuteSelect(q, false, args...) + if err != nil { + return nil, err + } + defer pendingTxRows.Close() + dbPendingTxs, err = mtu.PendingTransactionQuery.BuildModel(dbPendingTxs, pendingTxRows) + if err != nil { + return nil, err + } + pendingTxs = append(pendingTxs, dbPendingTxs...) + if len(pendingTxs) < 1 { + return nil, nil + } + var readyTxs []*model.MultiSignatureTransactionBody + for _, v := range pendingTxs { + var ( + sigInfo *model.SignatureInfo + pendingSigs []*model.PendingSignature + signatures = make(map[string][]byte) + validSignatureCounter uint32 + ) + q, args := mtu.PendingSignatureQuery.GetPendingSignatureByHash( + v.TransactionHash, + txHeight, constant.MinRollbackBlocks, + ) + pendingSigRows, err := mtu.QueryExecutor.ExecuteSelect(q, false, args...) + if err != nil { + return nil, err + } + pendingSigs, err = mtu.PendingSignatureQuery.BuildModel(pendingSigs, pendingSigRows) + if err != nil { + pendingSigRows.Close() + return nil, err + } + pendingSigRows.Close() + if err != nil { + return nil, err + } + for _, sig := range pendingSigs { + signatures[sig.AccountAddress] = sig.Signature + } + if len(pendingSigs) < 1 { + return nil, nil + } + if body.SignatureInfo != nil { + if bytes.Equal(v.TransactionHash, body.SignatureInfo.TransactionHash) { + for addr, sig := range body.SignatureInfo.Signatures { + signatures[addr] = sig + } + } + } + sigInfo = &model.SignatureInfo{ + TransactionHash: pendingSigs[0].TransactionHash, + Signatures: signatures, + } + for _, addr := range body.MultiSignatureInfo.Addresses { + if sigInfo.Signatures[addr] != nil { + validSignatureCounter++ + } + } + if validSignatureCounter >= body.MultiSignatureInfo.MinimumSignatures { + // todo: return ready to applyConfirm tx + cpTx := &model.MultiSignatureTransactionBody{ + MultiSignatureInfo: body.MultiSignatureInfo, + UnsignedTransactionBytes: v.TransactionBytes, + SignatureInfo: &model.SignatureInfo{ + TransactionHash: v.TransactionHash, + Signatures: signatures, + }, + } + readyTxs = append(readyTxs, cpTx) + } + } + return readyTxs, nil + } else if len(body.UnsignedTransactionBytes) > 0 { + var ( + multisigInfo model.MultiSignatureInfo + pendingSigs []*model.PendingSignature + validSignatureCounter uint32 + ) + txHash := sha3.Sum256(body.UnsignedTransactionBytes) + innerTx, err := mtu.TransactionUtil.ParseTransactionBytes(body.UnsignedTransactionBytes, false) + if err != nil { + return nil, blocker.NewBlocker( + blocker.ValidationErr, + "FailToParseTransactionBytes", + ) + } + q, args := mtu.MultisigInfoQuery.GetMultisignatureInfoByAddress( + innerTx.SenderAccountAddress, + txHeight, constant.MinRollbackBlocks, + ) + row, _ := mtu.QueryExecutor.ExecuteSelectRow(q, false, args...) + err = mtu.MultisigInfoQuery.Scan(&multisigInfo, row) + if err != nil { + if err == sql.ErrNoRows { // multisig info not present + return nil, nil + } + // other database errors + return nil, err + } + body.MultiSignatureInfo = &multisigInfo + if body.SignatureInfo != nil { + for addr, sig := range body.SignatureInfo.Signatures { + pendingSigs = append(pendingSigs, &model.PendingSignature{ + TransactionHash: body.SignatureInfo.TransactionHash, + AccountAddress: addr, + Signature: sig, + BlockHeight: txHeight, + }) + } + } + var dbPendingSigs []*model.PendingSignature + q, args = mtu.PendingSignatureQuery.GetPendingSignatureByHash( + txHash[:], + txHeight, constant.MinRollbackBlocks, + ) + rows, err := mtu.QueryExecutor.ExecuteSelect(q, false, args...) + if err != nil { + return nil, err + } + defer rows.Close() + dbPendingSigs, err = mtu.PendingSignatureQuery.BuildModel(dbPendingSigs, rows) + if err != nil { + return nil, err + } + pendingSigs = append(pendingSigs, dbPendingSigs...) + body.SignatureInfo = &model.SignatureInfo{ + TransactionHash: txHash[:], + Signatures: make(map[string][]byte), + } + for _, sig := range pendingSigs { + body.SignatureInfo.Signatures[sig.AccountAddress] = sig.Signature + } + if len(body.SignatureInfo.Signatures) < 1 { + return nil, nil + } + + for _, addr := range multisigInfo.Addresses { + if body.SignatureInfo.Signatures[addr] != nil { + validSignatureCounter++ + } + } + if validSignatureCounter >= multisigInfo.MinimumSignatures { + // replace unsigned tx to have status executed + pendingTxInsertQ := mtu.PendingTransactionQuery.InsertPendingTransaction(&model.PendingTransaction{ + SenderAddress: innerTx.SenderAccountAddress, + TransactionHash: txHash[:], + TransactionBytes: body.UnsignedTransactionBytes, + Status: model.PendingTransactionStatus_PendingTransactionExecuted, + BlockHeight: txHeight, + Latest: true, + }) + err = mtu.QueryExecutor.ExecuteTransactions(pendingTxInsertQ) + if err != nil { + return nil, blocker.NewBlocker(blocker.DBErr, err.Error()) + } + return []*model.MultiSignatureTransactionBody{ + body, + }, nil + } + } else if body.SignatureInfo != nil { + var ( + pendingTx model.PendingTransaction + pendingSigs []*model.PendingSignature + multisigInfo model.MultiSignatureInfo + validSignatureCounter uint32 + ) + txHash := body.SignatureInfo.TransactionHash + + q, args := mtu.PendingTransactionQuery.GetPendingTransactionByHash( + txHash, model.PendingTransactionStatus_PendingTransactionPending, + txHeight, constant.MinRollbackBlocks, + ) + row, err := mtu.QueryExecutor.ExecuteSelectRow(q, false, args...) + if err != nil { + return nil, err + } + err = mtu.PendingTransactionQuery.Scan(&pendingTx, row) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + body.UnsignedTransactionBytes = pendingTx.TransactionBytes + innerTx, err := mtu.TransactionUtil.ParseTransactionBytes(body.UnsignedTransactionBytes, false) + if err != nil { + return nil, blocker.NewBlocker( + blocker.ValidationErr, + "FailToParseTransactionBytes", + ) + } + q, args = mtu.PendingSignatureQuery.GetPendingSignatureByHash( + txHash, + txHeight, constant.MinRollbackBlocks, + ) + rowsPendingSigs, err := mtu.QueryExecutor.ExecuteSelect(q, false, args...) + if err != nil { + return nil, err + } + pendingSigs, err = mtu.PendingSignatureQuery.BuildModel(pendingSigs, rowsPendingSigs) + if err != nil { + return nil, err + } + defer rowsPendingSigs.Close() + for _, sig := range pendingSigs { + body.SignatureInfo.Signatures[sig.AccountAddress] = sig.Signature + } + q, args = mtu.MultisigInfoQuery.GetMultisignatureInfoByAddress( + innerTx.SenderAccountAddress, + txHeight, constant.MinRollbackBlocks, + ) + row, _ = mtu.QueryExecutor.ExecuteSelectRow(q, false, args...) + err = mtu.MultisigInfoQuery.Scan(&multisigInfo, row) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + // validate signature + for _, addr := range multisigInfo.Addresses { + if body.SignatureInfo.Signatures[addr] != nil { + validSignatureCounter++ + } + } + if validSignatureCounter >= multisigInfo.MinimumSignatures { + cpTx := body + cpTx.UnsignedTransactionBytes = pendingTx.TransactionBytes + cpTx.MultiSignatureInfo = &multisigInfo + return []*model.MultiSignatureTransactionBody{ + cpTx, + }, nil + } + + } + return nil, nil +} diff --git a/common/transaction/transactionGeneral_test.go b/common/transaction/transactionGeneral_test.go index 10f24a007..76538c0d3 100644 --- a/common/transaction/transactionGeneral_test.go +++ b/common/transaction/transactionGeneral_test.go @@ -632,7 +632,7 @@ func TestUtil_GenerateMultiSigAddress(t *testing.T) { "BCZKLvgUYZ1KKx-jtF9KoJskjVPvB9jpIjfzzI6zDW0J", }, }}, - want: "N8IH3smVnNkUwRwJj2ZnRjyS1_2n15lK9GfqhWcApHWa", + want: "C1Jgm37Y-xW6ls1l9JW5XsRnsifX0CkWB4QTwZE9keVt", }, } for _, tt := range tests { diff --git a/core/service/blockMainService.go b/core/service/blockMainService.go index b2653d2ce..ad2a7a373 100644 --- a/core/service/blockMainService.go +++ b/core/service/blockMainService.go @@ -430,7 +430,6 @@ func (bs *BlockService) PushBlock(previousBlock, block *model.Block, broadcast, } } rows.Close() - if block.Height > 0 { err = bs.TransactionCoreService.ValidateTransaction(txType, true) if err != nil { diff --git a/core/service/mempoolServiceUtil_test.go b/core/service/mempoolServiceUtil_test.go index eb425ebae..1e101c29e 100644 --- a/core/service/mempoolServiceUtil_test.go +++ b/core/service/mempoolServiceUtil_test.go @@ -173,6 +173,7 @@ func (*mockExecutorValidateMempoolTransactionSuccess) ExecuteSelectRow(qStr stri make([]byte, 0), nil, make([]byte, 64), + false, ), ) return db.QueryRow(qStr), nil