mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-22 09:29:38 +00:00
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:
parent
fcc7f7349d
commit
75d7891ca1
5 changed files with 592 additions and 475 deletions
|
@ -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
|
||||
process of signature collection. This is the number of keys that could potentially
|
||||
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
|
||||
|
||||
|
@ -107,11 +109,13 @@ This payload has two incomplete transactions inside:
|
|||
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`
|
||||
attribute with a count of zero.
|
||||
- *Main tx*. This is the one that actually needs to be completed, it either
|
||||
doesn't have all witnesses attached (in this case none of them can be
|
||||
multisignature), or it only has a partial multisignature, currenlty only one of
|
||||
the two is allowed. This transaction must have `Notary assisted` attribute with
|
||||
a count of `NKeys` (and Notary contract as one of the signers).
|
||||
- *Main tx*. This is the one that actually needs to be completed, it:
|
||||
1. *either* doesn't have all witnesses attached
|
||||
2. *or* it only has a partial multisignature
|
||||
3. *or* have not all witnesses attached and some of the rest are partial multisignature
|
||||
|
||||
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
|
||||
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.
|
||||
* Each signer is either multisignature or standard signature or a contract
|
||||
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.
|
||||
* 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
|
||||
constraints:
|
||||
|
@ -374,7 +376,8 @@ the steps to create a signature request:
|
|||
9. Construct the list of main transactions witnesses (that will be `Scripts`
|
||||
transaction field). Use the following rules:
|
||||
- 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
|
||||
have empty `Verification` script. `Invocation` script should be of the form
|
||||
[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.
|
||||
`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
|
||||
provide proper signature) **should be of the form [opcode.PUSHDATA1, 64,
|
||||
make([]byte, 64)...]**, i.e. to be a placeholder for signature.
|
||||
provide proper signature) **should be empty**.
|
||||
10. Define lifetime for the fallback transaction. Let the `fallbackValidFor` 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
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
@ -107,7 +106,6 @@ func TestNotary(t *testing.T) {
|
|||
defer mtx.RUnlock()
|
||||
return completedTxes[h]
|
||||
}
|
||||
|
||||
var completedTx *transaction.Transaction
|
||||
require.Eventually(t, func() bool {
|
||||
mtx.RLock()
|
||||
|
@ -138,6 +136,11 @@ func TestNotary(t *testing.T) {
|
|||
notaryNodes := keys.PublicKeys{acc1.PrivateKey().PublicKey(), acc2.PrivateKey().PublicKey()}
|
||||
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 {
|
||||
fallback := transaction.New([]byte{byte(opcode.RET)}, 2000_0000)
|
||||
fallback.Nonce = nonce
|
||||
|
@ -182,19 +185,37 @@ func TestNotary(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
return fallback
|
||||
}
|
||||
|
||||
createStandardRequest := func(requesters []*wallet.Account, NVBincrements ...uint32) []*payload.P2PNotaryRequest {
|
||||
createMixedRequest := func(requesters []requester, NVBincrements ...uint32) []*payload.P2PNotaryRequest {
|
||||
mainTx := *transaction.New([]byte{byte(opcode.RET)}, 11000000)
|
||||
mainTx.Nonce = nonce
|
||||
nonce++
|
||||
mainTx.SystemFee = 100000000
|
||||
mainTx.ValidUntilBlock = bc.BlockHeight() + 2*nvbDiffFallback
|
||||
signers := make([]transaction.Signer, len(requesters)+1)
|
||||
var (
|
||||
nKeys uint8
|
||||
verificationScripts [][]byte
|
||||
)
|
||||
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{
|
||||
Account: requesters[i].PrivateKey().PublicKey().GetScriptHash(),
|
||||
Account: hash.Hash160(script),
|
||||
Scopes: transaction.None,
|
||||
}
|
||||
verificationScripts = append(verificationScripts, script)
|
||||
}
|
||||
signers[len(signers)-1] = transaction.Signer{
|
||||
Account: bc.GetNotaryContractScriptHash(),
|
||||
|
@ -204,132 +225,73 @@ func TestNotary(t *testing.T) {
|
|||
mainTx.Attributes = []transaction.Attribute{
|
||||
{
|
||||
Type: transaction.NotaryAssistedT,
|
||||
Value: &transaction.NotaryAssisted{NKeys: uint8(len(requesters))},
|
||||
Value: &transaction.NotaryAssisted{NKeys: nKeys},
|
||||
},
|
||||
}
|
||||
payloads := make([]*payload.P2PNotaryRequest, len(requesters))
|
||||
for i := range payloads {
|
||||
payloads := make([]*payload.P2PNotaryRequest, nKeys)
|
||||
plIndex := 0
|
||||
// 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, r := range requesters {
|
||||
for _, acc := range r.accounts {
|
||||
cp := mainTx
|
||||
main := &cp
|
||||
scripts := make([]transaction.Witness, len(requesters)+1)
|
||||
for j := range requesters {
|
||||
scripts[j].VerificationScript = requesters[j].PrivateKey().PublicKey().GetVerificationScript()
|
||||
main.Scripts = make([]transaction.Witness, len(requesters))
|
||||
for j := range main.Scripts {
|
||||
main.Scripts[j].VerificationScript = verificationScripts[j]
|
||||
if i == j {
|
||||
main.Scripts[j].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, acc.PrivateKey().SignHashable(uint32(testchain.Network()), main)...)
|
||||
}
|
||||
scripts[i].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, requesters[i].PrivateKey().SignHashable(uint32(testchain.Network()), main)...)
|
||||
main.Scripts = scripts
|
||||
}
|
||||
main.Scripts = append(main.Scripts, transaction.Witness{}) // empty Notary witness
|
||||
|
||||
_ = main.Size() // for size update test
|
||||
|
||||
var fallback *transaction.Transaction
|
||||
if len(NVBincrements) == len(requesters) {
|
||||
fallback = createFallbackTx(requesters[i], main, NVBincrements[i])
|
||||
if len(NVBincrements) == int(nKeys) {
|
||||
fallback = createFallbackTx(acc, main, NVBincrements[plIndex])
|
||||
} else {
|
||||
fallback = createFallbackTx(requesters[i], main)
|
||||
fallback = createFallbackTx(acc, main)
|
||||
}
|
||||
|
||||
_ = fallback.Size() // for size update test
|
||||
|
||||
payloads[i] = &payload.P2PNotaryRequest{
|
||||
payloads[plIndex] = &payload.P2PNotaryRequest{
|
||||
MainTransaction: main,
|
||||
FallbackTransaction: fallback,
|
||||
}
|
||||
plIndex++
|
||||
}
|
||||
}
|
||||
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)
|
||||
for i := range payloads {
|
||||
cp := mainTx
|
||||
main := &cp
|
||||
main.Scripts = []transaction.Witness{
|
||||
{
|
||||
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, requesters[i].PrivateKey().SignHashable(uint32(testchain.Network()), main)...),
|
||||
VerificationScript: script,
|
||||
},
|
||||
{}, // empty Notary witness
|
||||
}
|
||||
fallback := createFallbackTx(requesters[i], main)
|
||||
payloads[i] = &payload.P2PNotaryRequest{
|
||||
MainTransaction: main,
|
||||
FallbackTransaction: fallback,
|
||||
checkMainTx := func(t *testing.T, requesters []requester, requests []*payload.P2PNotaryRequest, sentCount int, shouldComplete bool) {
|
||||
nSigs := 0
|
||||
for _, r := range requesters {
|
||||
switch r.typ {
|
||||
case notary.Signature:
|
||||
nSigs++
|
||||
case notary.MultiSignature:
|
||||
nSigs += r.m
|
||||
}
|
||||
}
|
||||
return payloads
|
||||
}
|
||||
checkSigTx := func(t *testing.T, requests []*payload.P2PNotaryRequest, sentCount int, shouldComplete bool) {
|
||||
nKeys := len(requests)
|
||||
if sentCount == nKeys && shouldComplete {
|
||||
nSigners := len(requesters) + 1
|
||||
if sentCount >= nSigs && shouldComplete {
|
||||
completedTx := getCompletedTx(t, true, requests[0].MainTransaction.Hash())
|
||||
require.Equal(t, nKeys+1, len(completedTx.Signers))
|
||||
require.Equal(t, nKeys+1, len(completedTx.Scripts))
|
||||
require.Equal(t, nSigners, len(completedTx.Signers))
|
||||
require.Equal(t, nSigners, len(completedTx.Scripts))
|
||||
|
||||
// check that tx size was updated
|
||||
require.Equal(t, io.GetVarSize(completedTx), completedTx.Size())
|
||||
|
||||
for i := 0; i < len(completedTx.Scripts)-1; i++ {
|
||||
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx)
|
||||
for i, req := range requests {
|
||||
require.Equal(t, req.MainTransaction.Scripts[i], completedTx.Scripts[i])
|
||||
_, err := bc.verifyHashAgainstScript(completedTx.Signers[i].Account, &completedTx.Scripts[i], 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[nKeys])
|
||||
} 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))
|
||||
}
|
||||
}, 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, nSigs))
|
||||
|
@ -358,13 +320,17 @@ func TestNotary(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
checkCompleteStandardRequest := func(t *testing.T, nKeys int, shouldComplete bool, nvbIncrements ...uint32) []*payload.P2PNotaryRequest {
|
||||
requesters := make([]*wallet.Account, nKeys)
|
||||
checkCompleteStandardRequest := func(t *testing.T, nKeys int, shouldComplete bool, nvbIncrements ...uint32) ([]*payload.P2PNotaryRequest, []requester) {
|
||||
requesters := make([]requester, nKeys)
|
||||
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)
|
||||
for i := range sendOrder {
|
||||
sendOrder[i] = i
|
||||
|
@ -374,22 +340,29 @@ func TestNotary(t *testing.T) {
|
|||
})
|
||||
for i := range requests {
|
||||
ntr1.OnNewRequest(requests[sendOrder[i]])
|
||||
checkSigTx(t, requests, i+1, shouldComplete)
|
||||
checkMainTx(t, requesters, requests, i+1, shouldComplete)
|
||||
completedCount := len(completedTxes)
|
||||
|
||||
// check that the same request won't be processed twice
|
||||
ntr1.OnNewRequest(requests[sendOrder[i]])
|
||||
checkSigTx(t, requests, i+1, shouldComplete)
|
||||
checkMainTx(t, requesters, requests, i+1, shouldComplete)
|
||||
require.Equal(t, completedCount, len(completedTxes))
|
||||
}
|
||||
return requests
|
||||
return requests, requesters
|
||||
}
|
||||
checkCompleteMultisigRequest := func(t *testing.T, nSigs int, nKeys int, shouldComplete bool) []*payload.P2PNotaryRequest {
|
||||
requesters := make([]*wallet.Account, nKeys)
|
||||
for i := range requesters {
|
||||
requesters[i], _ = wallet.NewAccount()
|
||||
checkCompleteMultisigRequest := func(t *testing.T, nSigs int, nKeys int, shouldComplete bool) ([]*payload.P2PNotaryRequest, []requester) {
|
||||
accounts := make([]*wallet.Account, nKeys)
|
||||
for i := range accounts {
|
||||
accounts[i], _ = wallet.NewAccount()
|
||||
}
|
||||
requests := createMultisigRequest(nSigs, requesters)
|
||||
requesters := []requester{
|
||||
{
|
||||
accounts: accounts,
|
||||
m: nSigs,
|
||||
typ: notary.MultiSignature,
|
||||
},
|
||||
}
|
||||
requests := createMixedRequest(requesters)
|
||||
sendOrder := make([]int, nKeys)
|
||||
for i := range sendOrder {
|
||||
sendOrder[i] = i
|
||||
|
@ -404,11 +377,11 @@ func TestNotary(t *testing.T) {
|
|||
submittedRequests = append(submittedRequests, 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
|
||||
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
|
||||
|
@ -417,120 +390,161 @@ func TestNotary(t *testing.T) {
|
|||
submittedRequests = append(submittedRequests, 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))
|
||||
}
|
||||
|
||||
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
|
||||
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
|
||||
r := checkCompleteStandardRequest(t, 1, false)
|
||||
r, _ := checkCompleteStandardRequest(t, 1, false)
|
||||
checkFallbackTxs(t, r, false)
|
||||
// set account back for the next tests
|
||||
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
|
||||
|
||||
// OnNewRequest: signature request
|
||||
for _, i := range []int{1, 2, 3, 10} {
|
||||
r := checkCompleteStandardRequest(t, i, true)
|
||||
r, _ := checkCompleteStandardRequest(t, i, true)
|
||||
checkFallbackTxs(t, r, false)
|
||||
}
|
||||
|
||||
// OnNewRequest: multisignature request
|
||||
r = checkCompleteMultisigRequest(t, 1, 1, true)
|
||||
r, _ = checkCompleteMultisigRequest(t, 1, 1, true)
|
||||
checkFallbackTxs(t, r, false)
|
||||
r = checkCompleteMultisigRequest(t, 1, 2, true)
|
||||
r, _ = checkCompleteMultisigRequest(t, 1, 2, true)
|
||||
checkFallbackTxs(t, r, false)
|
||||
r = checkCompleteMultisigRequest(t, 1, 3, true)
|
||||
r, _ = checkCompleteMultisigRequest(t, 1, 3, true)
|
||||
checkFallbackTxs(t, r, false)
|
||||
r = checkCompleteMultisigRequest(t, 3, 3, true)
|
||||
r, _ = checkCompleteMultisigRequest(t, 3, 3, true)
|
||||
checkFallbackTxs(t, r, false)
|
||||
r = checkCompleteMultisigRequest(t, 3, 4, true)
|
||||
r, _ = checkCompleteMultisigRequest(t, 3, 4, true)
|
||||
checkFallbackTxs(t, r, false)
|
||||
r = checkCompleteMultisigRequest(t, 3, 10, true)
|
||||
r, _ = checkCompleteMultisigRequest(t, 3, 10, true)
|
||||
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
|
||||
setFinalizeWithError(true)
|
||||
r = checkCompleteStandardRequest(t, 1, false)
|
||||
r, requesters := checkCompleteStandardRequest(t, 1, false)
|
||||
checkFallbackTxs(t, r, false)
|
||||
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
|
||||
setFinalizeWithError(false)
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
checkSigTx(t, r, 1, false)
|
||||
checkMainTx(t, requesters, r, 1, false)
|
||||
checkFallbackTxs(t, r, false)
|
||||
// set account back for the next tests
|
||||
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
|
||||
|
||||
// PostPersist: complete main transaction, signature request
|
||||
setFinalizeWithError(true)
|
||||
requests := checkCompleteStandardRequest(t, 3, false)
|
||||
requests, requesters := checkCompleteStandardRequest(t, 3, false)
|
||||
// check PostPersist with finalisation error
|
||||
setFinalizeWithError(true)
|
||||
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
|
||||
setFinalizeWithError(false)
|
||||
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
|
||||
setFinalizeWithError(true)
|
||||
requests = checkCompleteMultisigRequest(t, 3, 4, false)
|
||||
requests, requesters = checkCompleteMultisigRequest(t, 3, 4, false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
// check PostPersist with finalisation error
|
||||
setFinalizeWithError(true)
|
||||
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)
|
||||
// check PostPersist without finalisation error
|
||||
setFinalizeWithError(false)
|
||||
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)
|
||||
|
||||
// PostPersist: complete fallback, signature request
|
||||
setFinalizeWithError(true)
|
||||
requests = checkCompleteStandardRequest(t, 3, false)
|
||||
requests, requesters = checkCompleteStandardRequest(t, 3, false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
// make fallbacks valid
|
||||
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||
require.NoError(t, err)
|
||||
// check PostPersist for valid fallbacks with finalisation error
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
checkSigTx(t, requests, len(requests), false)
|
||||
checkMainTx(t, requesters, requests, len(requests), false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
// check PostPersist for valid fallbacks without finalisation error
|
||||
setFinalizeWithError(false)
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
checkSigTx(t, requests, len(requests), false)
|
||||
checkMainTx(t, requesters, requests, len(requests), false)
|
||||
checkFallbackTxs(t, requests, true)
|
||||
|
||||
// PostPersist: complete fallback, multisignature request
|
||||
nSigs, nKeys := 3, 5
|
||||
// check OnNewRequest with finalization error
|
||||
setFinalizeWithError(true)
|
||||
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
||||
requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
// make fallbacks valid
|
||||
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||
require.NoError(t, err)
|
||||
// check PostPersist for valid fallbacks with finalisation error
|
||||
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)
|
||||
// check PostPersist for valid fallbacks without finalisation error
|
||||
setFinalizeWithError(false)
|
||||
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)
|
||||
// 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)
|
||||
|
||||
// PostPersist: partial fallbacks completion due to finalisation errors
|
||||
setFinalizeWithError(true)
|
||||
requests = checkCompleteStandardRequest(t, 5, false)
|
||||
requests, requesters = checkCompleteStandardRequest(t, 5, false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
// make fallbacks valid
|
||||
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||
|
@ -541,7 +555,7 @@ func TestNotary(t *testing.T) {
|
|||
setChoosy(true)
|
||||
// check PostPersist for lucky fallbacks
|
||||
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, unluckies, false)
|
||||
// reset finalisation function for unlucky fallbacks to finalise without an error
|
||||
|
@ -549,14 +563,14 @@ func TestNotary(t *testing.T) {
|
|||
setFinalizeWithError(false)
|
||||
// check PostPersist for unlucky fallbacks
|
||||
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, unluckies, true)
|
||||
|
||||
// PostPersist: different NVBs
|
||||
// check OnNewRequest with finalization error and different NVBs
|
||||
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)
|
||||
// generate blocks to reach the most earlier fallback's NVB
|
||||
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||
|
@ -578,7 +592,7 @@ func TestNotary(t *testing.T) {
|
|||
defer mtx.RUnlock()
|
||||
return len(completedTxes)-start >= i+1
|
||||
}, 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:], false)
|
||||
}
|
||||
|
@ -586,7 +600,7 @@ func TestNotary(t *testing.T) {
|
|||
// OnRequestRemoval: missing account
|
||||
// check OnNewRequest with finalization error
|
||||
setFinalizeWithError(true)
|
||||
requests = checkCompleteStandardRequest(t, 4, false)
|
||||
requests, requesters = checkCompleteStandardRequest(t, 4, false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
// make fallbacks valid and remove one fallback
|
||||
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||
|
@ -596,7 +610,7 @@ func TestNotary(t *testing.T) {
|
|||
// non of the fallbacks should be completed
|
||||
setFinalizeWithError(false)
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
checkSigTx(t, requests, len(requests), false)
|
||||
checkMainTx(t, requesters, requests, len(requests), false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
// set account back for the next tests
|
||||
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
|
||||
|
@ -604,7 +618,7 @@ func TestNotary(t *testing.T) {
|
|||
// OnRequestRemoval: signature request, remove one fallback
|
||||
// check OnNewRequest with finalization error
|
||||
setFinalizeWithError(true)
|
||||
requests = checkCompleteStandardRequest(t, 4, false)
|
||||
requests, requesters = checkCompleteStandardRequest(t, 4, false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
// make fallbacks valid and remove one fallback
|
||||
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||
|
@ -614,13 +628,13 @@ func TestNotary(t *testing.T) {
|
|||
// rest of the fallbacks should be completed
|
||||
setFinalizeWithError(false)
|
||||
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)
|
||||
require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()])
|
||||
|
||||
// OnRequestRemoval: signature request, remove all fallbacks
|
||||
setFinalizeWithError(true)
|
||||
requests = checkCompleteStandardRequest(t, 4, false)
|
||||
requests, requesters = checkCompleteStandardRequest(t, 4, false)
|
||||
// remove all fallbacks
|
||||
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||
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
|
||||
setFinalizeWithError(false)
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
checkSigTx(t, requests, len(requests), false)
|
||||
checkMainTx(t, requesters, requests, len(requests), false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
|
||||
// OnRequestRemoval: signature request, remove unexisting fallback
|
||||
ntr1.OnRequestRemoval(requests[0])
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
checkSigTx(t, requests, len(requests), false)
|
||||
checkMainTx(t, requesters, requests, len(requests), false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
|
||||
// OnRequestRemoval: multisignature request, remove one fallback
|
||||
nSigs, nKeys = 3, 5
|
||||
// check OnNewRequest with finalization error
|
||||
setFinalizeWithError(true)
|
||||
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
||||
checkMultisigTx(t, nSigs, requests, len(requests), false)
|
||||
requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
||||
checkMainTx(t, requesters, requests, len(requests), false)
|
||||
checkFallbackTxs(t, requests, false)
|
||||
// make fallbacks valid and remove the last fallback
|
||||
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||
|
@ -654,7 +668,7 @@ func TestNotary(t *testing.T) {
|
|||
// then (m-1) out of n fallbacks should be completed
|
||||
setFinalizeWithError(false)
|
||||
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)
|
||||
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
|
||||
|
@ -662,7 +676,7 @@ func TestNotary(t *testing.T) {
|
|||
|
||||
// OnRequestRemoval: multisignature request, remove all fallbacks
|
||||
setFinalizeWithError(true)
|
||||
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
||||
requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
||||
// make fallbacks valid and then remove all of them
|
||||
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||
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
|
||||
setFinalizeWithError(false)
|
||||
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)
|
||||
|
||||
// // OnRequestRemoval: multisignature request, remove unexisting fallbac, i.e. there still shouldn't be any completed transactions after this
|
||||
ntr1.OnRequestRemoval(requests[0])
|
||||
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)
|
||||
|
||||
// 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))
|
||||
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
|
||||
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[1].FallbackTransaction, feer, requests[1]))
|
||||
require.Eventually(t, func() bool {
|
||||
|
|
|
@ -67,9 +67,9 @@ type (
|
|||
|
||||
const defaultTxChannelCapacity = 100
|
||||
|
||||
// request represents Notary service request.
|
||||
type request struct {
|
||||
typ RequestType
|
||||
type (
|
||||
// request represents Notary service request.
|
||||
request struct {
|
||||
// isSent indicates whether main transaction was successfully sent to the network.
|
||||
isSent bool
|
||||
main *transaction.Transaction
|
||||
|
@ -77,15 +77,37 @@ type request struct {
|
|||
// We stop trying to send mainTx to the network if the chain reaches minNotValidBefore height.
|
||||
minNotValidBefore uint32
|
||||
fallbacks []*transaction.Transaction
|
||||
|
||||
witnessInfo []witnessInfo
|
||||
}
|
||||
|
||||
// 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 == 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
|
||||
// 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.
|
||||
|
@ -163,7 +185,13 @@ func (n *Notary) OnNewRequest(payload *payload.P2PNotaryRequest) {
|
|||
|
||||
nvbFallback := payload.FallbackTransaction.GetAttributes(transaction.NotValidBeforeT)[0].Value.(*transaction.NotValidBefore).Height
|
||||
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()
|
||||
defer n.reqMtx.Unlock()
|
||||
r, exists := n.requests[payload.MainTransaction.Hash()]
|
||||
|
@ -176,75 +204,66 @@ func (n *Notary) OnNewRequest(payload *payload.P2PNotaryRequest) {
|
|||
if nvbFallback < r.minNotValidBefore {
|
||||
r.minNotValidBefore = nvbFallback
|
||||
}
|
||||
if r.typ == Unknown && validationErr == nil {
|
||||
r.typ = typ
|
||||
r.nSigsLeft = nSigs
|
||||
}
|
||||
} else {
|
||||
r = &request{
|
||||
nSigsLeft: nSigs,
|
||||
main: payload.MainTransaction,
|
||||
typ: typ,
|
||||
minNotValidBefore: nvbFallback,
|
||||
}
|
||||
n.requests[payload.MainTransaction.Hash()] = r
|
||||
}
|
||||
if r.witnessInfo == nil && validationErr == nil {
|
||||
r.witnessInfo = newInfo
|
||||
}
|
||||
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
|
||||
}
|
||||
if validationErr == nil {
|
||||
loop:
|
||||
mainHash := hash.NetSha256(uint32(n.Network), r.main).BytesBE()
|
||||
for i, w := range payload.MainTransaction.Scripts {
|
||||
if payload.MainTransaction.Signers[i].Account.Equals(n.Config.Chain.GetNotaryContractScriptHash()) {
|
||||
if r.witnessInfo[i].typ == Contract || // check that we need to fill that witness
|
||||
len(w.InvocationScript) == 0 || // check that signature for this witness was provided
|
||||
r.witnessInfo[i].nSigsLeft == 0 { // check that signature wasn't yet added (consider receiving the same payload multiple times)
|
||||
continue
|
||||
}
|
||||
if len(w.InvocationScript) != 0 && len(w.VerificationScript) != 0 {
|
||||
switch r.typ {
|
||||
switch r.witnessInfo[i].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)
|
||||
if r.witnessInfo[i].pubs[0].Verify(w.InvocationScript[2:], mainHash) {
|
||||
r.main.Scripts[i] = w
|
||||
r.nSigsLeft--
|
||||
}
|
||||
if r.nSigsLeft == 0 {
|
||||
break loop
|
||||
r.witnessInfo[i].nSigsLeft--
|
||||
}
|
||||
case MultiSignature:
|
||||
if r.sigs == nil {
|
||||
r.sigs = make(map[*keys.PublicKey][]byte)
|
||||
if r.witnessInfo[i].sigs == nil {
|
||||
r.witnessInfo[i].sigs = make(map[*keys.PublicKey][]byte)
|
||||
}
|
||||
|
||||
hash := hash.NetSha256(uint32(n.Network), r.main).BytesBE()
|
||||
for _, pub := range pubs {
|
||||
if r.sigs[pub] != nil {
|
||||
for _, pub := range r.witnessInfo[i].pubs {
|
||||
if r.witnessInfo[i].sigs[pub] != nil {
|
||||
continue // signature for this pub has already been added
|
||||
}
|
||||
if pub.Verify(w.InvocationScript[2:], hash) { // then pub is the owner of the signature
|
||||
r.sigs[pub] = w.InvocationScript
|
||||
r.nSigsLeft--
|
||||
if r.nSigsLeft == 0 {
|
||||
if pub.Verify(w.InvocationScript[2:], mainHash) { // then pub is the owner of the signature
|
||||
r.witnessInfo[i].sigs[pub] = w.InvocationScript
|
||||
r.witnessInfo[i].nSigsLeft--
|
||||
if r.witnessInfo[i].nSigsLeft == 0 {
|
||||
var invScript []byte
|
||||
for j := range pubs {
|
||||
if sig, ok := r.sigs[pubs[j]]; ok {
|
||||
for j := range r.witnessInfo[i].pubs {
|
||||
if sig, ok := r.witnessInfo[i].sigs[r.witnessInfo[i].pubs[j]]; ok {
|
||||
invScript = append(invScript, sig...)
|
||||
}
|
||||
}
|
||||
r.main.Scripts[i].InvocationScript = invScript
|
||||
}
|
||||
break loop
|
||||
break
|
||||
}
|
||||
}
|
||||
// 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 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 {
|
||||
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()
|
||||
currHeight := n.Config.Chain.BlockHeight()
|
||||
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 {
|
||||
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
|
||||
// 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.
|
||||
func (n *Notary) verifyIncompleteWitnesses(tx *transaction.Transaction, nKeys uint8) (RequestType, uint8, keys.PublicKeys, error) {
|
||||
var (
|
||||
typ RequestType
|
||||
nSigs int
|
||||
nKeysActual uint8
|
||||
pubsBytes [][]byte
|
||||
pubs keys.PublicKeys
|
||||
ok bool
|
||||
)
|
||||
func (n *Notary) verifyIncompleteWitnesses(tx *transaction.Transaction, nKeysExpected uint8) ([]witnessInfo, error) {
|
||||
var nKeysActual uint8
|
||||
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()) {
|
||||
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 {
|
||||
// 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() {
|
||||
continue
|
||||
}
|
||||
// Do not check witness for Notary contract -- it will be replaced by proper witness in any case.
|
||||
// Also do not check other contract-based witnesses (they can be combined with anything)
|
||||
if len(w.VerificationScript) == 0 {
|
||||
// then it's a contract verification (can be combined with anything)
|
||||
result[i] = witnessInfo{
|
||||
typ: Contract,
|
||||
nSigsLeft: 0,
|
||||
}
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
if typ == Signature || typ == MultiSignature {
|
||||
return Unknown, 0, nil, fmt.Errorf("bad type of witness #%d: only one multisignature witness is allowed", i)
|
||||
}
|
||||
typ = MultiSignature
|
||||
nKeysActual = uint8(len(pubsBytes))
|
||||
// Each verification script is allowed to have either one signature or zero signatures. If signature is provided, then need to verify it.
|
||||
if len(w.InvocationScript) != 0 {
|
||||
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)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if vm.IsSignatureContract(w.VerificationScript) {
|
||||
if typ == MultiSignature {
|
||||
return Unknown, 0, nil, fmt.Errorf("bad type of witness #%d: multisignature witness can not be combined with other witnesses", i)
|
||||
if nSigs, pubsBytes, ok := vm.ParseMultiSigContract(w.VerificationScript); ok {
|
||||
result[i] = witnessInfo{
|
||||
typ: MultiSignature,
|
||||
nSigsLeft: uint8(nSigs),
|
||||
pubs: make(keys.PublicKeys, len(pubsBytes)),
|
||||
}
|
||||
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 {
|
||||
for j, pBytes := range pubsBytes {
|
||||
pub, err := keys.NewPublicKeyFromBytes(pBytes, elliptic.P256())
|
||||
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 #%d public key: %s", i, j, hex.EncodeToString(pBytes))
|
||||
}
|
||||
pubs[i] = pub
|
||||
result[i].pubs[j] = pub
|
||||
}
|
||||
default:
|
||||
return Unknown, 0, nil, errors.New("unexpected Notary request type")
|
||||
nKeysActual += uint8(len(pubsBytes))
|
||||
continue
|
||||
}
|
||||
return typ, uint8(nSigs), pubs, nil
|
||||
if pBytes, ok := vm.ParseSignatureContract(w.VerificationScript); ok {
|
||||
pub, err := keys.NewPublicKeyFromBytes(pBytes, elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("witness #%d: invalid bytes of public key: %s", i, hex.EncodeToString(pBytes))
|
||||
}
|
||||
result[i] = witnessInfo{
|
||||
typ: Signature,
|
||||
nSigsLeft: 1,
|
||||
pubs: keys.PublicKeys{pub},
|
||||
}
|
||||
nKeysActual++
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("witness #%d: unable to detect witness type, only sig/multisig/contract are supported", i)
|
||||
}
|
||||
if nKeysActual != nKeysExpected {
|
||||
return nil, fmt.Errorf("expected and actual NKeys mismatch: %d vs %d", nKeysExpected, nKeysActual)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
@ -67,11 +66,9 @@ func TestVerifyIncompleteRequest(t *testing.T) {
|
|||
multisigScriptHash2 := hash.Hash160(multisigScript2)
|
||||
|
||||
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.Equal(t, Unknown, typ)
|
||||
require.Equal(t, uint8(0), nSigs)
|
||||
require.Nil(t, pubs)
|
||||
require.Nil(t, witnessInfo)
|
||||
}
|
||||
|
||||
errCases := map[string]struct {
|
||||
|
@ -90,15 +87,6 @@ func TestVerifyIncompleteRequest(t *testing.T) {
|
|||
Scripts: []transaction.Witness{{}, {}},
|
||||
},
|
||||
},
|
||||
"unknown witness type": {
|
||||
tx: &transaction.Transaction{
|
||||
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
|
||||
Scripts: []transaction.Witness{
|
||||
{},
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
"bad verification script": {
|
||||
tx: &transaction.Transaction{
|
||||
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": {
|
||||
tx: &transaction.Transaction{
|
||||
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: acc2.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
|
||||
|
@ -283,9 +152,7 @@ func TestVerifyIncompleteRequest(t *testing.T) {
|
|||
testCases := map[string]struct {
|
||||
tx *transaction.Transaction
|
||||
nKeys uint8
|
||||
expectedType RequestType
|
||||
expectedNSigs uint8
|
||||
expectedPubs keys.PublicKeys
|
||||
expectedInfo []witnessInfo
|
||||
}{
|
||||
"single sig": {
|
||||
tx: &transaction.Transaction{
|
||||
|
@ -299,8 +166,10 @@ func TestVerifyIncompleteRequest(t *testing.T) {
|
|||
},
|
||||
},
|
||||
nKeys: 1,
|
||||
expectedType: Signature,
|
||||
expectedNSigs: 1,
|
||||
expectedInfo: []witnessInfo{
|
||||
{typ: Signature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey()}},
|
||||
{typ: Contract},
|
||||
},
|
||||
},
|
||||
"multiple sig": {
|
||||
tx: &transaction.Transaction{
|
||||
|
@ -312,7 +181,7 @@ func TestVerifyIncompleteRequest(t *testing.T) {
|
|||
},
|
||||
{
|
||||
InvocationScript: []byte{},
|
||||
VerificationScript: []byte{},
|
||||
VerificationScript: sigScript2,
|
||||
},
|
||||
{
|
||||
InvocationScript: sig,
|
||||
|
@ -322,10 +191,14 @@ func TestVerifyIncompleteRequest(t *testing.T) {
|
|||
},
|
||||
},
|
||||
nKeys: 3,
|
||||
expectedType: Signature,
|
||||
expectedNSigs: 3,
|
||||
expectedInfo: []witnessInfo{
|
||||
{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{
|
||||
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: notaryContractHash}},
|
||||
Scripts: []transaction.Witness{
|
||||
|
@ -337,11 +210,12 @@ func TestVerifyIncompleteRequest(t *testing.T) {
|
|||
},
|
||||
},
|
||||
nKeys: 3,
|
||||
expectedType: MultiSignature,
|
||||
expectedNSigs: 1,
|
||||
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()},
|
||||
expectedInfo: []witnessInfo{
|
||||
{typ: MultiSignature, nSigsLeft: 1, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
|
||||
{typ: Contract},
|
||||
},
|
||||
"multisig 2 out of 3": {
|
||||
},
|
||||
"single multisig 2 out of 3": {
|
||||
tx: &transaction.Transaction{
|
||||
Signers: []transaction.Signer{{Account: multisigScriptHash2}, {Account: notaryContractHash}},
|
||||
Scripts: []transaction.Witness{
|
||||
|
@ -353,17 +227,18 @@ func TestVerifyIncompleteRequest(t *testing.T) {
|
|||
},
|
||||
},
|
||||
nKeys: 3,
|
||||
expectedType: MultiSignature,
|
||||
expectedNSigs: 2,
|
||||
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()},
|
||||
expectedInfo: []witnessInfo{
|
||||
{typ: MultiSignature, nSigsLeft: 2, pubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()}},
|
||||
{typ: Contract},
|
||||
},
|
||||
"empty + multisig": {
|
||||
},
|
||||
"empty sig + single multisig 1 out of 3": {
|
||||
tx: &transaction.Transaction{
|
||||
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
|
||||
Scripts: []transaction.Witness{
|
||||
{
|
||||
InvocationScript: []byte{},
|
||||
VerificationScript: []byte{},
|
||||
VerificationScript: sigScript1,
|
||||
},
|
||||
{
|
||||
InvocationScript: sig,
|
||||
|
@ -372,12 +247,14 @@ func TestVerifyIncompleteRequest(t *testing.T) {
|
|||
{},
|
||||
},
|
||||
},
|
||||
nKeys: 3,
|
||||
expectedType: MultiSignature,
|
||||
expectedNSigs: 1,
|
||||
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()},
|
||||
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": {
|
||||
},
|
||||
"single multisig 1 out of 3 + empty single sig": {
|
||||
tx: &transaction.Transaction{
|
||||
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
|
||||
Scripts: []transaction.Witness{
|
||||
|
@ -387,25 +264,228 @@ func TestVerifyIncompleteRequest(t *testing.T) {
|
|||
},
|
||||
{
|
||||
InvocationScript: []byte{},
|
||||
VerificationScript: []byte{},
|
||||
VerificationScript: sigScript1,
|
||||
},
|
||||
{},
|
||||
},
|
||||
},
|
||||
nKeys: 3,
|
||||
expectedType: MultiSignature,
|
||||
expectedNSigs: 1,
|
||||
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()},
|
||||
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},
|
||||
},
|
||||
},
|
||||
"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 {
|
||||
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)
|
||||
assert.Equal(t, testCase.expectedType, typ)
|
||||
assert.Equal(t, testCase.expectedNSigs, nSigs)
|
||||
assert.ElementsMatch(t, testCase.expectedPubs, pubs)
|
||||
require.Equal(t, len(testCase.expectedInfo), len(actualInfo))
|
||||
for i, expected := range testCase.expectedInfo {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ package notary
|
|||
type RequestType byte
|
||||
|
||||
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 RequestType = 0x01
|
||||
// MultiSignature represents m out of n multisignature request type.
|
||||
MultiSignature RequestType = 0x02
|
||||
// Contract represents contract witness type.
|
||||
Contract RequestType = 0x03
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue