2020-12-30 08:01:13 +00:00
package notary
import (
"bytes"
"crypto/elliptic"
"encoding/hex"
"errors"
"fmt"
"sync"
"github.com/nspcc-dev/neo-go/pkg/config"
2021-03-25 16:18:01 +00:00
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
2020-12-30 08:01:13 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
2021-05-28 11:47:33 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
2020-12-30 08:01:13 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
2021-02-24 08:35:09 +00:00
"github.com/nspcc-dev/neo-go/pkg/io"
2020-12-30 08:01:13 +00:00
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"go.uber.org/zap"
)
type (
// Notary represents Notary module.
Notary struct {
Config Config
2021-03-25 16:18:01 +00:00
Network netmode . Magic
2020-12-30 08:01:13 +00:00
// onTransaction is a callback for completed transactions (mains or fallbacks) sending.
onTransaction func ( tx * transaction . Transaction ) error
2021-07-13 11:22:21 +00:00
// newTxs is a channel where new transactions are sent
// to be processed in a `onTransaction` callback.
newTxs chan txHashPair
2020-12-30 08:01:13 +00:00
// reqMtx protects requests list.
reqMtx sync . RWMutex
// requests represents the map of main transactions which needs to be completed
// with the associated fallback transactions grouped by the main transaction hash
requests map [ util . Uint256 ] * request
// accMtx protects account.
accMtx sync . RWMutex
currAccount * wallet . Account
wallet * wallet . Wallet
2021-01-15 12:40:15 +00:00
mp * mempool . Pool
// requests channel
2021-05-28 11:47:33 +00:00
reqCh chan mempoolevent . Event
2021-02-09 13:03:06 +00:00
blocksCh chan * block . Block
stopCh chan struct { }
2020-12-30 08:01:13 +00:00
}
// Config represents external configuration for Notary module.
Config struct {
MainCfg config . P2PNotary
Chain blockchainer . Blockchainer
Log * zap . Logger
}
)
2021-07-13 11:22:21 +00:00
const defaultTxChannelCapacity = 100
2021-10-20 15:43:32 +00:00
type (
// request represents Notary service request.
request struct {
// isSent indicates whether main transaction was successfully sent to the network.
isSent bool
main * transaction . Transaction
// minNotValidBefore is the minimum NVB value among fallbacks transactions.
// 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 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
2020-12-30 08:01:13 +00:00
}
// NewNotary returns new Notary module.
2021-03-25 16:18:01 +00:00
func NewNotary ( cfg Config , net netmode . Magic , mp * mempool . Pool , onTransaction func ( tx * transaction . Transaction ) error ) ( * Notary , error ) {
2021-02-16 10:49:56 +00:00
w := cfg . MainCfg . UnlockWallet
2020-12-30 08:01:13 +00:00
wallet , err := wallet . NewWalletFromFile ( w . Path )
if err != nil {
return nil , err
}
haveAccount := false
for _ , acc := range wallet . Accounts {
2021-06-04 11:27:22 +00:00
if err := acc . Decrypt ( w . Password , wallet . Scrypt ) ; err == nil {
2020-12-30 08:01:13 +00:00
haveAccount = true
break
}
}
if ! haveAccount {
return nil , errors . New ( "no wallet account could be unlocked" )
}
return & Notary {
2021-02-16 10:49:56 +00:00
requests : make ( map [ util . Uint256 ] * request ) ,
Config : cfg ,
2021-03-25 16:18:01 +00:00
Network : net ,
2020-12-30 08:01:13 +00:00
wallet : wallet ,
onTransaction : onTransaction ,
2021-07-13 11:22:21 +00:00
newTxs : make ( chan txHashPair , defaultTxChannelCapacity ) ,
2021-01-15 12:40:15 +00:00
mp : mp ,
2021-05-28 11:47:33 +00:00
reqCh : make ( chan mempoolevent . Event ) ,
2021-02-09 13:03:06 +00:00
blocksCh : make ( chan * block . Block ) ,
2021-01-15 12:40:15 +00:00
stopCh : make ( chan struct { } ) ,
2020-12-30 08:01:13 +00:00
} , nil
}
2021-01-15 12:40:15 +00:00
// Run runs Notary module and should be called in a separate goroutine.
func ( n * Notary ) Run ( ) {
2021-04-02 10:13:26 +00:00
n . Config . Log . Info ( "starting notary service" )
2021-02-09 13:03:06 +00:00
n . Config . Chain . SubscribeForBlocks ( n . blocksCh )
2021-01-15 12:40:15 +00:00
n . mp . SubscribeForTransactions ( n . reqCh )
2021-07-13 11:22:21 +00:00
go n . newTxCallbackLoop ( )
2021-01-15 12:40:15 +00:00
for {
select {
case <- n . stopCh :
n . mp . UnsubscribeFromTransactions ( n . reqCh )
2021-02-09 13:03:06 +00:00
n . Config . Chain . UnsubscribeFromBlocks ( n . blocksCh )
2021-01-15 12:40:15 +00:00
return
case event := <- n . reqCh :
if req , ok := event . Data . ( * payload . P2PNotaryRequest ) ; ok {
switch event . Type {
2021-05-28 11:47:33 +00:00
case mempoolevent . TransactionAdded :
2021-01-15 12:40:15 +00:00
n . OnNewRequest ( req )
2021-05-28 11:47:33 +00:00
case mempoolevent . TransactionRemoved :
2021-01-15 12:40:15 +00:00
n . OnRequestRemoval ( req )
}
}
2021-02-09 13:03:06 +00:00
case <- n . blocksCh :
// new block was added, need to check for valid fallbacks
n . PostPersist ( )
2021-01-15 12:40:15 +00:00
}
}
}
// Stop shutdowns Notary module.
func ( n * Notary ) Stop ( ) {
close ( n . stopCh )
}
2020-12-30 08:01:13 +00:00
// OnNewRequest is a callback method which is called after new notary request is added to the notary request pool.
func ( n * Notary ) OnNewRequest ( payload * payload . P2PNotaryRequest ) {
notary: fix possible deadlock in `UpdateNotaryNodes`
`UpdateNotaryNodes` takes account then request mutex, and `PostPersist` takes
them in a different order. Because they are executed concurrently a deadlock
can appear.
```
2021-07-23T11:06:58.3732405Z panic: test timed out after 10m0s
2021-07-23T11:06:58.3732642Z
2021-07-23T11:06:58.3742610Z goroutine 7351 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3743140Z sync.runtime_SemacquireMutex(0xc00010e4dc, 0x1100000000, 0x1)
2021-07-23T11:06:58.3743747Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3744222Z sync.(*Mutex).lockSlow(0xc00010e4d8)
2021-07-23T11:06:58.3744742Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:138 +0x1c1
2021-07-23T11:06:58.3745209Z sync.(*Mutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3745692Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:81 +0x7d
2021-07-23T11:06:58.3746162Z sync.(*RWMutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3746764Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:98 +0x4a
2021-07-23T11:06:58.3747699Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).UpdateNotaryNodes(0xc00010e480, 0xc000105b90, 0x1, 0x1)
2021-07-23T11:06:58.3748621Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:44 +0x3ba
2021-07-23T11:06:58.3749367Z github.com/nspcc-dev/neo-go/pkg/core.TestNotary(0xc0003677a0)
2021-07-23T11:06:58.3750116Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:594 +0x2dba
2021-07-23T11:06:58.3750641Z testing.tRunner(0xc0003677a0, 0x16f3308)
2021-07-23T11:06:58.3751202Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1050 +0x1ec
2021-07-23T11:06:58.3751696Z created by testing.(*T).Run
2021-07-23T11:06:58.3752225Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1095 +0x538
2021-07-23T11:06:58.3752573Z
2021-07-23T11:06:58.3771319Z goroutine 7340 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3772048Z sync.runtime_SemacquireMutex(0xc00010e504, 0x0, 0x0)
2021-07-23T11:06:58.3772889Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3773581Z sync.(*RWMutex).RLock(0xc00010e4f8)
2021-07-23T11:06:58.3774310Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:50 +0xa4
2021-07-23T11:06:58.3775449Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).getAccount(0xc00010e480, 0x0)
2021-07-23T11:06:58.3776626Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:51 +0x51
2021-07-23T11:06:58.3778270Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).finalize(0xc00010e480, 0xc0003b2630, 0xa97df1bc78dd5787, 0xcc8a4d69e7f5d62a, 0x1a4d7981bd86b087, 0xbafdb720c93480b3, 0x0, 0x0)
2021-07-23T11:06:58.3779845Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:306 +0x54
2021-07-23T11:06:58.3781022Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).PostPersist(0xc00010e480)
2021-07-23T11:06:58.3782232Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:297 +0x662
2021-07-23T11:06:58.3782989Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).Run(0xc00010e480)
2021-07-23T11:06:58.3783941Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:148 +0x3cb
2021-07-23T11:06:58.3784702Z created by github.com/nspcc-dev/neo-go/pkg/core.TestNotary
2021-07-23T11:06:58.3785451Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:132 +0x6e0
```
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
2021-07-23 11:28:11 +00:00
acc := n . getAccount ( )
if acc == nil {
2020-12-30 08:01:13 +00:00
return
}
nvbFallback := payload . FallbackTransaction . GetAttributes ( transaction . NotValidBeforeT ) [ 0 ] . Value . ( * transaction . NotValidBefore ) . Height
nKeys := payload . MainTransaction . GetAttributes ( transaction . NotaryAssistedT ) [ 0 ] . Value . ( * transaction . NotaryAssisted ) . NKeys
2021-10-20 15:43:32 +00:00
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 ( ) ) )
}
2020-12-30 08:01:13 +00:00
n . reqMtx . Lock ( )
defer n . reqMtx . Unlock ( )
r , exists := n . requests [ payload . MainTransaction . Hash ( ) ]
if exists {
for _ , fb := range r . fallbacks {
if fb . Hash ( ) . Equals ( payload . FallbackTransaction . Hash ( ) ) {
return // then we already have processed this request
}
}
if nvbFallback < r . minNotValidBefore {
r . minNotValidBefore = nvbFallback
}
} else {
r = & request {
main : payload . MainTransaction ,
minNotValidBefore : nvbFallback ,
}
n . requests [ payload . MainTransaction . Hash ( ) ] = r
}
2021-10-20 15:43:32 +00:00
if r . witnessInfo == nil && validationErr == nil {
r . witnessInfo = newInfo
}
2020-12-30 08:01:13 +00:00
r . fallbacks = append ( r . fallbacks , payload . FallbackTransaction )
2021-10-20 15:43:32 +00:00
if exists && r . isMainCompleted ( ) || validationErr != nil {
2020-12-30 08:01:13 +00:00
return
}
2021-10-20 15:43:32 +00:00
mainHash := hash . NetSha256 ( uint32 ( n . Network ) , r . main ) . BytesBE ( )
for i , w := range payload . MainTransaction . Scripts {
2021-10-22 13:59:16 +00:00
if len ( w . InvocationScript ) == 0 || // check that signature for this witness was provided
( r . witnessInfo [ i ] . nSigsLeft == 0 && r . witnessInfo [ i ] . typ != Contract ) { // check that signature wasn't yet added (consider receiving the same payload multiple times)
2021-10-20 15:43:32 +00:00
continue
}
switch r . witnessInfo [ i ] . typ {
2021-10-22 13:59:16 +00:00
case Contract :
// Need to check even if r.main.Scripts[i].InvocationScript is already filled in.
err := n . Config . Chain . VerifyWitness ( r . main . Signers [ i ] . Account , r . main , & w , n . Config . Chain . GetPolicer ( ) . GetMaxVerificationGAS ( ) )
if err != nil {
continue
}
r . main . Scripts [ i ] . InvocationScript = w . InvocationScript
2021-10-20 15:43:32 +00:00
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 )
2020-12-30 08:01:13 +00:00
}
2021-10-20 15:43:32 +00:00
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 : ] , 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 r . witnessInfo [ i ] . pubs {
if sig , ok := r . witnessInfo [ i ] . sigs [ r . witnessInfo [ i ] . pubs [ j ] ] ; ok {
invScript = append ( invScript , sig ... )
2020-12-30 08:01:13 +00:00
}
}
2021-10-20 15:43:32 +00:00
r . main . Scripts [ i ] . InvocationScript = invScript
2020-12-30 08:01:13 +00:00
}
2021-10-20 15:43:32 +00:00
break
2020-12-30 08:01:13 +00:00
}
}
2021-10-20 15:43:32 +00:00
// 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
2020-12-30 08:01:13 +00:00
}
}
2021-10-20 15:43:32 +00:00
if r . isMainCompleted ( ) && r . minNotValidBefore > n . Config . Chain . BlockHeight ( ) {
notary: fix possible deadlock in `UpdateNotaryNodes`
`UpdateNotaryNodes` takes account then request mutex, and `PostPersist` takes
them in a different order. Because they are executed concurrently a deadlock
can appear.
```
2021-07-23T11:06:58.3732405Z panic: test timed out after 10m0s
2021-07-23T11:06:58.3732642Z
2021-07-23T11:06:58.3742610Z goroutine 7351 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3743140Z sync.runtime_SemacquireMutex(0xc00010e4dc, 0x1100000000, 0x1)
2021-07-23T11:06:58.3743747Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3744222Z sync.(*Mutex).lockSlow(0xc00010e4d8)
2021-07-23T11:06:58.3744742Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:138 +0x1c1
2021-07-23T11:06:58.3745209Z sync.(*Mutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3745692Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:81 +0x7d
2021-07-23T11:06:58.3746162Z sync.(*RWMutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3746764Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:98 +0x4a
2021-07-23T11:06:58.3747699Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).UpdateNotaryNodes(0xc00010e480, 0xc000105b90, 0x1, 0x1)
2021-07-23T11:06:58.3748621Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:44 +0x3ba
2021-07-23T11:06:58.3749367Z github.com/nspcc-dev/neo-go/pkg/core.TestNotary(0xc0003677a0)
2021-07-23T11:06:58.3750116Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:594 +0x2dba
2021-07-23T11:06:58.3750641Z testing.tRunner(0xc0003677a0, 0x16f3308)
2021-07-23T11:06:58.3751202Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1050 +0x1ec
2021-07-23T11:06:58.3751696Z created by testing.(*T).Run
2021-07-23T11:06:58.3752225Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1095 +0x538
2021-07-23T11:06:58.3752573Z
2021-07-23T11:06:58.3771319Z goroutine 7340 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3772048Z sync.runtime_SemacquireMutex(0xc00010e504, 0x0, 0x0)
2021-07-23T11:06:58.3772889Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3773581Z sync.(*RWMutex).RLock(0xc00010e4f8)
2021-07-23T11:06:58.3774310Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:50 +0xa4
2021-07-23T11:06:58.3775449Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).getAccount(0xc00010e480, 0x0)
2021-07-23T11:06:58.3776626Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:51 +0x51
2021-07-23T11:06:58.3778270Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).finalize(0xc00010e480, 0xc0003b2630, 0xa97df1bc78dd5787, 0xcc8a4d69e7f5d62a, 0x1a4d7981bd86b087, 0xbafdb720c93480b3, 0x0, 0x0)
2021-07-23T11:06:58.3779845Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:306 +0x54
2021-07-23T11:06:58.3781022Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).PostPersist(0xc00010e480)
2021-07-23T11:06:58.3782232Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:297 +0x662
2021-07-23T11:06:58.3782989Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).Run(0xc00010e480)
2021-07-23T11:06:58.3783941Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:148 +0x3cb
2021-07-23T11:06:58.3784702Z created by github.com/nspcc-dev/neo-go/pkg/core.TestNotary
2021-07-23T11:06:58.3785451Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:132 +0x6e0
```
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
2021-07-23 11:28:11 +00:00
if err := n . finalize ( acc , r . main , payload . MainTransaction . Hash ( ) ) ; err != nil {
2021-10-20 15:43:32 +00:00
n . Config . Log . Error ( "failed to finalize main transaction" ,
zap . String ( "hash" , r . main . Hash ( ) . StringLE ( ) ) ,
zap . Error ( err ) )
2020-12-30 08:01:13 +00:00
}
}
}
// OnRequestRemoval is a callback which is called after fallback transaction is removed
// from the notary payload pool due to expiration, main tx appliance or any other reason.
func ( n * Notary ) OnRequestRemoval ( pld * payload . P2PNotaryRequest ) {
if n . getAccount ( ) == nil {
return
}
n . reqMtx . Lock ( )
defer n . reqMtx . Unlock ( )
r , ok := n . requests [ pld . MainTransaction . Hash ( ) ]
if ! ok {
return
}
for i , fb := range r . fallbacks {
if fb . Hash ( ) . Equals ( pld . FallbackTransaction . Hash ( ) ) {
r . fallbacks = append ( r . fallbacks [ : i ] , r . fallbacks [ i + 1 : ] ... )
break
}
}
if len ( r . fallbacks ) == 0 {
delete ( n . requests , r . main . Hash ( ) )
}
}
2021-02-09 13:03:06 +00:00
// PostPersist is a callback which is called after new block event is received.
// PostPersist must not be called under the blockchain lock, because it uses finalization function.
func ( n * Notary ) PostPersist ( ) {
notary: fix possible deadlock in `UpdateNotaryNodes`
`UpdateNotaryNodes` takes account then request mutex, and `PostPersist` takes
them in a different order. Because they are executed concurrently a deadlock
can appear.
```
2021-07-23T11:06:58.3732405Z panic: test timed out after 10m0s
2021-07-23T11:06:58.3732642Z
2021-07-23T11:06:58.3742610Z goroutine 7351 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3743140Z sync.runtime_SemacquireMutex(0xc00010e4dc, 0x1100000000, 0x1)
2021-07-23T11:06:58.3743747Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3744222Z sync.(*Mutex).lockSlow(0xc00010e4d8)
2021-07-23T11:06:58.3744742Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:138 +0x1c1
2021-07-23T11:06:58.3745209Z sync.(*Mutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3745692Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:81 +0x7d
2021-07-23T11:06:58.3746162Z sync.(*RWMutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3746764Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:98 +0x4a
2021-07-23T11:06:58.3747699Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).UpdateNotaryNodes(0xc00010e480, 0xc000105b90, 0x1, 0x1)
2021-07-23T11:06:58.3748621Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:44 +0x3ba
2021-07-23T11:06:58.3749367Z github.com/nspcc-dev/neo-go/pkg/core.TestNotary(0xc0003677a0)
2021-07-23T11:06:58.3750116Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:594 +0x2dba
2021-07-23T11:06:58.3750641Z testing.tRunner(0xc0003677a0, 0x16f3308)
2021-07-23T11:06:58.3751202Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1050 +0x1ec
2021-07-23T11:06:58.3751696Z created by testing.(*T).Run
2021-07-23T11:06:58.3752225Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1095 +0x538
2021-07-23T11:06:58.3752573Z
2021-07-23T11:06:58.3771319Z goroutine 7340 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3772048Z sync.runtime_SemacquireMutex(0xc00010e504, 0x0, 0x0)
2021-07-23T11:06:58.3772889Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3773581Z sync.(*RWMutex).RLock(0xc00010e4f8)
2021-07-23T11:06:58.3774310Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:50 +0xa4
2021-07-23T11:06:58.3775449Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).getAccount(0xc00010e480, 0x0)
2021-07-23T11:06:58.3776626Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:51 +0x51
2021-07-23T11:06:58.3778270Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).finalize(0xc00010e480, 0xc0003b2630, 0xa97df1bc78dd5787, 0xcc8a4d69e7f5d62a, 0x1a4d7981bd86b087, 0xbafdb720c93480b3, 0x0, 0x0)
2021-07-23T11:06:58.3779845Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:306 +0x54
2021-07-23T11:06:58.3781022Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).PostPersist(0xc00010e480)
2021-07-23T11:06:58.3782232Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:297 +0x662
2021-07-23T11:06:58.3782989Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).Run(0xc00010e480)
2021-07-23T11:06:58.3783941Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:148 +0x3cb
2021-07-23T11:06:58.3784702Z created by github.com/nspcc-dev/neo-go/pkg/core.TestNotary
2021-07-23T11:06:58.3785451Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:132 +0x6e0
```
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
2021-07-23 11:28:11 +00:00
acc := n . getAccount ( )
if acc == nil {
2020-12-30 08:01:13 +00:00
return
}
n . reqMtx . Lock ( )
defer n . reqMtx . Unlock ( )
2021-02-09 13:03:06 +00:00
currHeight := n . Config . Chain . BlockHeight ( )
2020-12-30 08:01:13 +00:00
for h , r := range n . requests {
2021-10-20 15:43:32 +00:00
if ! r . isSent && r . isMainCompleted ( ) && r . minNotValidBefore > currHeight {
notary: fix possible deadlock in `UpdateNotaryNodes`
`UpdateNotaryNodes` takes account then request mutex, and `PostPersist` takes
them in a different order. Because they are executed concurrently a deadlock
can appear.
```
2021-07-23T11:06:58.3732405Z panic: test timed out after 10m0s
2021-07-23T11:06:58.3732642Z
2021-07-23T11:06:58.3742610Z goroutine 7351 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3743140Z sync.runtime_SemacquireMutex(0xc00010e4dc, 0x1100000000, 0x1)
2021-07-23T11:06:58.3743747Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3744222Z sync.(*Mutex).lockSlow(0xc00010e4d8)
2021-07-23T11:06:58.3744742Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:138 +0x1c1
2021-07-23T11:06:58.3745209Z sync.(*Mutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3745692Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:81 +0x7d
2021-07-23T11:06:58.3746162Z sync.(*RWMutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3746764Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:98 +0x4a
2021-07-23T11:06:58.3747699Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).UpdateNotaryNodes(0xc00010e480, 0xc000105b90, 0x1, 0x1)
2021-07-23T11:06:58.3748621Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:44 +0x3ba
2021-07-23T11:06:58.3749367Z github.com/nspcc-dev/neo-go/pkg/core.TestNotary(0xc0003677a0)
2021-07-23T11:06:58.3750116Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:594 +0x2dba
2021-07-23T11:06:58.3750641Z testing.tRunner(0xc0003677a0, 0x16f3308)
2021-07-23T11:06:58.3751202Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1050 +0x1ec
2021-07-23T11:06:58.3751696Z created by testing.(*T).Run
2021-07-23T11:06:58.3752225Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1095 +0x538
2021-07-23T11:06:58.3752573Z
2021-07-23T11:06:58.3771319Z goroutine 7340 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3772048Z sync.runtime_SemacquireMutex(0xc00010e504, 0x0, 0x0)
2021-07-23T11:06:58.3772889Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3773581Z sync.(*RWMutex).RLock(0xc00010e4f8)
2021-07-23T11:06:58.3774310Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:50 +0xa4
2021-07-23T11:06:58.3775449Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).getAccount(0xc00010e480, 0x0)
2021-07-23T11:06:58.3776626Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:51 +0x51
2021-07-23T11:06:58.3778270Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).finalize(0xc00010e480, 0xc0003b2630, 0xa97df1bc78dd5787, 0xcc8a4d69e7f5d62a, 0x1a4d7981bd86b087, 0xbafdb720c93480b3, 0x0, 0x0)
2021-07-23T11:06:58.3779845Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:306 +0x54
2021-07-23T11:06:58.3781022Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).PostPersist(0xc00010e480)
2021-07-23T11:06:58.3782232Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:297 +0x662
2021-07-23T11:06:58.3782989Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).Run(0xc00010e480)
2021-07-23T11:06:58.3783941Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:148 +0x3cb
2021-07-23T11:06:58.3784702Z created by github.com/nspcc-dev/neo-go/pkg/core.TestNotary
2021-07-23T11:06:58.3785451Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:132 +0x6e0
```
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
2021-07-23 11:28:11 +00:00
if err := n . finalize ( acc , r . main , h ) ; err != nil {
2020-12-30 08:01:13 +00:00
n . Config . Log . Error ( "failed to finalize main transaction" , zap . Error ( err ) )
}
continue
}
2021-02-09 13:03:06 +00:00
if r . minNotValidBefore <= currHeight { // then at least one of the fallbacks can already be sent.
2020-12-30 08:01:13 +00:00
for _ , fb := range r . fallbacks {
2021-02-09 13:03:06 +00:00
if nvb := fb . GetAttributes ( transaction . NotValidBeforeT ) [ 0 ] . Value . ( * transaction . NotValidBefore ) . Height ; nvb <= currHeight {
2021-07-13 11:22:21 +00:00
// Ignore the error, wait for the next block to resend them
notary: fix possible deadlock in `UpdateNotaryNodes`
`UpdateNotaryNodes` takes account then request mutex, and `PostPersist` takes
them in a different order. Because they are executed concurrently a deadlock
can appear.
```
2021-07-23T11:06:58.3732405Z panic: test timed out after 10m0s
2021-07-23T11:06:58.3732642Z
2021-07-23T11:06:58.3742610Z goroutine 7351 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3743140Z sync.runtime_SemacquireMutex(0xc00010e4dc, 0x1100000000, 0x1)
2021-07-23T11:06:58.3743747Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3744222Z sync.(*Mutex).lockSlow(0xc00010e4d8)
2021-07-23T11:06:58.3744742Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:138 +0x1c1
2021-07-23T11:06:58.3745209Z sync.(*Mutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3745692Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:81 +0x7d
2021-07-23T11:06:58.3746162Z sync.(*RWMutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3746764Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:98 +0x4a
2021-07-23T11:06:58.3747699Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).UpdateNotaryNodes(0xc00010e480, 0xc000105b90, 0x1, 0x1)
2021-07-23T11:06:58.3748621Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:44 +0x3ba
2021-07-23T11:06:58.3749367Z github.com/nspcc-dev/neo-go/pkg/core.TestNotary(0xc0003677a0)
2021-07-23T11:06:58.3750116Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:594 +0x2dba
2021-07-23T11:06:58.3750641Z testing.tRunner(0xc0003677a0, 0x16f3308)
2021-07-23T11:06:58.3751202Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1050 +0x1ec
2021-07-23T11:06:58.3751696Z created by testing.(*T).Run
2021-07-23T11:06:58.3752225Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1095 +0x538
2021-07-23T11:06:58.3752573Z
2021-07-23T11:06:58.3771319Z goroutine 7340 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3772048Z sync.runtime_SemacquireMutex(0xc00010e504, 0x0, 0x0)
2021-07-23T11:06:58.3772889Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3773581Z sync.(*RWMutex).RLock(0xc00010e4f8)
2021-07-23T11:06:58.3774310Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:50 +0xa4
2021-07-23T11:06:58.3775449Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).getAccount(0xc00010e480, 0x0)
2021-07-23T11:06:58.3776626Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:51 +0x51
2021-07-23T11:06:58.3778270Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).finalize(0xc00010e480, 0xc0003b2630, 0xa97df1bc78dd5787, 0xcc8a4d69e7f5d62a, 0x1a4d7981bd86b087, 0xbafdb720c93480b3, 0x0, 0x0)
2021-07-23T11:06:58.3779845Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:306 +0x54
2021-07-23T11:06:58.3781022Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).PostPersist(0xc00010e480)
2021-07-23T11:06:58.3782232Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:297 +0x662
2021-07-23T11:06:58.3782989Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).Run(0xc00010e480)
2021-07-23T11:06:58.3783941Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:148 +0x3cb
2021-07-23T11:06:58.3784702Z created by github.com/nspcc-dev/neo-go/pkg/core.TestNotary
2021-07-23T11:06:58.3785451Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:132 +0x6e0
```
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
2021-07-23 11:28:11 +00:00
_ = n . finalize ( acc , fb , h )
2020-12-30 08:01:13 +00:00
}
}
}
}
}
// finalize adds missing Notary witnesses to the transaction (main or fallback) and pushes it to the network.
notary: fix possible deadlock in `UpdateNotaryNodes`
`UpdateNotaryNodes` takes account then request mutex, and `PostPersist` takes
them in a different order. Because they are executed concurrently a deadlock
can appear.
```
2021-07-23T11:06:58.3732405Z panic: test timed out after 10m0s
2021-07-23T11:06:58.3732642Z
2021-07-23T11:06:58.3742610Z goroutine 7351 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3743140Z sync.runtime_SemacquireMutex(0xc00010e4dc, 0x1100000000, 0x1)
2021-07-23T11:06:58.3743747Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3744222Z sync.(*Mutex).lockSlow(0xc00010e4d8)
2021-07-23T11:06:58.3744742Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:138 +0x1c1
2021-07-23T11:06:58.3745209Z sync.(*Mutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3745692Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/mutex.go:81 +0x7d
2021-07-23T11:06:58.3746162Z sync.(*RWMutex).Lock(0xc00010e4d8)
2021-07-23T11:06:58.3746764Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:98 +0x4a
2021-07-23T11:06:58.3747699Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).UpdateNotaryNodes(0xc00010e480, 0xc000105b90, 0x1, 0x1)
2021-07-23T11:06:58.3748621Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:44 +0x3ba
2021-07-23T11:06:58.3749367Z github.com/nspcc-dev/neo-go/pkg/core.TestNotary(0xc0003677a0)
2021-07-23T11:06:58.3750116Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:594 +0x2dba
2021-07-23T11:06:58.3750641Z testing.tRunner(0xc0003677a0, 0x16f3308)
2021-07-23T11:06:58.3751202Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1050 +0x1ec
2021-07-23T11:06:58.3751696Z created by testing.(*T).Run
2021-07-23T11:06:58.3752225Z /opt/hostedtoolcache/go/1.14.15/x64/src/testing/testing.go:1095 +0x538
2021-07-23T11:06:58.3752573Z
2021-07-23T11:06:58.3771319Z goroutine 7340 [semacquire, 9 minutes]:
2021-07-23T11:06:58.3772048Z sync.runtime_SemacquireMutex(0xc00010e504, 0x0, 0x0)
2021-07-23T11:06:58.3772889Z /opt/hostedtoolcache/go/1.14.15/x64/src/runtime/sema.go:71 +0x47
2021-07-23T11:06:58.3773581Z sync.(*RWMutex).RLock(0xc00010e4f8)
2021-07-23T11:06:58.3774310Z /opt/hostedtoolcache/go/1.14.15/x64/src/sync/rwmutex.go:50 +0xa4
2021-07-23T11:06:58.3775449Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).getAccount(0xc00010e480, 0x0)
2021-07-23T11:06:58.3776626Z /home/runner/work/neo-go/neo-go/pkg/services/notary/node.go:51 +0x51
2021-07-23T11:06:58.3778270Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).finalize(0xc00010e480, 0xc0003b2630, 0xa97df1bc78dd5787, 0xcc8a4d69e7f5d62a, 0x1a4d7981bd86b087, 0xbafdb720c93480b3, 0x0, 0x0)
2021-07-23T11:06:58.3779845Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:306 +0x54
2021-07-23T11:06:58.3781022Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).PostPersist(0xc00010e480)
2021-07-23T11:06:58.3782232Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:297 +0x662
2021-07-23T11:06:58.3782989Z github.com/nspcc-dev/neo-go/pkg/services/notary.(*Notary).Run(0xc00010e480)
2021-07-23T11:06:58.3783941Z /home/runner/work/neo-go/neo-go/pkg/services/notary/notary.go:148 +0x3cb
2021-07-23T11:06:58.3784702Z created by github.com/nspcc-dev/neo-go/pkg/core.TestNotary
2021-07-23T11:06:58.3785451Z /home/runner/work/neo-go/neo-go/pkg/core/notary_test.go:132 +0x6e0
```
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
2021-07-23 11:28:11 +00:00
func ( n * Notary ) finalize ( acc * wallet . Account , tx * transaction . Transaction , h util . Uint256 ) error {
2020-12-30 08:01:13 +00:00
notaryWitness := transaction . Witness {
2021-03-25 16:18:01 +00:00
InvocationScript : append ( [ ] byte { byte ( opcode . PUSHDATA1 ) , 64 } , acc . PrivateKey ( ) . SignHashable ( uint32 ( n . Network ) , tx ) ... ) ,
2020-12-30 08:01:13 +00:00
VerificationScript : [ ] byte { } ,
}
for i , signer := range tx . Signers {
if signer . Account == n . Config . Chain . GetNotaryContractScriptHash ( ) {
tx . Scripts [ i ] = notaryWitness
break
}
}
2021-02-24 08:35:09 +00:00
newTx , err := updateTxSize ( tx )
if err != nil {
return fmt . Errorf ( "failed to update completed transaction's size: %w" , err )
}
2021-07-13 11:22:21 +00:00
n . pushNewTx ( newTx , h )
return nil
}
type txHashPair struct {
tx * transaction . Transaction
mainHash util . Uint256
}
func ( n * Notary ) pushNewTx ( tx * transaction . Transaction , h util . Uint256 ) {
select {
case n . newTxs <- txHashPair { tx , h } :
default :
}
}
func ( n * Notary ) newTxCallbackLoop ( ) {
for {
select {
case tx := <- n . newTxs :
isMain := tx . tx . Hash ( ) == tx . mainHash
n . reqMtx . Lock ( )
r , ok := n . requests [ tx . mainHash ]
if ! ok || isMain && ( r . isSent || r . minNotValidBefore <= n . Config . Chain . BlockHeight ( ) ) {
n . reqMtx . Unlock ( )
continue
}
if ! isMain {
// Ensure that fallback was not already completed.
var isPending bool
for _ , fb := range r . fallbacks {
if fb . Hash ( ) == tx . tx . Hash ( ) {
isPending = true
break
}
}
if ! isPending {
n . reqMtx . Unlock ( )
continue
}
}
n . reqMtx . Unlock ( )
err := n . onTransaction ( tx . tx )
if err != nil {
n . Config . Log . Error ( "new transaction callback finished with error" , zap . Error ( err ) )
continue
}
n . reqMtx . Lock ( )
if isMain {
r . isSent = true
} else {
for i := range r . fallbacks {
if r . fallbacks [ i ] . Hash ( ) == tx . tx . Hash ( ) {
r . fallbacks = append ( r . fallbacks [ : i ] , r . fallbacks [ i + 1 : ] ... )
break
}
}
if len ( r . fallbacks ) == 0 {
delete ( n . requests , tx . mainHash )
}
}
n . reqMtx . Unlock ( )
case <- n . stopCh :
return
}
}
2021-02-24 08:35:09 +00:00
}
// updateTxSize returns transaction with re-calculated size and an error.
func updateTxSize ( tx * transaction . Transaction ) ( * transaction . Transaction , error ) {
bw := io . NewBufBinWriter ( )
tx . EncodeBinary ( bw . BinWriter )
if bw . Err != nil {
return nil , fmt . Errorf ( "encode binary: %w" , bw . Err )
}
2021-03-25 16:18:01 +00:00
return transaction . NewTransactionFromBytes ( tx . Bytes ( ) )
2020-12-30 08:01:13 +00:00
}
// 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.
2021-10-20 15:43:32 +00:00
func ( n * Notary ) verifyIncompleteWitnesses ( tx * transaction . Transaction , nKeysExpected uint8 ) ( [ ] witnessInfo , error ) {
var nKeysActual uint8
2020-12-30 08:01:13 +00:00
if len ( tx . Signers ) < 2 {
2021-10-20 15:43:32 +00:00
return nil , errors . New ( "transaction should have at least 2 signers" )
2020-12-30 08:01:13 +00:00
}
if ! tx . HasSigner ( n . Config . Chain . GetNotaryContractScriptHash ( ) ) {
2021-10-20 15:43:32 +00:00
return nil , fmt . Errorf ( "P2PNotary contract should be a signer of the transaction" )
2020-12-30 08:01:13 +00:00
}
2021-10-20 15:43:32 +00:00
result := make ( [ ] witnessInfo , len ( tx . Signers ) )
2020-12-30 08:01:13 +00:00
for i , w := range tx . Scripts {
2021-10-20 15:43:32 +00:00
// 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)
2020-12-30 08:01:13 +00:00
if len ( w . VerificationScript ) == 0 {
2021-10-20 15:43:32 +00:00
result [ i ] = witnessInfo {
typ : Contract ,
nSigsLeft : 0 ,
}
2020-12-30 08:01:13 +00:00
continue
}
if ! tx . Signers [ i ] . Account . Equals ( hash . Hash160 ( w . VerificationScript ) ) { // https://github.com/nspcc-dev/neo-go/pull/1658#discussion_r564265987
2021-10-20 15:43:32 +00:00
return nil , fmt . Errorf ( "transaction should have valid verification script for signer #%d" , i )
2020-12-30 08:01:13 +00:00
}
2021-10-20 15:43:32 +00:00
// 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 {
2020-12-30 08:01:13 +00:00
if len ( w . InvocationScript ) != 66 || ! bytes . HasPrefix ( w . InvocationScript , [ ] byte { byte ( opcode . PUSHDATA1 ) , 64 } ) {
2021-10-20 15:43:32 +00:00
return nil , fmt . Errorf ( "witness #%d: invocation script should have length = 66 and be of the form [PUSHDATA1, 64, signatureBytes...]" , i )
2020-12-30 08:01:13 +00:00
}
}
2021-10-20 15:43:32 +00:00
if nSigs , pubsBytes , ok := vm . ParseMultiSigContract ( w . VerificationScript ) ; ok {
result [ i ] = witnessInfo {
typ : MultiSignature ,
nSigsLeft : uint8 ( nSigs ) ,
pubs : make ( keys . PublicKeys , len ( pubsBytes ) ) ,
2020-12-30 08:01:13 +00:00
}
2021-10-20 15:43:32 +00:00
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 ) )
2020-12-30 08:01:13 +00:00
continue
}
2021-10-20 15:43:32 +00:00
if pBytes , ok := vm . ParseSignatureContract ( w . VerificationScript ) ; ok {
2020-12-30 08:01:13 +00:00
pub , err := keys . NewPublicKeyFromBytes ( pBytes , elliptic . P256 ( ) )
if err != nil {
2021-10-20 15:43:32 +00:00
return nil , fmt . Errorf ( "witness #%d: invalid bytes of public key: %s" , i , hex . EncodeToString ( pBytes ) )
2020-12-30 08:01:13 +00:00
}
2021-10-20 15:43:32 +00:00
result [ i ] = witnessInfo {
typ : Signature ,
nSigsLeft : 1 ,
pubs : keys . PublicKeys { pub } ,
}
nKeysActual ++
continue
2020-12-30 08:01:13 +00:00
}
2021-10-20 15:43:32 +00:00
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 )
2020-12-30 08:01:13 +00:00
}
2021-10-20 15:43:32 +00:00
return result , nil
2020-12-30 08:01:13 +00:00
}