neo-go/pkg/services/notary/notary.go

579 lines
19 KiB
Go
Raw Normal View History

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"
"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/mempool"
"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"
"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/util/slice"
2020-12-30 08:01:13 +00:00
"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/atomic"
2020-12-30 08:01:13 +00:00
"go.uber.org/zap"
)
type (
// Ledger is the interface to Blockchain sufficient for Notary.
Ledger interface {
BlockHeight() uint32
GetMaxVerificationGAS() int64
GetNotaryContractScriptHash() util.Uint160
SubscribeForBlocks(ch chan *block.Block)
UnsubscribeFromBlocks(ch chan *block.Block)
VerifyWitness(util.Uint160, hash.Hashable, *transaction.Witness, int64) (int64, error)
}
// Notary represents a Notary module.
2020-12-30 08:01:13 +00:00
Notary struct {
Config Config
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
// newTxs is a channel where new transactions are sent
// to be processed in an `onTransaction` callback.
newTxs chan txHashPair
// started is a status bool to protect from double start/shutdown.
started *atomic.Bool
2020-12-30 08:01:13 +00:00
// reqMtx protects requests list.
reqMtx sync.RWMutex
// requests represents a map of main transactions which needs to be completed
2020-12-30 08:01:13 +00:00
// 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
reqCh chan mempoolevent.Event
// blocksCh is a channel used to receive block notifications from the
// Blockchain. It is not buffered intentionally, as it's important to keep
// the notary request pool in sync with the current blockchain heigh, thus,
// it's not recommended to use a large size of notary requests pool as it may
// slow down the block processing.
blocksCh chan *block.Block
stopCh chan struct{}
done chan struct{}
2020-12-30 08:01:13 +00:00
}
// Config represents external configuration for Notary module.
Config struct {
MainCfg config.P2PNotary
Chain Ledger
2020-12-30 08:01:13 +00:00
Log *zap.Logger
}
)
const defaultTxChannelCapacity = 100
type (
// request represents Notary service request.
request struct {
// isSent indicates whether the 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 the mainTx to the network if the chain reaches the minNotValidBefore height.
minNotValidBefore uint32
fallbacks []*transaction.Transaction
witnessInfo []witnessInfo
}
// witnessInfo represents information about the signer and its witness.
witnessInfo struct {
typ RequestType
// nSigsLeft is the number of signatures left to collect to complete the 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 were 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 a new Notary module.
func NewNotary(cfg Config, net netmode.Magic, mp *mempool.Pool, onTransaction func(tx *transaction.Transaction) error) (*Notary, error) {
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 {
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{
requests: make(map[util.Uint256]*request),
Config: cfg,
Network: net,
started: atomic.NewBool(false),
2020-12-30 08:01:13 +00:00
wallet: wallet,
onTransaction: onTransaction,
newTxs: make(chan txHashPair, defaultTxChannelCapacity),
2021-01-15 12:40:15 +00:00
mp: mp,
reqCh: make(chan mempoolevent.Event),
blocksCh: make(chan *block.Block),
2021-01-15 12:40:15 +00:00
stopCh: make(chan struct{}),
done: make(chan struct{}),
2020-12-30 08:01:13 +00:00
}, nil
}
// Name returns service name.
func (n *Notary) Name() string {
return "notary"
}
// Start runs a Notary module in a separate goroutine.
// The Notary only starts once, subsequent calls to Start are no-op.
func (n *Notary) Start() {
if !n.started.CompareAndSwap(false, true) {
return
}
n.Config.Log.Info("starting notary service")
n.Config.Chain.SubscribeForBlocks(n.blocksCh)
2021-01-15 12:40:15 +00:00
n.mp.SubscribeForTransactions(n.reqCh)
go n.newTxCallbackLoop()
go n.mainLoop()
}
func (n *Notary) mainLoop() {
mainloop:
2021-01-15 12:40:15 +00:00
for {
select {
case <-n.stopCh:
n.mp.UnsubscribeFromTransactions(n.reqCh)
n.Config.Chain.UnsubscribeFromBlocks(n.blocksCh)
break mainloop
2021-01-15 12:40:15 +00:00
case event := <-n.reqCh:
if req, ok := event.Data.(*payload.P2PNotaryRequest); ok {
switch event.Type {
case mempoolevent.TransactionAdded:
2021-01-15 12:40:15 +00:00
n.OnNewRequest(req)
case mempoolevent.TransactionRemoved:
2021-01-15 12:40:15 +00:00
n.OnRequestRemoval(req)
}
}
case <-n.blocksCh:
// a new block was added, we need to check for valid fallbacks
n.PostPersist()
2021-01-15 12:40:15 +00:00
}
}
drainLoop:
for {
select {
case <-n.blocksCh:
case <-n.reqCh:
default:
break drainLoop
}
}
close(n.blocksCh)
close(n.reqCh)
close(n.done)
2021-01-15 12:40:15 +00:00
}
// Shutdown stops the Notary module. It can only be called once, subsequent calls
// to Shutdown on the same instance are no-op. The instance that was stopped can
// not be started again by calling Start (use a new instance if needed).
func (n *Notary) Shutdown() {
if !n.started.CompareAndSwap(true, false) {
return
}
2022-07-26 19:03:58 +00:00
n.Config.Log.Info("stopping notary service")
2021-01-15 12:40:15 +00:00
close(n.stopCh)
<-n.done
n.wallet.Close()
2021-01-15 12:40:15 +00:00
}
// OnNewRequest is a callback method which is called after a new notary request is added to the notary request pool.
2020-12-30 08:01:13 +00:00
func (n *Notary) OnNewRequest(payload *payload.P2PNotaryRequest) {
if !n.started.Load() {
return
}
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
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 {
// Avoid changes in the main transaction witnesses got from the notary request pool to
// keep the pooled tx valid. We will update its copy => the copy's size will be changed.
2020-12-30 08:01:13 +00:00
r = &request{
main: safeCopy(payload.MainTransaction),
2020-12-30 08:01:13 +00:00
minNotValidBefore: nvbFallback,
}
n.requests[payload.MainTransaction.Hash()] = r
}
if r.witnessInfo == nil && validationErr == nil {
r.witnessInfo = newInfo
}
// Disallow modification of a fallback transaction got from the notary
// request pool. Even though it has dummy Notary witness attached and its
// size won't be changed after finalisation, the witness bytes changes may
// affect the other users of notary pool and cause race. Avoid this by making
// the copy.
r.fallbacks = append(r.fallbacks, safeCopy(payload.FallbackTransaction))
if exists && r.isMainCompleted() || validationErr != nil {
2020-12-30 08:01:13 +00:00
return
}
mainHash := hash.NetSha256(uint32(n.Network), r.main).BytesBE()
for i, w := range payload.MainTransaction.Scripts {
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)
continue
}
switch r.witnessInfo[i].typ {
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.GetMaxVerificationGAS())
if err != nil {
continue
}
r.main.Scripts[i].InvocationScript = w.InvocationScript
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
}
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
}
}
r.main.Scripts[i].InvocationScript = invScript
2020-12-30 08:01:13 +00:00
}
break
2020-12-30 08:01:13 +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
}
}
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 {
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
}
}
}
// safeCopy creates a copy of provided transaction by dereferencing it and creating
// fresh witnesses so that the tx's witnesses may be modified without affecting the
// copy's ones.
func safeCopy(tx *transaction.Transaction) *transaction.Transaction {
cp := *tx
cp.Scripts = make([]transaction.Witness, len(tx.Scripts))
for i := range cp.Scripts {
cp.Scripts[i] = transaction.Witness{
InvocationScript: slice.Copy(tx.Scripts[i].InvocationScript),
VerificationScript: slice.Copy(tx.Scripts[i].VerificationScript),
}
}
return &cp
}
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.started.Load() || n.getAccount() == nil {
2020-12-30 08:01:13 +00:00
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())
}
}
// PostPersist is a callback which is called after a new block event is received.
// PostPersist must not be called under the blockchain lock, because it uses finalization function.
func (n *Notary) PostPersist() {
if !n.started.Load() {
return
}
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()
currHeight := n.Config.Chain.BlockHeight()
2020-12-30 08:01:13 +00:00
for h, r := range n.requests {
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
}
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 {
if nvb := fb.GetAttributes(transaction.NotValidBeforeT)[0].Value.(*transaction.NotValidBefore).Height; nvb <= currHeight {
// 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{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen}, acc.SignHashable(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
}
}
newTx, err := updateTxSize(tx)
if err != nil {
return fmt.Errorf("failed to update completed transaction's size: %w", err)
}
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
}
}
}
// updateTxSize returns a 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)
}
return transaction.NewTransactionFromBytes(tx.Bytes())
2020-12-30 08:01:13 +00:00
}
// verifyIncompleteWitnesses checks that the 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
2020-12-30 08:01:13 +00:00
// number of signatures to be collected, sorted public keys (for multisig request only) and an error.
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 {
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()) {
return nil, fmt.Errorf("P2PNotary contract should be a signer of the transaction")
2020-12-30 08:01:13 +00:00
}
result := make([]witnessInfo, len(tx.Signers))
2020-12-30 08:01:13 +00:00
for i, w := range tx.Scripts {
// Do not check witness for a 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 {
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
return nil, fmt.Errorf("transaction should have valid verification script for signer #%d", i)
2020-12-30 08:01:13 +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 {
if len(w.InvocationScript) != 66 || !bytes.HasPrefix(w.InvocationScript, []byte{byte(opcode.PUSHDATA1), keys.SignatureLen}) {
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
}
}
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
}
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
}
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 {
return nil, fmt.Errorf("witness #%d: invalid bytes of public key: %s", i, hex.EncodeToString(pBytes))
2020-12-30 08:01:13 +00:00
}
result[i] = witnessInfo{
typ: Signature,
nSigsLeft: 1,
pubs: keys.PublicKeys{pub},
}
nKeysActual++
continue
2020-12-30 08:01:13 +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
}
return result, nil
2020-12-30 08:01:13 +00:00
}