services: add ability to combine notary signers

Notes for witnesses:
* [N sig + M multisig + K contract] combination is possible where N, M, K >=0.
* Each verification script should be properly filled in.
* Each invocation script should either be empty or contain exactly one
signature.
This commit is contained in:
Anna Shaleva 2021-10-20 18:43:32 +03:00 committed by AnnaShaleva
parent fcc7f7349d
commit 75d7891ca1
5 changed files with 592 additions and 475 deletions

View file

@ -35,7 +35,9 @@ for this service. `FEE` is set to be 0.1 GAS.
We'll also use `NKeys` definition as the number of keys that participate in the We'll also use `NKeys` definition as the number of keys that participate in the
process of signature collection. This is the number of keys that could potentially process of signature collection. This is the number of keys that could potentially
sign the transaction, for transactions lacking appropriate witnesses that would be sign the transaction, for transactions lacking appropriate witnesses that would be
the number of witnesses, for "M out of N" multisignature scripts that's N. the number of witnesses, for "M out of N" multisignature scripts that's N, for
combination of K standard signature witnesses and L multisignature "M out of N"
witnesses that's K+N*L.
### Transaction attributes ### Transaction attributes
@ -107,11 +109,13 @@ This payload has two incomplete transactions inside:
than the current chain height and it must have `Conflicts` attribute with the than the current chain height and it must have `Conflicts` attribute with the
hash of the main transaction. It at the same time must have `Notary assisted` hash of the main transaction. It at the same time must have `Notary assisted`
attribute with a count of zero. attribute with a count of zero.
- *Main tx*. This is the one that actually needs to be completed, it either - *Main tx*. This is the one that actually needs to be completed, it:
doesn't have all witnesses attached (in this case none of them can be 1. *either* doesn't have all witnesses attached
multisignature), or it only has a partial multisignature, currenlty only one of 2. *or* it only has a partial multisignature
the two is allowed. This transaction must have `Notary assisted` attribute with 3. *or* have not all witnesses attached and some of the rest are partial multisignature
a count of `NKeys` (and Notary contract as one of the signers).
This transaction must have `Notary assisted` attribute with a count of `NKeys`
(and Notary contract as one of the signers).
See the [Notary request submission guide](#2-request-submission) to learn how to See the [Notary request submission guide](#2-request-submission) to learn how to
construct and send the payload. construct and send the payload.
@ -297,10 +301,8 @@ the steps to create a signature request:
* First signer is the one who pays transaction fees. * First signer is the one who pays transaction fees.
* Each signer is either multisignature or standard signature or a contract * Each signer is either multisignature or standard signature or a contract
signer. signer.
* Multisignature and signature signers can't be combined. * Multisignature and signature signers can be combined.
* Contract signer can be combined with any other signer. * Contract signer can be combined with any other signer.
* Maximum number of multisignature signers is 1.
* Maximum number of signature or contract signers is unlimited.
Include Notary native contract in the list of signers with the following Include Notary native contract in the list of signers with the following
constraints: constraints:
@ -374,7 +376,8 @@ the steps to create a signature request:
9. Construct the list of main transactions witnesses (that will be `Scripts` 9. Construct the list of main transactions witnesses (that will be `Scripts`
transaction field). Use the following rules: transaction field). Use the following rules:
- Contract-based witness should have `Invocation` script that pushes arguments - Contract-based witness should have `Invocation` script that pushes arguments
on stack (it may be empty) and empty `Verification` script. on stack (it may be empty) and empty `Verification` script. Currently, **only
empty** `Invocation` scripts are supported for contract-based witnesses.
- **Notary contract witness** (which is also a contract-based witness) should - **Notary contract witness** (which is also a contract-based witness) should
have empty `Verification` script. `Invocation` script should be of the form have empty `Verification` script. `Invocation` script should be of the form
[opcode.PUSHDATA1, 64, make([]byte, 64)...], i.e. to be a placeholder for [opcode.PUSHDATA1, 64, make([]byte, 64)...], i.e. to be a placeholder for
@ -388,8 +391,7 @@ the steps to create a signature request:
if `Invocation` script is to be collected from other notary requests. if `Invocation` script is to be collected from other notary requests.
`Invocation` script either should push on stack signature bytes (one `Invocation` script either should push on stack signature bytes (one
signature at max per one resuest) **or** (in case if there's no ability to signature at max per one resuest) **or** (in case if there's no ability to
provide proper signature) **should be of the form [opcode.PUSHDATA1, 64, provide proper signature) **should be empty**.
make([]byte, 64)...]**, i.e. to be a placeholder for signature.
10. Define lifetime for the fallback transaction. Let the `fallbackValidFor` be 10. Define lifetime for the fallback transaction. Let the `fallbackValidFor` be
the lifetime. Let `N` be the current chain's height and `VUB` be the lifetime. Let `N` be the current chain's height and `VUB` be
`ValidUntilBlock` value estimated at the step 3. Then notary node is trying to `ValidUntilBlock` value estimated at the step 3. Then notary node is trying to

View file

@ -1,7 +1,6 @@
package core package core
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand"
@ -107,7 +106,6 @@ func TestNotary(t *testing.T) {
defer mtx.RUnlock() defer mtx.RUnlock()
return completedTxes[h] return completedTxes[h]
} }
var completedTx *transaction.Transaction var completedTx *transaction.Transaction
require.Eventually(t, func() bool { require.Eventually(t, func() bool {
mtx.RLock() mtx.RLock()
@ -138,6 +136,11 @@ func TestNotary(t *testing.T) {
notaryNodes := keys.PublicKeys{acc1.PrivateKey().PublicKey(), acc2.PrivateKey().PublicKey()} notaryNodes := keys.PublicKeys{acc1.PrivateKey().PublicKey(), acc2.PrivateKey().PublicKey()}
bc.setNodesByRole(t, true, noderoles.P2PNotary, notaryNodes) bc.setNodesByRole(t, true, noderoles.P2PNotary, notaryNodes)
type requester struct {
accounts []*wallet.Account
m int
typ notary.RequestType
}
createFallbackTx := func(requester *wallet.Account, mainTx *transaction.Transaction, nvbIncrement ...uint32) *transaction.Transaction { createFallbackTx := func(requester *wallet.Account, mainTx *transaction.Transaction, nvbIncrement ...uint32) *transaction.Transaction {
fallback := transaction.New([]byte{byte(opcode.RET)}, 2000_0000) fallback := transaction.New([]byte{byte(opcode.RET)}, 2000_0000)
fallback.Nonce = nonce fallback.Nonce = nonce
@ -182,19 +185,37 @@ func TestNotary(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
return fallback return fallback
} }
createMixedRequest := func(requesters []requester, NVBincrements ...uint32) []*payload.P2PNotaryRequest {
createStandardRequest := func(requesters []*wallet.Account, NVBincrements ...uint32) []*payload.P2PNotaryRequest {
mainTx := *transaction.New([]byte{byte(opcode.RET)}, 11000000) mainTx := *transaction.New([]byte{byte(opcode.RET)}, 11000000)
mainTx.Nonce = nonce mainTx.Nonce = nonce
nonce++ nonce++
mainTx.SystemFee = 100000000 mainTx.SystemFee = 100000000
mainTx.ValidUntilBlock = bc.BlockHeight() + 2*nvbDiffFallback mainTx.ValidUntilBlock = bc.BlockHeight() + 2*nvbDiffFallback
signers := make([]transaction.Signer, len(requesters)+1) signers := make([]transaction.Signer, len(requesters)+1)
var (
nKeys uint8
verificationScripts [][]byte
)
for i := range requesters { for i := range requesters {
var script []byte
switch requesters[i].typ {
case notary.Signature:
script = requesters[i].accounts[0].PrivateKey().PublicKey().GetVerificationScript()
nKeys++
case notary.MultiSignature:
pubs := make(keys.PublicKeys, len(requesters[i].accounts))
for j, r := range requesters[i].accounts {
pubs[j] = r.PrivateKey().PublicKey()
}
script, err = smartcontract.CreateMultiSigRedeemScript(requesters[i].m, pubs)
require.NoError(t, err)
nKeys += uint8(len(requesters[i].accounts))
}
signers[i] = transaction.Signer{ signers[i] = transaction.Signer{
Account: requesters[i].PrivateKey().PublicKey().GetScriptHash(), Account: hash.Hash160(script),
Scopes: transaction.None, Scopes: transaction.None,
} }
verificationScripts = append(verificationScripts, script)
} }
signers[len(signers)-1] = transaction.Signer{ signers[len(signers)-1] = transaction.Signer{
Account: bc.GetNotaryContractScriptHash(), Account: bc.GetNotaryContractScriptHash(),
@ -204,132 +225,73 @@ func TestNotary(t *testing.T) {
mainTx.Attributes = []transaction.Attribute{ mainTx.Attributes = []transaction.Attribute{
{ {
Type: transaction.NotaryAssistedT, Type: transaction.NotaryAssistedT,
Value: &transaction.NotaryAssisted{NKeys: uint8(len(requesters))}, Value: &transaction.NotaryAssisted{NKeys: nKeys},
}, },
} }
payloads := make([]*payload.P2PNotaryRequest, len(requesters)) payloads := make([]*payload.P2PNotaryRequest, nKeys)
for i := range payloads { plIndex := 0
cp := mainTx
main := &cp
scripts := make([]transaction.Witness, len(requesters)+1)
for j := range requesters {
scripts[j].VerificationScript = requesters[j].PrivateKey().PublicKey().GetVerificationScript()
}
scripts[i].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, requesters[i].PrivateKey().SignHashable(uint32(testchain.Network()), main)...)
main.Scripts = scripts
_ = main.Size() // for size update test
var fallback *transaction.Transaction
if len(NVBincrements) == len(requesters) {
fallback = createFallbackTx(requesters[i], main, NVBincrements[i])
} else {
fallback = createFallbackTx(requesters[i], main)
}
_ = fallback.Size() // for size update test
payloads[i] = &payload.P2PNotaryRequest{
MainTransaction: main,
FallbackTransaction: fallback,
}
}
return payloads
}
createMultisigRequest := func(m int, requesters []*wallet.Account) []*payload.P2PNotaryRequest {
mainTx := *transaction.New([]byte{byte(opcode.RET)}, 11000000)
mainTx.Nonce = nonce
nonce++
mainTx.SystemFee = 100000000
mainTx.ValidUntilBlock = bc.BlockHeight() + 2*nvbDiffFallback
pubs := make(keys.PublicKeys, len(requesters))
for i, r := range requesters {
pubs[i] = r.PrivateKey().PublicKey()
}
script, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
require.NoError(t, err)
mainTx.Signers = []transaction.Signer{
{
Account: hash.Hash160(script),
Scopes: transaction.None,
},
{
Account: bc.GetNotaryContractScriptHash(),
Scopes: transaction.None,
},
}
mainTx.Attributes = []transaction.Attribute{
{
Type: transaction.NotaryAssistedT,
Value: &transaction.NotaryAssisted{NKeys: uint8(len(requesters))},
},
}
payloads := make([]*payload.P2PNotaryRequest, len(requesters))
// we'll collect only m signatures out of n (so only m payloads are needed), but let's create payloads for all requesters (for the next tests) // we'll collect only m signatures out of n (so only m payloads are needed), but let's create payloads for all requesters (for the next tests)
for i := range payloads { for i, r := range requesters {
cp := mainTx for _, acc := range r.accounts {
main := &cp cp := mainTx
main.Scripts = []transaction.Witness{ main := &cp
{ main.Scripts = make([]transaction.Witness, len(requesters))
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, requesters[i].PrivateKey().SignHashable(uint32(testchain.Network()), main)...), for j := range main.Scripts {
VerificationScript: script, main.Scripts[j].VerificationScript = verificationScripts[j]
}, if i == j {
{}, // empty Notary witness main.Scripts[j].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, acc.PrivateKey().SignHashable(uint32(testchain.Network()), main)...)
} }
fallback := createFallbackTx(requesters[i], main) }
payloads[i] = &payload.P2PNotaryRequest{ main.Scripts = append(main.Scripts, transaction.Witness{}) // empty Notary witness
MainTransaction: main,
FallbackTransaction: fallback, _ = main.Size() // for size update test
var fallback *transaction.Transaction
if len(NVBincrements) == int(nKeys) {
fallback = createFallbackTx(acc, main, NVBincrements[plIndex])
} else {
fallback = createFallbackTx(acc, main)
}
_ = fallback.Size() // for size update test
payloads[plIndex] = &payload.P2PNotaryRequest{
MainTransaction: main,
FallbackTransaction: fallback,
}
plIndex++
} }
} }
return payloads return payloads
} }
checkSigTx := func(t *testing.T, requests []*payload.P2PNotaryRequest, sentCount int, shouldComplete bool) { checkMainTx := func(t *testing.T, requesters []requester, requests []*payload.P2PNotaryRequest, sentCount int, shouldComplete bool) {
nKeys := len(requests) nSigs := 0
if sentCount == nKeys && shouldComplete { for _, r := range requesters {
switch r.typ {
case notary.Signature:
nSigs++
case notary.MultiSignature:
nSigs += r.m
}
}
nSigners := len(requesters) + 1
if sentCount >= nSigs && shouldComplete {
completedTx := getCompletedTx(t, true, requests[0].MainTransaction.Hash()) completedTx := getCompletedTx(t, true, requests[0].MainTransaction.Hash())
require.Equal(t, nKeys+1, len(completedTx.Signers)) require.Equal(t, nSigners, len(completedTx.Signers))
require.Equal(t, nKeys+1, len(completedTx.Scripts)) require.Equal(t, nSigners, len(completedTx.Scripts))
// check that tx size was updated // check that tx size was updated
require.Equal(t, io.GetVarSize(completedTx), completedTx.Size()) require.Equal(t, io.GetVarSize(completedTx), completedTx.Size())
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx) for i := 0; i < len(completedTx.Scripts)-1; i++ {
for i, req := range requests { interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx)
require.Equal(t, req.MainTransaction.Scripts[i], completedTx.Scripts[i])
_, err := bc.verifyHashAgainstScript(completedTx.Signers[i].Account, &completedTx.Scripts[i], interopCtx, -1) _, err := bc.verifyHashAgainstScript(completedTx.Signers[i].Account, &completedTx.Scripts[i], interopCtx, -1)
require.NoError(t, err) require.NoError(t, err)
} }
require.Equal(t, transaction.Witness{ require.Equal(t, transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(testchain.Network()), requests[0].MainTransaction)...), InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(testchain.Network()), requests[0].MainTransaction)...),
VerificationScript: []byte{}, VerificationScript: []byte{},
}, completedTx.Scripts[nKeys]) }, completedTx.Scripts[len(completedTx.Scripts)-1])
} else {
completedTx := getCompletedTx(t, false, requests[0].MainTransaction.Hash())
require.Nil(t, completedTx, fmt.Errorf("main transaction shouldn't be completed: sent %d out of %d requests", sentCount, nKeys))
}
}
checkMultisigTx := func(t *testing.T, nSigs int, requests []*payload.P2PNotaryRequest, sentCount int, shouldComplete bool) {
if sentCount >= nSigs && shouldComplete {
completedTx := getCompletedTx(t, true, requests[0].MainTransaction.Hash())
require.Equal(t, 2, len(completedTx.Signers))
require.Equal(t, 2, len(completedTx.Scripts))
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx)
_, err := bc.verifyHashAgainstScript(completedTx.Signers[0].Account, &completedTx.Scripts[0], interopCtx, -1)
require.NoError(t, err)
require.Equal(t, transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(testchain.Network()), requests[0].MainTransaction)...),
VerificationScript: []byte{},
}, completedTx.Scripts[1])
// check that only nSigs out of nKeys signatures are presented in the invocation script
for i, req := range requests[:nSigs] {
require.True(t, bytes.Contains(completedTx.Scripts[0].InvocationScript, req.MainTransaction.Scripts[0].InvocationScript), fmt.Errorf("signature from extra request #%d shouldn't be presented in the main tx", i))
}
// the rest (nKeys-nSigs) out of nKeys shouldn't be presented in the invocation script
for i, req := range requests[nSigs:] {
require.False(t, bytes.Contains(completedTx.Scripts[0].InvocationScript, req.MainTransaction.Scripts[0].InvocationScript), fmt.Errorf("signature from extra request #%d shouldn't be presented in the main tx", i))
}
} else { } else {
completedTx := getCompletedTx(t, false, requests[0].MainTransaction.Hash()) completedTx := getCompletedTx(t, false, requests[0].MainTransaction.Hash())
require.Nil(t, completedTx, fmt.Errorf("main transaction shouldn't be completed: sent %d out of %d requests", sentCount, nSigs)) require.Nil(t, completedTx, fmt.Errorf("main transaction shouldn't be completed: sent %d out of %d requests", sentCount, nSigs))
@ -358,13 +320,17 @@ func TestNotary(t *testing.T) {
} }
} }
} }
checkCompleteStandardRequest := func(t *testing.T, nKeys int, shouldComplete bool, nvbIncrements ...uint32) []*payload.P2PNotaryRequest { checkCompleteStandardRequest := func(t *testing.T, nKeys int, shouldComplete bool, nvbIncrements ...uint32) ([]*payload.P2PNotaryRequest, []requester) {
requesters := make([]*wallet.Account, nKeys) requesters := make([]requester, nKeys)
for i := range requesters { for i := range requesters {
requesters[i], _ = wallet.NewAccount() acc, _ := wallet.NewAccount()
requesters[i] = requester{
accounts: []*wallet.Account{acc},
typ: notary.Signature,
}
} }
requests := createStandardRequest(requesters, nvbIncrements...) requests := createMixedRequest(requesters, nvbIncrements...)
sendOrder := make([]int, nKeys) sendOrder := make([]int, nKeys)
for i := range sendOrder { for i := range sendOrder {
sendOrder[i] = i sendOrder[i] = i
@ -374,22 +340,29 @@ func TestNotary(t *testing.T) {
}) })
for i := range requests { for i := range requests {
ntr1.OnNewRequest(requests[sendOrder[i]]) ntr1.OnNewRequest(requests[sendOrder[i]])
checkSigTx(t, requests, i+1, shouldComplete) checkMainTx(t, requesters, requests, i+1, shouldComplete)
completedCount := len(completedTxes) completedCount := len(completedTxes)
// check that the same request won't be processed twice // check that the same request won't be processed twice
ntr1.OnNewRequest(requests[sendOrder[i]]) ntr1.OnNewRequest(requests[sendOrder[i]])
checkSigTx(t, requests, i+1, shouldComplete) checkMainTx(t, requesters, requests, i+1, shouldComplete)
require.Equal(t, completedCount, len(completedTxes)) require.Equal(t, completedCount, len(completedTxes))
} }
return requests return requests, requesters
} }
checkCompleteMultisigRequest := func(t *testing.T, nSigs int, nKeys int, shouldComplete bool) []*payload.P2PNotaryRequest { checkCompleteMultisigRequest := func(t *testing.T, nSigs int, nKeys int, shouldComplete bool) ([]*payload.P2PNotaryRequest, []requester) {
requesters := make([]*wallet.Account, nKeys) accounts := make([]*wallet.Account, nKeys)
for i := range requesters { for i := range accounts {
requesters[i], _ = wallet.NewAccount() accounts[i], _ = wallet.NewAccount()
} }
requests := createMultisigRequest(nSigs, requesters) requesters := []requester{
{
accounts: accounts,
m: nSigs,
typ: notary.MultiSignature,
},
}
requests := createMixedRequest(requesters)
sendOrder := make([]int, nKeys) sendOrder := make([]int, nKeys)
for i := range sendOrder { for i := range sendOrder {
sendOrder[i] = i sendOrder[i] = i
@ -404,11 +377,11 @@ func TestNotary(t *testing.T) {
submittedRequests = append(submittedRequests, requests[sendOrder[i]]) submittedRequests = append(submittedRequests, requests[sendOrder[i]])
ntr1.OnNewRequest(requests[sendOrder[i]]) ntr1.OnNewRequest(requests[sendOrder[i]])
checkMultisigTx(t, nSigs, submittedRequests, i+1, shouldComplete) checkMainTx(t, requesters, submittedRequests, i+1, shouldComplete)
// check that the same request won't be processed twice // check that the same request won't be processed twice
ntr1.OnNewRequest(requests[sendOrder[i]]) ntr1.OnNewRequest(requests[sendOrder[i]])
checkMultisigTx(t, nSigs, submittedRequests, i+1, shouldComplete) checkMainTx(t, requesters, submittedRequests, i+1, shouldComplete)
} }
// sent the rest (n-m) out of n requests: main tx is already collected, so only fallbacks should be applied // sent the rest (n-m) out of n requests: main tx is already collected, so only fallbacks should be applied
@ -417,120 +390,161 @@ func TestNotary(t *testing.T) {
submittedRequests = append(submittedRequests, requests[sendOrder[i]]) submittedRequests = append(submittedRequests, requests[sendOrder[i]])
ntr1.OnNewRequest(requests[sendOrder[i]]) ntr1.OnNewRequest(requests[sendOrder[i]])
checkMultisigTx(t, nSigs, submittedRequests, i+1, shouldComplete) checkMainTx(t, requesters, submittedRequests, i+1, shouldComplete)
require.Equal(t, completedCount, len(completedTxes)) require.Equal(t, completedCount, len(completedTxes))
} }
return submittedRequests return submittedRequests, requesters
}
checkCompleteMixedRequest := func(t *testing.T, nSigSigners int, shouldComplete bool) ([]*payload.P2PNotaryRequest, []requester) {
requesters := make([]requester, nSigSigners)
for i := range requesters {
acc, _ := wallet.NewAccount()
requesters[i] = requester{
accounts: []*wallet.Account{acc},
typ: notary.Signature,
}
}
multisigAccounts := make([]*wallet.Account, 3)
for i := range multisigAccounts {
multisigAccounts[i], _ = wallet.NewAccount()
}
requesters = append(requesters, requester{
accounts: multisigAccounts,
m: 2,
typ: notary.MultiSignature,
})
requests := createMixedRequest(requesters)
for i := range requests {
ntr1.OnNewRequest(requests[i])
checkMainTx(t, requesters, requests, i+1, shouldComplete)
completedCount := len(completedTxes)
// check that the same request won't be processed twice
ntr1.OnNewRequest(requests[i])
checkMainTx(t, requesters, requests, i+1, shouldComplete)
require.Equal(t, completedCount, len(completedTxes))
}
return requests, requesters
} }
// OnNewRequest: missing account // OnNewRequest: missing account
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()}) ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
r := checkCompleteStandardRequest(t, 1, false) r, _ := checkCompleteStandardRequest(t, 1, false)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
// set account back for the next tests // set account back for the next tests
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()}) ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
// OnNewRequest: signature request // OnNewRequest: signature request
for _, i := range []int{1, 2, 3, 10} { for _, i := range []int{1, 2, 3, 10} {
r := checkCompleteStandardRequest(t, i, true) r, _ := checkCompleteStandardRequest(t, i, true)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
} }
// OnNewRequest: multisignature request // OnNewRequest: multisignature request
r = checkCompleteMultisigRequest(t, 1, 1, true) r, _ = checkCompleteMultisigRequest(t, 1, 1, true)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
r = checkCompleteMultisigRequest(t, 1, 2, true) r, _ = checkCompleteMultisigRequest(t, 1, 2, true)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
r = checkCompleteMultisigRequest(t, 1, 3, true) r, _ = checkCompleteMultisigRequest(t, 1, 3, true)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
r = checkCompleteMultisigRequest(t, 3, 3, true) r, _ = checkCompleteMultisigRequest(t, 3, 3, true)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
r = checkCompleteMultisigRequest(t, 3, 4, true) r, _ = checkCompleteMultisigRequest(t, 3, 4, true)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
r = checkCompleteMultisigRequest(t, 3, 10, true) r, _ = checkCompleteMultisigRequest(t, 3, 10, true)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
// OnNewRequest: mixed request
r, _ = checkCompleteMixedRequest(t, 1, true)
checkFallbackTxs(t, r, false)
r, _ = checkCompleteMixedRequest(t, 2, true)
checkFallbackTxs(t, r, false)
r, _ = checkCompleteMixedRequest(t, 3, true)
checkFallbackTxs(t, r, false)
// PostPersist: missing account // PostPersist: missing account
setFinalizeWithError(true) setFinalizeWithError(true)
r = checkCompleteStandardRequest(t, 1, false) r, requesters := checkCompleteStandardRequest(t, 1, false)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()}) ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, r, 1, false) checkMainTx(t, requesters, r, 1, false)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
// set account back for the next tests // set account back for the next tests
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()}) ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
// PostPersist: complete main transaction, signature request // PostPersist: complete main transaction, signature request
setFinalizeWithError(true) setFinalizeWithError(true)
requests := checkCompleteStandardRequest(t, 3, false) requests, requesters := checkCompleteStandardRequest(t, 3, false)
// check PostPersist with finalisation error // check PostPersist with finalisation error
setFinalizeWithError(true) setFinalizeWithError(true)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
// check PostPersist without finalisation error // check PostPersist without finalisation error
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, requests, len(requests), true) checkMainTx(t, requesters, requests, len(requests), true)
// PostPersist: complete main transaction, multisignature account // PostPersist: complete main transaction, multisignature account
setFinalizeWithError(true) setFinalizeWithError(true)
requests = checkCompleteMultisigRequest(t, 3, 4, false) requests, requesters = checkCompleteMultisigRequest(t, 3, 4, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// check PostPersist with finalisation error // check PostPersist with finalisation error
setFinalizeWithError(true) setFinalizeWithError(true)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkMultisigTx(t, 3, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// check PostPersist without finalisation error // check PostPersist without finalisation error
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkMultisigTx(t, 3, requests, len(requests), true) checkMainTx(t, requesters, requests, len(requests), true)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// PostPersist: complete fallback, signature request // PostPersist: complete fallback, signature request
setFinalizeWithError(true) setFinalizeWithError(true)
requests = checkCompleteStandardRequest(t, 3, false) requests, requesters = checkCompleteStandardRequest(t, 3, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid // make fallbacks valid
_, err = bc.genBlocks(int(nvbDiffFallback)) _, err = bc.genBlocks(int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
// check PostPersist for valid fallbacks with finalisation error // check PostPersist for valid fallbacks with finalisation error
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// check PostPersist for valid fallbacks without finalisation error // check PostPersist for valid fallbacks without finalisation error
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, true) checkFallbackTxs(t, requests, true)
// PostPersist: complete fallback, multisignature request // PostPersist: complete fallback, multisignature request
nSigs, nKeys := 3, 5 nSigs, nKeys := 3, 5
// check OnNewRequest with finalization error // check OnNewRequest with finalization error
setFinalizeWithError(true) setFinalizeWithError(true)
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false) requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid // make fallbacks valid
_, err = bc.genBlocks(int(nvbDiffFallback)) _, err = bc.genBlocks(int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
// check PostPersist for valid fallbacks with finalisation error // check PostPersist for valid fallbacks with finalisation error
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkMultisigTx(t, nSigs, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// check PostPersist for valid fallbacks without finalisation error // check PostPersist for valid fallbacks without finalisation error
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkMultisigTx(t, nSigs, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:nSigs], true) checkFallbackTxs(t, requests[:nSigs], true)
// the rest of fallbacks should also be applied even if the main tx was already constructed by the moment they were sent // the rest of fallbacks should also be applied even if the main tx was already constructed by the moment they were sent
checkFallbackTxs(t, requests[nSigs:], true) checkFallbackTxs(t, requests[nSigs:], true)
// PostPersist: partial fallbacks completion due to finalisation errors // PostPersist: partial fallbacks completion due to finalisation errors
setFinalizeWithError(true) setFinalizeWithError(true)
requests = checkCompleteStandardRequest(t, 5, false) requests, requesters = checkCompleteStandardRequest(t, 5, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid // make fallbacks valid
_, err = bc.genBlocks(int(nvbDiffFallback)) _, err = bc.genBlocks(int(nvbDiffFallback))
@ -541,7 +555,7 @@ func TestNotary(t *testing.T) {
setChoosy(true) setChoosy(true)
// check PostPersist for lucky fallbacks // check PostPersist for lucky fallbacks
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, lucky, true) checkFallbackTxs(t, lucky, true)
checkFallbackTxs(t, unluckies, false) checkFallbackTxs(t, unluckies, false)
// reset finalisation function for unlucky fallbacks to finalise without an error // reset finalisation function for unlucky fallbacks to finalise without an error
@ -549,14 +563,14 @@ func TestNotary(t *testing.T) {
setFinalizeWithError(false) setFinalizeWithError(false)
// check PostPersist for unlucky fallbacks // check PostPersist for unlucky fallbacks
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, lucky, true) checkFallbackTxs(t, lucky, true)
checkFallbackTxs(t, unluckies, true) checkFallbackTxs(t, unluckies, true)
// PostPersist: different NVBs // PostPersist: different NVBs
// check OnNewRequest with finalization error and different NVBs // check OnNewRequest with finalization error and different NVBs
setFinalizeWithError(true) setFinalizeWithError(true)
requests = checkCompleteStandardRequest(t, 5, false, 1, 2, 3, 4, 5) requests, requesters = checkCompleteStandardRequest(t, 5, false, 1, 2, 3, 4, 5)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// generate blocks to reach the most earlier fallback's NVB // generate blocks to reach the most earlier fallback's NVB
_, err = bc.genBlocks(int(nvbDiffFallback)) _, err = bc.genBlocks(int(nvbDiffFallback))
@ -578,7 +592,7 @@ func TestNotary(t *testing.T) {
defer mtx.RUnlock() defer mtx.RUnlock()
return len(completedTxes)-start >= i+1 return len(completedTxes)-start >= i+1
}, time.Second*3, time.Millisecond) }, time.Second*3, time.Millisecond)
checkSigTx(t, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:i+1], true) checkFallbackTxs(t, requests[:i+1], true)
checkFallbackTxs(t, requests[i+1:], false) checkFallbackTxs(t, requests[i+1:], false)
} }
@ -586,7 +600,7 @@ func TestNotary(t *testing.T) {
// OnRequestRemoval: missing account // OnRequestRemoval: missing account
// check OnNewRequest with finalization error // check OnNewRequest with finalization error
setFinalizeWithError(true) setFinalizeWithError(true)
requests = checkCompleteStandardRequest(t, 4, false) requests, requesters = checkCompleteStandardRequest(t, 4, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid and remove one fallback // make fallbacks valid and remove one fallback
_, err = bc.genBlocks(int(nvbDiffFallback)) _, err = bc.genBlocks(int(nvbDiffFallback))
@ -596,7 +610,7 @@ func TestNotary(t *testing.T) {
// non of the fallbacks should be completed // non of the fallbacks should be completed
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// set account back for the next tests // set account back for the next tests
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()}) ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
@ -604,7 +618,7 @@ func TestNotary(t *testing.T) {
// OnRequestRemoval: signature request, remove one fallback // OnRequestRemoval: signature request, remove one fallback
// check OnNewRequest with finalization error // check OnNewRequest with finalization error
setFinalizeWithError(true) setFinalizeWithError(true)
requests = checkCompleteStandardRequest(t, 4, false) requests, requesters = checkCompleteStandardRequest(t, 4, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid and remove one fallback // make fallbacks valid and remove one fallback
_, err = bc.genBlocks(int(nvbDiffFallback)) _, err = bc.genBlocks(int(nvbDiffFallback))
@ -614,13 +628,13 @@ func TestNotary(t *testing.T) {
// rest of the fallbacks should be completed // rest of the fallbacks should be completed
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:3], true) checkFallbackTxs(t, requests[:3], true)
require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()]) require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()])
// OnRequestRemoval: signature request, remove all fallbacks // OnRequestRemoval: signature request, remove all fallbacks
setFinalizeWithError(true) setFinalizeWithError(true)
requests = checkCompleteStandardRequest(t, 4, false) requests, requesters = checkCompleteStandardRequest(t, 4, false)
// remove all fallbacks // remove all fallbacks
_, err = bc.genBlocks(int(nvbDiffFallback)) _, err = bc.genBlocks(int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
@ -630,21 +644,21 @@ func TestNotary(t *testing.T) {
// then the whole request should be removed, i.e. there are no completed transactions // then the whole request should be removed, i.e. there are no completed transactions
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// OnRequestRemoval: signature request, remove unexisting fallback // OnRequestRemoval: signature request, remove unexisting fallback
ntr1.OnRequestRemoval(requests[0]) ntr1.OnRequestRemoval(requests[0])
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkSigTx(t, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// OnRequestRemoval: multisignature request, remove one fallback // OnRequestRemoval: multisignature request, remove one fallback
nSigs, nKeys = 3, 5 nSigs, nKeys = 3, 5
// check OnNewRequest with finalization error // check OnNewRequest with finalization error
setFinalizeWithError(true) setFinalizeWithError(true)
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false) requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
checkMultisigTx(t, nSigs, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid and remove the last fallback // make fallbacks valid and remove the last fallback
_, err = bc.genBlocks(int(nvbDiffFallback)) _, err = bc.genBlocks(int(nvbDiffFallback))
@ -654,7 +668,7 @@ func TestNotary(t *testing.T) {
// then (m-1) out of n fallbacks should be completed // then (m-1) out of n fallbacks should be completed
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkMultisigTx(t, nSigs, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:nSigs-1], true) checkFallbackTxs(t, requests[:nSigs-1], true)
require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()]) require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()])
// the rest (n-(m-1)) out of n fallbacks should also be completed even if main tx has been collected by the moment they were sent // the rest (n-(m-1)) out of n fallbacks should also be completed even if main tx has been collected by the moment they were sent
@ -662,7 +676,7 @@ func TestNotary(t *testing.T) {
// OnRequestRemoval: multisignature request, remove all fallbacks // OnRequestRemoval: multisignature request, remove all fallbacks
setFinalizeWithError(true) setFinalizeWithError(true)
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false) requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
// make fallbacks valid and then remove all of them // make fallbacks valid and then remove all of them
_, err = bc.genBlocks(int(nvbDiffFallback)) _, err = bc.genBlocks(int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
@ -672,13 +686,13 @@ func TestNotary(t *testing.T) {
// then the whole request should be removed, i.e. there are no completed transactions // then the whole request should be removed, i.e. there are no completed transactions
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkMultisigTx(t, nSigs, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// // OnRequestRemoval: multisignature request, remove unexisting fallbac, i.e. there still shouldn't be any completed transactions after this // // OnRequestRemoval: multisignature request, remove unexisting fallbac, i.e. there still shouldn't be any completed transactions after this
ntr1.OnRequestRemoval(requests[0]) ntr1.OnRequestRemoval(requests[0])
require.NoError(t, bc.AddBlock(bc.newBlock())) require.NoError(t, bc.AddBlock(bc.newBlock()))
checkMultisigTx(t, nSigs, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// Subscriptions test // Subscriptions test
@ -692,7 +706,16 @@ func TestNotary(t *testing.T) {
transferTokenFromMultisigAccountCheckOK(t, bc, bc.GetNotaryContractScriptHash(), bc.contracts.GAS.Hash, amount, requester2.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight()+50)) transferTokenFromMultisigAccountCheckOK(t, bc, bc.GetNotaryContractScriptHash(), bc.contracts.GAS.Hash, amount, requester2.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight()+50))
checkBalanceOf(t, bc, bc.contracts.Notary.Hash, int(2*amount)) checkBalanceOf(t, bc, bc.contracts.Notary.Hash, int(2*amount))
// create request for 2 standard signatures => main tx should be completed after the second request is added to the pool // create request for 2 standard signatures => main tx should be completed after the second request is added to the pool
requests = createStandardRequest([]*wallet.Account{requester1, requester2}) requests = createMixedRequest([]requester{
{
accounts: []*wallet.Account{requester1},
typ: notary.Signature,
},
{
accounts: []*wallet.Account{requester2},
typ: notary.Signature,
},
})
require.NoError(t, mp1.Add(requests[0].FallbackTransaction, feer, requests[0])) require.NoError(t, mp1.Add(requests[0].FallbackTransaction, feer, requests[0]))
require.NoError(t, mp1.Add(requests[1].FallbackTransaction, feer, requests[1])) require.NoError(t, mp1.Add(requests[1].FallbackTransaction, feer, requests[1]))
require.Eventually(t, func() bool { require.Eventually(t, func() bool {

View file

@ -67,25 +67,47 @@ type (
const defaultTxChannelCapacity = 100 const defaultTxChannelCapacity = 100
// request represents Notary service request. type (
type request struct { // request represents Notary service request.
typ RequestType request struct {
// isSent indicates whether main transaction was successfully sent to the network. // isSent indicates whether main transaction was successfully sent to the network.
isSent bool isSent bool
main *transaction.Transaction main *transaction.Transaction
// minNotValidBefore is the minimum NVB value among fallbacks transactions. // minNotValidBefore is the minimum NVB value among fallbacks transactions.
// We stop trying to send mainTx to the network if the chain reaches minNotValidBefore height. // We stop trying to send mainTx to the network if the chain reaches minNotValidBefore height.
minNotValidBefore uint32 minNotValidBefore uint32
fallbacks []*transaction.Transaction fallbacks []*transaction.Transaction
// nSigsLeft is the number of signatures left to collect to complete main transaction.
// Initial nSigsLeft value is defined as following:
// nSigsLeft == nKeys for standard signature request;
// nSigsLeft <= nKeys for multisignature request;
// nSigsLeft == 0 when all received requests were invalid, so check request.typ before access to nSigs.
nSigsLeft uint8
// sigs is a map of partial multisig invocation scripts [opcode.PUSHDATA1+64+signatureBytes] grouped by public keys witnessInfo []witnessInfo
sigs map[*keys.PublicKey][]byte }
// witnessInfo represents information about signer and its witness.
witnessInfo struct {
typ RequestType
// nSigsLeft is the number of signatures left to collect to complete main transaction.
// Initial nSigsLeft value is defined as following:
// nSigsLeft == nKeys for standard signature request;
// nSigsLeft <= nKeys for multisignature request;
nSigsLeft uint8
// sigs is a map of partial multisig invocation scripts [opcode.PUSHDATA1+64+signatureBytes] grouped by public keys.
sigs map[*keys.PublicKey][]byte
// pubs is a set of public keys participating in the multisignature witness collection.
pubs keys.PublicKeys
}
)
// isMainCompleted denotes whether all signatures for the main transaction was collected.
func (r request) isMainCompleted() bool {
if r.witnessInfo == nil {
return false
}
for _, wi := range r.witnessInfo {
if wi.nSigsLeft != 0 {
return false
}
}
return true
} }
// NewNotary returns new Notary module. // NewNotary returns new Notary module.
@ -163,7 +185,13 @@ func (n *Notary) OnNewRequest(payload *payload.P2PNotaryRequest) {
nvbFallback := payload.FallbackTransaction.GetAttributes(transaction.NotValidBeforeT)[0].Value.(*transaction.NotValidBefore).Height nvbFallback := payload.FallbackTransaction.GetAttributes(transaction.NotValidBeforeT)[0].Value.(*transaction.NotValidBefore).Height
nKeys := payload.MainTransaction.GetAttributes(transaction.NotaryAssistedT)[0].Value.(*transaction.NotaryAssisted).NKeys nKeys := payload.MainTransaction.GetAttributes(transaction.NotaryAssistedT)[0].Value.(*transaction.NotaryAssisted).NKeys
typ, nSigs, pubs, validationErr := n.verifyIncompleteWitnesses(payload.MainTransaction, nKeys) newInfo, validationErr := n.verifyIncompleteWitnesses(payload.MainTransaction, nKeys)
if validationErr != nil {
n.Config.Log.Info("verification of main notary transaction failed; fallback transaction will be completed",
zap.String("main hash", payload.MainTransaction.Hash().StringLE()),
zap.String("fallback hash", payload.FallbackTransaction.Hash().StringLE()),
zap.String("verification error", validationErr.Error()))
}
n.reqMtx.Lock() n.reqMtx.Lock()
defer n.reqMtx.Unlock() defer n.reqMtx.Unlock()
r, exists := n.requests[payload.MainTransaction.Hash()] r, exists := n.requests[payload.MainTransaction.Hash()]
@ -176,75 +204,66 @@ func (n *Notary) OnNewRequest(payload *payload.P2PNotaryRequest) {
if nvbFallback < r.minNotValidBefore { if nvbFallback < r.minNotValidBefore {
r.minNotValidBefore = nvbFallback r.minNotValidBefore = nvbFallback
} }
if r.typ == Unknown && validationErr == nil {
r.typ = typ
r.nSigsLeft = nSigs
}
} else { } else {
r = &request{ r = &request{
nSigsLeft: nSigs,
main: payload.MainTransaction, main: payload.MainTransaction,
typ: typ,
minNotValidBefore: nvbFallback, minNotValidBefore: nvbFallback,
} }
n.requests[payload.MainTransaction.Hash()] = r n.requests[payload.MainTransaction.Hash()] = r
} }
if r.witnessInfo == nil && validationErr == nil {
r.witnessInfo = newInfo
}
r.fallbacks = append(r.fallbacks, payload.FallbackTransaction) r.fallbacks = append(r.fallbacks, payload.FallbackTransaction)
if exists && r.typ != Unknown && r.nSigsLeft == 0 { // already collected sufficient number of signatures to complete main transaction if exists && r.isMainCompleted() || validationErr != nil {
return return
} }
if validationErr == nil { mainHash := hash.NetSha256(uint32(n.Network), r.main).BytesBE()
loop: for i, w := range payload.MainTransaction.Scripts {
for i, w := range payload.MainTransaction.Scripts { if r.witnessInfo[i].typ == Contract || // check that we need to fill that witness
if payload.MainTransaction.Signers[i].Account.Equals(n.Config.Chain.GetNotaryContractScriptHash()) { len(w.InvocationScript) == 0 || // check that signature for this witness was provided
continue r.witnessInfo[i].nSigsLeft == 0 { // check that signature wasn't yet added (consider receiving the same payload multiple times)
continue
}
switch r.witnessInfo[i].typ {
case Signature:
if r.witnessInfo[i].pubs[0].Verify(w.InvocationScript[2:], mainHash) {
r.main.Scripts[i] = w
r.witnessInfo[i].nSigsLeft--
}
case MultiSignature:
if r.witnessInfo[i].sigs == nil {
r.witnessInfo[i].sigs = make(map[*keys.PublicKey][]byte)
} }
if len(w.InvocationScript) != 0 && len(w.VerificationScript) != 0 {
switch r.typ {
case Signature:
if !exists {
r.nSigsLeft--
} else if len(r.main.Scripts[i].InvocationScript) == 0 { // need this check because signature can already be added (consider receiving the same payload multiple times)
r.main.Scripts[i] = w
r.nSigsLeft--
}
if r.nSigsLeft == 0 {
break loop
}
case MultiSignature:
if r.sigs == nil {
r.sigs = make(map[*keys.PublicKey][]byte)
}
hash := hash.NetSha256(uint32(n.Network), r.main).BytesBE() for _, pub := range r.witnessInfo[i].pubs {
for _, pub := range pubs { if r.witnessInfo[i].sigs[pub] != nil {
if r.sigs[pub] != nil { continue // signature for this pub has already been added
continue // signature for this pub has already been added }
} if pub.Verify(w.InvocationScript[2:], mainHash) { // then pub is the owner of the signature
if pub.Verify(w.InvocationScript[2:], hash) { // then pub is the owner of the signature r.witnessInfo[i].sigs[pub] = w.InvocationScript
r.sigs[pub] = w.InvocationScript r.witnessInfo[i].nSigsLeft--
r.nSigsLeft-- if r.witnessInfo[i].nSigsLeft == 0 {
if r.nSigsLeft == 0 { var invScript []byte
var invScript []byte for j := range r.witnessInfo[i].pubs {
for j := range pubs { if sig, ok := r.witnessInfo[i].sigs[r.witnessInfo[i].pubs[j]]; ok {
if sig, ok := r.sigs[pubs[j]]; ok { invScript = append(invScript, sig...)
invScript = append(invScript, sig...)
}
}
r.main.Scripts[i].InvocationScript = invScript
} }
break loop
} }
r.main.Scripts[i].InvocationScript = invScript
} }
// pubKey was not found for the signature i.e. signature is bad - we're OK with that, let the fallback TX to be added break
break loop // only one multisignature is allowed
} }
} }
// pubKey was not found for the signature (i.e. signature is bad) or the signature has already
// been added - we're OK with that, let the fallback TX to be added
} }
} }
if r.typ != Unknown && r.nSigsLeft == 0 && r.minNotValidBefore > n.Config.Chain.BlockHeight() { if r.isMainCompleted() && r.minNotValidBefore > n.Config.Chain.BlockHeight() {
if err := n.finalize(acc, r.main, payload.MainTransaction.Hash()); err != nil { if err := n.finalize(acc, r.main, payload.MainTransaction.Hash()); err != nil {
n.Config.Log.Error("failed to finalize main transaction", zap.Error(err)) n.Config.Log.Error("failed to finalize main transaction",
zap.String("hash", r.main.Hash().StringLE()),
zap.Error(err))
} }
} }
} }
@ -285,7 +304,7 @@ func (n *Notary) PostPersist() {
defer n.reqMtx.Unlock() defer n.reqMtx.Unlock()
currHeight := n.Config.Chain.BlockHeight() currHeight := n.Config.Chain.BlockHeight()
for h, r := range n.requests { for h, r := range n.requests {
if !r.isSent && r.typ != Unknown && r.nSigsLeft == 0 && r.minNotValidBefore > currHeight { if !r.isSent && r.isMainCompleted() && r.minNotValidBefore > currHeight {
if err := n.finalize(acc, r.main, h); err != nil { if err := n.finalize(acc, r.main, h); err != nil {
n.Config.Log.Error("failed to finalize main transaction", zap.Error(err)) n.Config.Log.Error("failed to finalize main transaction", zap.Error(err))
} }
@ -404,74 +423,67 @@ func updateTxSize(tx *transaction.Transaction) (*transaction.Transaction, error)
// verifyIncompleteWitnesses checks that tx either doesn't have all witnesses attached (in this case none of them // verifyIncompleteWitnesses checks that tx either doesn't have all witnesses attached (in this case none of them
// can be multisignature), or it only has a partial multisignature. It returns the request type (sig/multisig), the // can be multisignature), or it only has a partial multisignature. It returns the request type (sig/multisig), the
// number of signatures to be collected, sorted public keys (for multisig request only) and an error. // number of signatures to be collected, sorted public keys (for multisig request only) and an error.
func (n *Notary) verifyIncompleteWitnesses(tx *transaction.Transaction, nKeys uint8) (RequestType, uint8, keys.PublicKeys, error) { func (n *Notary) verifyIncompleteWitnesses(tx *transaction.Transaction, nKeysExpected uint8) ([]witnessInfo, error) {
var ( var nKeysActual uint8
typ RequestType
nSigs int
nKeysActual uint8
pubsBytes [][]byte
pubs keys.PublicKeys
ok bool
)
if len(tx.Signers) < 2 { if len(tx.Signers) < 2 {
return Unknown, 0, nil, errors.New("transaction should have at least 2 signers") return nil, errors.New("transaction should have at least 2 signers")
} }
if !tx.HasSigner(n.Config.Chain.GetNotaryContractScriptHash()) { if !tx.HasSigner(n.Config.Chain.GetNotaryContractScriptHash()) {
return Unknown, 0, nil, fmt.Errorf("P2PNotary contract should be a signer of the transaction") return nil, fmt.Errorf("P2PNotary contract should be a signer of the transaction")
} }
result := make([]witnessInfo, len(tx.Signers))
for i, w := range tx.Scripts { for i, w := range tx.Scripts {
// do not check witness for Notary contract -- it will be replaced by proper witness in any case. // Do not check witness for Notary contract -- it will be replaced by proper witness in any case.
if tx.Signers[i].Account == n.Config.Chain.GetNotaryContractScriptHash() { // Also do not check other contract-based witnesses (they can be combined with anything)
continue
}
if len(w.VerificationScript) == 0 { if len(w.VerificationScript) == 0 {
// then it's a contract verification (can be combined with anything) result[i] = witnessInfo{
typ: Contract,
nSigsLeft: 0,
}
continue continue
} }
if !tx.Signers[i].Account.Equals(hash.Hash160(w.VerificationScript)) { // https://github.com/nspcc-dev/neo-go/pull/1658#discussion_r564265987 if !tx.Signers[i].Account.Equals(hash.Hash160(w.VerificationScript)) { // https://github.com/nspcc-dev/neo-go/pull/1658#discussion_r564265987
return Unknown, 0, nil, fmt.Errorf("transaction should have valid verification script for signer #%d", i) return nil, fmt.Errorf("transaction should have valid verification script for signer #%d", i)
} }
if nSigs, pubsBytes, ok = vm.ParseMultiSigContract(w.VerificationScript); ok { // Each verification script is allowed to have either one signature or zero signatures. If signature is provided, then need to verify it.
if typ == Signature || typ == MultiSignature { if len(w.InvocationScript) != 0 {
return Unknown, 0, nil, fmt.Errorf("bad type of witness #%d: only one multisignature witness is allowed", i)
}
typ = MultiSignature
nKeysActual = uint8(len(pubsBytes))
if len(w.InvocationScript) != 66 || !bytes.HasPrefix(w.InvocationScript, []byte{byte(opcode.PUSHDATA1), 64}) { if len(w.InvocationScript) != 66 || !bytes.HasPrefix(w.InvocationScript, []byte{byte(opcode.PUSHDATA1), 64}) {
return Unknown, 0, nil, fmt.Errorf("multisignature invocation script should have length = 66 and be of the form [PUSHDATA1, 64, signatureBytes...]") return nil, fmt.Errorf("witness #%d: invocation script should have length = 66 and be of the form [PUSHDATA1, 64, signatureBytes...]", i)
} }
}
if nSigs, pubsBytes, ok := vm.ParseMultiSigContract(w.VerificationScript); ok {
result[i] = witnessInfo{
typ: MultiSignature,
nSigsLeft: uint8(nSigs),
pubs: make(keys.PublicKeys, len(pubsBytes)),
}
for j, pBytes := range pubsBytes {
pub, err := keys.NewPublicKeyFromBytes(pBytes, elliptic.P256())
if err != nil {
return nil, fmt.Errorf("witness #%d: invalid bytes of #%d public key: %s", i, j, hex.EncodeToString(pBytes))
}
result[i].pubs[j] = pub
}
nKeysActual += uint8(len(pubsBytes))
continue continue
} }
if vm.IsSignatureContract(w.VerificationScript) { if pBytes, ok := vm.ParseSignatureContract(w.VerificationScript); ok {
if typ == MultiSignature {
return Unknown, 0, nil, fmt.Errorf("bad type of witness #%d: multisignature witness can not be combined with other witnesses", i)
}
typ = Signature
nSigs = int(nKeys)
continue
}
return Unknown, 0, nil, fmt.Errorf("unable to define the type of witness #%d", i)
}
switch typ {
case Signature:
if len(tx.Scripts) < int(nKeys+1) {
return Unknown, 0, nil, fmt.Errorf("transaction should comtain at least %d witnesses (1 for notary + nKeys)", nKeys+1)
}
case MultiSignature:
if nKeysActual != nKeys {
return Unknown, 0, nil, fmt.Errorf("bad m out of n partial multisignature witness: expected n = %d, got n = %d", nKeys, nKeysActual)
}
pubs = make(keys.PublicKeys, len(pubsBytes))
for i, pBytes := range pubsBytes {
pub, err := keys.NewPublicKeyFromBytes(pBytes, elliptic.P256()) pub, err := keys.NewPublicKeyFromBytes(pBytes, elliptic.P256())
if err != nil { if err != nil {
return Unknown, 0, nil, fmt.Errorf("invalid bytes of #%d public key: %s", i, hex.EncodeToString(pBytes)) return nil, fmt.Errorf("witness #%d: invalid bytes of public key: %s", i, hex.EncodeToString(pBytes))
} }
pubs[i] = pub result[i] = witnessInfo{
typ: Signature,
nSigsLeft: 1,
pubs: keys.PublicKeys{pub},
}
nKeysActual++
continue
} }
default: return nil, fmt.Errorf("witness #%d: unable to detect witness type, only sig/multisig/contract are supported", i)
return Unknown, 0, nil, errors.New("unexpected Notary request type")
} }
return typ, uint8(nSigs), pubs, nil if nKeysActual != nKeysExpected {
return nil, fmt.Errorf("expected and actual NKeys mismatch: %d vs %d", nKeysExpected, nKeysActual)
}
return result, nil
} }

View file

@ -13,7 +13,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest"
) )
@ -67,11 +66,9 @@ func TestVerifyIncompleteRequest(t *testing.T) {
multisigScriptHash2 := hash.Hash160(multisigScript2) multisigScriptHash2 := hash.Hash160(multisigScript2)
checkErr := func(t *testing.T, tx *transaction.Transaction, nKeys uint8) { checkErr := func(t *testing.T, tx *transaction.Transaction, nKeys uint8) {
typ, nSigs, pubs, err := ntr.verifyIncompleteWitnesses(tx, nKeys) witnessInfo, err := ntr.verifyIncompleteWitnesses(tx, nKeys)
require.Error(t, err) require.Error(t, err)
require.Equal(t, Unknown, typ) require.Nil(t, witnessInfo)
require.Equal(t, uint8(0), nSigs)
require.Nil(t, pubs)
} }
errCases := map[string]struct { errCases := map[string]struct {
@ -90,15 +87,6 @@ func TestVerifyIncompleteRequest(t *testing.T) {
Scripts: []transaction.Witness{{}, {}}, Scripts: []transaction.Witness{{}, {}},
}, },
}, },
"unknown witness type": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{},
{},
},
},
},
"bad verification script": { "bad verification script": {
tx: &transaction.Transaction{ tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}}, Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
@ -111,125 +99,6 @@ func TestVerifyIncompleteRequest(t *testing.T) {
}, },
}, },
}, },
"several multisig witnesses": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: multisigScriptHash2}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{
InvocationScript: sig,
VerificationScript: multisigScript2,
},
{},
},
},
nKeys: 2,
},
"multisig + sig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{
InvocationScript: sig,
VerificationScript: sigScript1,
},
{},
},
},
nKeys: 2,
},
"sig + multisig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: sigScript1,
},
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{},
},
},
nKeys: 2,
},
"empty multisig + sig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: []byte{},
VerificationScript: multisigScript1,
},
{
InvocationScript: sig,
VerificationScript: sigScript1,
},
{},
},
},
nKeys: 2,
},
"sig + empty multisig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: sigScript1,
},
{
InvocationScript: []byte{},
VerificationScript: multisigScript1,
},
{},
},
},
nKeys: 2,
},
"multisig + empty sig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{
InvocationScript: []byte{},
VerificationScript: sigScript1,
},
{},
},
},
nKeys: 2,
},
"empty sig + multisig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: []byte{},
VerificationScript: sigScript1,
},
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{},
},
},
nKeys: 2,
},
"sig: bad nKeys": { "sig: bad nKeys": {
tx: &transaction.Transaction{ tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: acc2.PublicKey().GetScriptHash()}, {Account: notaryContractHash}}, Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: acc2.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
@ -281,11 +150,9 @@ func TestVerifyIncompleteRequest(t *testing.T) {
} }
testCases := map[string]struct { testCases := map[string]struct {
tx *transaction.Transaction tx *transaction.Transaction
nKeys uint8 nKeys uint8
expectedType RequestType expectedInfo []witnessInfo
expectedNSigs uint8
expectedPubs keys.PublicKeys
}{ }{
"single sig": { "single sig": {
tx: &transaction.Transaction{ tx: &transaction.Transaction{
@ -298,9 +165,11 @@ func TestVerifyIncompleteRequest(t *testing.T) {
{}, {},
}, },
}, },
nKeys: 1, nKeys: 1,
expectedType: Signature, expectedInfo: []witnessInfo{
expectedNSigs: 1, {typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
{typ: Contract},
},
}, },
"multiple sig": { "multiple sig": {
tx: &transaction.Transaction{ tx: &transaction.Transaction{
@ -312,7 +181,7 @@ func TestVerifyIncompleteRequest(t *testing.T) {
}, },
{ {
InvocationScript: []byte{}, InvocationScript: []byte{},
VerificationScript: []byte{}, VerificationScript: sigScript2,
}, },
{ {
InvocationScript: sig, InvocationScript: sig,
@ -321,11 +190,15 @@ func TestVerifyIncompleteRequest(t *testing.T) {
{}, {},
}, },
}, },
nKeys: 3, nKeys: 3,
expectedType: Signature, expectedInfo: []witnessInfo{
expectedNSigs: 3, {typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc2.PublicKey()}},
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc3.PublicKey()}},
{typ: Contract},
},
}, },
"multisig 1 out of 3": { "single multisig 1 out of 3": {
tx: &transaction.Transaction{ tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: notaryContractHash}}, Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{ Scripts: []transaction.Witness{
@ -336,12 +209,13 @@ func TestVerifyIncompleteRequest(t *testing.T) {
{}, {},
}, },
}, },
nKeys: 3, nKeys: 3,
expectedType: MultiSignature, expectedInfo: []witnessInfo{
expectedNSigs: 1, {typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}, {typ: Contract},
},
}, },
"multisig 2 out of 3": { "single multisig 2 out of 3": {
tx: &transaction.Transaction{ tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash2}, {Account: notaryContractHash}}, Signers: []transaction.Signer{{Account: multisigScriptHash2}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{ Scripts: []transaction.Witness{
@ -352,18 +226,19 @@ func TestVerifyIncompleteRequest(t *testing.T) {
{}, {},
}, },
}, },
nKeys: 3, nKeys: 3,
expectedType: MultiSignature, expectedInfo: []witnessInfo{
expectedNSigs: 2, {typ: MultiSignature, nSigsLeft: 2, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}, {typ: Contract},
},
}, },
"empty + multisig": { "empty sig + single multisig 1 out of 3": {
tx: &transaction.Transaction{ tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}}, Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{ Scripts: []transaction.Witness{
{ {
InvocationScript: []byte{}, InvocationScript: []byte{},
VerificationScript: []byte{}, VerificationScript: sigScript1,
}, },
{ {
InvocationScript: sig, InvocationScript: sig,
@ -372,12 +247,14 @@ func TestVerifyIncompleteRequest(t *testing.T) {
{}, {},
}, },
}, },
nKeys: 3, nKeys: 1 + 3,
expectedType: MultiSignature, expectedInfo: []witnessInfo{
expectedNSigs: 1, {typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}, {typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: Contract},
},
}, },
"multisig + empty": { "single multisig 1 out of 3 + empty single sig": {
tx: &transaction.Transaction{ tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}}, Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{ Scripts: []transaction.Witness{
@ -387,25 +264,228 @@ func TestVerifyIncompleteRequest(t *testing.T) {
}, },
{ {
InvocationScript: []byte{}, InvocationScript: []byte{},
VerificationScript: []byte{}, VerificationScript: sigScript1,
}, },
{}, {},
}, },
}, },
nKeys: 3, nKeys: 3 + 1,
expectedType: MultiSignature, expectedInfo: []witnessInfo{
expectedNSigs: 1, {typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}, {typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
{typ: Contract},
},
},
"several multisig witnesses": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: multisigScriptHash2}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{
InvocationScript: sig,
VerificationScript: multisigScript2,
},
{},
},
},
nKeys: 3 + 3,
expectedInfo: []witnessInfo{
{typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: MultiSignature, nSigsLeft: 2, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: Contract},
},
},
"multisig + sig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{
InvocationScript: sig,
VerificationScript: sigScript1,
},
{},
},
},
nKeys: 3 + 1,
expectedInfo: []witnessInfo{
{typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
{typ: Contract},
},
},
"sig + multisig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: sigScript1,
},
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{},
},
},
nKeys: 1 + 3,
expectedInfo: []witnessInfo{
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
{typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: Contract},
},
},
"empty multisig + sig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: []byte{},
VerificationScript: multisigScript1,
},
{
InvocationScript: sig,
VerificationScript: sigScript1,
},
{},
},
},
nKeys: 3 + 1,
expectedInfo: []witnessInfo{
{typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
{typ: Contract},
},
},
"sig + empty multisig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: sigScript1,
},
{
InvocationScript: []byte{},
VerificationScript: multisigScript1,
},
{},
},
},
nKeys: 1 + 3,
expectedInfo: []witnessInfo{
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
{typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: Contract},
},
},
"multisig + empty sig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{
InvocationScript: []byte{},
VerificationScript: sigScript1,
},
{},
},
},
nKeys: 3 + 1,
expectedInfo: []witnessInfo{
{typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
{typ: Contract},
},
},
"empty sig + multisig": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: []byte{},
VerificationScript: sigScript1,
},
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{},
},
},
nKeys: 1 + 3,
expectedInfo: []witnessInfo{
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
{typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: Contract},
},
},
"multiple sigs + multiple multisigs": {
tx: &transaction.Transaction{
Signers: []transaction.Signer{{Account: multisigScriptHash1},
{Account: acc1.PublicKey().GetScriptHash()},
{Account: acc2.PublicKey().GetScriptHash()},
{Account: acc3.PublicKey().GetScriptHash()},
{Account: multisigScriptHash2},
{Account: notaryContractHash}},
Scripts: []transaction.Witness{
{
InvocationScript: sig,
VerificationScript: multisigScript1,
},
{
InvocationScript: sig,
VerificationScript: sigScript1,
},
{
InvocationScript: []byte{},
VerificationScript: sigScript2,
},
{
InvocationScript: sig,
VerificationScript: sigScript3,
},
{
InvocationScript: []byte{},
VerificationScript: multisigScript2,
},
{},
},
},
nKeys: 3 + 1 + 1 + 1 + 3,
expectedInfo: []witnessInfo{
{typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc2.PublicKey()}},
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc3.PublicKey()}},
{typ: MultiSignature, nSigsLeft: 2, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
{typ: Contract},
},
}, },
} }
for name, testCase := range testCases { for name, testCase := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
typ, nSigs, pubs, err := ntr.verifyIncompleteWitnesses(testCase.tx, testCase.nKeys) actualInfo, err := ntr.verifyIncompleteWitnesses(testCase.tx, testCase.nKeys)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, testCase.expectedType, typ) require.Equal(t, len(testCase.expectedInfo), len(actualInfo))
assert.Equal(t, testCase.expectedNSigs, nSigs) for i, expected := range testCase.expectedInfo {
assert.ElementsMatch(t, testCase.expectedPubs, pubs) actual := actualInfo[i]
require.Equal(t, expected.typ, actual.typ)
require.Equal(t, expected.nSigsLeft, actual.nSigsLeft)
require.ElementsMatch(t, expected.pubs, actual.pubs)
require.Nil(t, actual.sigs)
}
}) })
} }
} }

View file

@ -4,10 +4,10 @@ package notary
type RequestType byte type RequestType byte
const ( const (
// Unknown represents unknown request type which means that main tx witnesses are invalid.
Unknown RequestType = 0x00
// Signature represents standard single signature request type. // Signature represents standard single signature request type.
Signature RequestType = 0x01 Signature RequestType = 0x01
// MultiSignature represents m out of n multisignature request type. // MultiSignature represents m out of n multisignature request type.
MultiSignature RequestType = 0x02 MultiSignature RequestType = 0x02
// Contract represents contract witness type.
Contract RequestType = 0x03
) )