neotest: support painless multi-signing
Implementing a separate `Signer` interface is beneficial in multiple ways: 1. Support both single and multiple transaction witnesses. 2. It should be easy to add contract signer this way. Tests should use accounts created with `NewAccount` so hiding all details doesn't seem to be an issue. Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
1f9fd4a472
commit
950adb7b89
5 changed files with 197 additions and 87 deletions
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ func TestNameService_Price(t *testing.T) {
|
||||||
|
|
||||||
t.Run("set, not signed by committee", func(t *testing.T) {
|
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||||
acc := c.NewAccount(t)
|
acc := c.NewAccount(t)
|
||||||
cAcc := c.WithSigner(acc)
|
cAcc := c.WithSigners(acc)
|
||||||
cAcc.InvokeFail(t, "not witnessed by committee", "setPrice", minPrice+1)
|
cAcc.InvokeFail(t, "not witnessed by committee", "setPrice", minPrice+1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -68,7 +67,7 @@ func TestNameService_Price(t *testing.T) {
|
||||||
func TestNonfungible(t *testing.T) {
|
func TestNonfungible(t *testing.T) {
|
||||||
c := newNSClient(t)
|
c := newNSClient(t)
|
||||||
|
|
||||||
c.Signer = c.NewAccount(t)
|
c.Signers = []neotest.Signer{c.NewAccount(t)}
|
||||||
c.Invoke(t, "NNS", "symbol")
|
c.Invoke(t, "NNS", "symbol")
|
||||||
c.Invoke(t, 0, "decimals")
|
c.Invoke(t, 0, "decimals")
|
||||||
c.Invoke(t, 0, "totalSupply")
|
c.Invoke(t, 0, "totalSupply")
|
||||||
|
@ -82,7 +81,7 @@ func TestAddRoot(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("not signed by committee", func(t *testing.T) {
|
t.Run("not signed by committee", func(t *testing.T) {
|
||||||
acc := c.NewAccount(t)
|
acc := c.NewAccount(t)
|
||||||
c := c.WithSigner(acc)
|
c := c.WithSigners(acc)
|
||||||
c.InvokeFail(t, "not witnessed by committee", "addRoot", "some")
|
c.InvokeFail(t, "not witnessed by committee", "addRoot", "some")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -98,14 +97,14 @@ func TestExpiration(t *testing.T) {
|
||||||
bc := e.Chain
|
bc := e.Chain
|
||||||
|
|
||||||
acc := e.NewAccount(t)
|
acc := e.NewAccount(t)
|
||||||
cAcc := c.WithSigner(acc)
|
cAcc := c.WithSigners(acc)
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||||
cAcc.Invoke(t, true, "register", "first.com", acc.Contract.ScriptHash())
|
cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash())
|
||||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext")
|
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext")
|
||||||
b1 := e.TopBlock(t)
|
b1 := e.TopBlock(t)
|
||||||
|
|
||||||
tx := cAcc.PrepareInvoke(t, "register", "second.com", acc.Contract.ScriptHash())
|
tx := cAcc.PrepareInvoke(t, "register", "second.com", acc.ScriptHash())
|
||||||
b2 := e.NewUnsignedBlock(t, tx)
|
b2 := e.NewUnsignedBlock(t, tx)
|
||||||
b2.Index = b1.Index + 1
|
b2.Index = b1.Index + 1
|
||||||
b2.PrevHash = b1.Hash()
|
b2.PrevHash = b1.Hash()
|
||||||
|
@ -191,7 +190,7 @@ func TestSetGetRecord(t *testing.T) {
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
|
||||||
acc := e.NewAccount(t)
|
acc := e.NewAccount(t)
|
||||||
cAcc := c.WithSigner(acc)
|
cAcc := c.WithSigners(acc)
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||||
|
|
||||||
t.Run("set before register", func(t *testing.T) {
|
t.Run("set before register", func(t *testing.T) {
|
||||||
|
@ -295,22 +294,22 @@ func TestSetAdmin(t *testing.T) {
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
|
||||||
owner := e.NewAccount(t)
|
owner := e.NewAccount(t)
|
||||||
cOwner := c.WithSigner(owner)
|
cOwner := c.WithSigners(owner)
|
||||||
admin := e.NewAccount(t)
|
admin := e.NewAccount(t)
|
||||||
cAdmin := c.WithSigner(admin)
|
cAdmin := c.WithSigners(admin)
|
||||||
guest := e.NewAccount(t)
|
guest := e.NewAccount(t)
|
||||||
cGuest := c.WithSigner(guest)
|
cGuest := c.WithSigners(guest)
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||||
|
|
||||||
cOwner.Invoke(t, true, "register", "neo.com", owner.PrivateKey().GetScriptHash())
|
cOwner.Invoke(t, true, "register", "neo.com", owner.ScriptHash())
|
||||||
cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
|
cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.ScriptHash())
|
||||||
|
|
||||||
// Must be witnessed by both owner and admin.
|
// Must be witnessed by both owner and admin.
|
||||||
cOwner.InvokeFail(t, "not witnessed by admin", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
|
cOwner.InvokeFail(t, "not witnessed by admin", "setAdmin", "neo.com", admin.ScriptHash())
|
||||||
cAdmin.InvokeFail(t, "not witnessed by owner", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
|
cAdmin.InvokeFail(t, "not witnessed by owner", "setAdmin", "neo.com", admin.ScriptHash())
|
||||||
cc := c.WithSigner([]*wallet.Account{owner, admin})
|
cc := c.WithSigners(owner, admin)
|
||||||
cc.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
|
cc.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", admin.ScriptHash())
|
||||||
|
|
||||||
t.Run("set and delete by admin", func(t *testing.T) {
|
t.Run("set and delete by admin", func(t *testing.T) {
|
||||||
cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
|
cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
|
||||||
|
@ -330,18 +329,18 @@ func TestTransfer(t *testing.T) {
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
|
||||||
from := e.NewAccount(t)
|
from := e.NewAccount(t)
|
||||||
cFrom := c.WithSigner(from)
|
cFrom := c.WithSigners(from)
|
||||||
to := e.NewAccount(t)
|
to := e.NewAccount(t)
|
||||||
cTo := c.WithSigner(to)
|
cTo := c.WithSigners(to)
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||||
cFrom.Invoke(t, true, "register", "neo.com", from.PrivateKey().GetScriptHash())
|
cFrom.Invoke(t, true, "register", "neo.com", from.ScriptHash())
|
||||||
cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||||
cFrom.InvokeFail(t, "token not found", "transfer", to.Contract.ScriptHash(), "not.exists", nil)
|
cFrom.InvokeFail(t, "token not found", "transfer", to.ScriptHash(), "not.exists", nil)
|
||||||
c.Invoke(t, false, "transfer", to.Contract.ScriptHash(), "neo.com", nil)
|
c.Invoke(t, false, "transfer", to.ScriptHash(), "neo.com", nil)
|
||||||
cFrom.Invoke(t, true, "transfer", to.Contract.ScriptHash(), "neo.com", nil)
|
cFrom.Invoke(t, true, "transfer", to.ScriptHash(), "neo.com", nil)
|
||||||
cFrom.Invoke(t, 1, "totalSupply")
|
cFrom.Invoke(t, 1, "totalSupply")
|
||||||
cFrom.Invoke(t, to.Contract.ScriptHash().BytesBE(), "ownerOf", "neo.com")
|
cFrom.Invoke(t, to.ScriptHash().BytesBE(), "ownerOf", "neo.com")
|
||||||
|
|
||||||
// without onNEP11Transfer
|
// without onNEP11Transfer
|
||||||
ctr := neotest.CompileSource(t, e.CommitteeHash,
|
ctr := neotest.CompileSource(t, e.CommitteeHash,
|
||||||
|
@ -368,16 +367,16 @@ func TestTokensOf(t *testing.T) {
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
|
||||||
acc1 := e.NewAccount(t)
|
acc1 := e.NewAccount(t)
|
||||||
cAcc1 := c.WithSigner(acc1)
|
cAcc1 := c.WithSigners(acc1)
|
||||||
acc2 := e.NewAccount(t)
|
acc2 := e.NewAccount(t)
|
||||||
cAcc2 := c.WithSigner(acc2)
|
cAcc2 := c.WithSigners(acc2)
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||||
cAcc1.Invoke(t, true, "register", "neo.com", acc1.PrivateKey().GetScriptHash())
|
cAcc1.Invoke(t, true, "register", "neo.com", acc1.ScriptHash())
|
||||||
cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.PrivateKey().GetScriptHash())
|
cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash())
|
||||||
|
|
||||||
testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.Contract.ScriptHash().BytesBE())
|
testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
|
||||||
testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.Contract.ScriptHash().BytesBE())
|
testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
|
||||||
testTokensOf(t, c, [][]byte{[]byte("neo.com"), []byte("nspcc.com")})
|
testTokensOf(t, c, [][]byte{[]byte("neo.com"), []byte("nspcc.com")})
|
||||||
testTokensOf(t, c, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still
|
testTokensOf(t, c, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still
|
||||||
}
|
}
|
||||||
|
@ -408,14 +407,14 @@ func TestResolve(t *testing.T) {
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
|
||||||
acc := e.NewAccount(t)
|
acc := e.NewAccount(t)
|
||||||
cAcc := c.WithSigner(acc)
|
cAcc := c.WithSigners(acc)
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||||
cAcc.Invoke(t, true, "register", "neo.com", acc.PrivateKey().GetScriptHash())
|
cAcc.Invoke(t, true, "register", "neo.com", acc.ScriptHash())
|
||||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com")
|
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com")
|
||||||
|
|
||||||
cAcc.Invoke(t, true, "register", "alias.com", acc.PrivateKey().GetScriptHash())
|
cAcc.Invoke(t, true, "register", "alias.com", acc.ScriptHash())
|
||||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "alias.com", int64(nns.TXT), "sometxt")
|
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "alias.com", int64(nns.TXT), "sometxt")
|
||||||
|
|
||||||
c.Invoke(t, "1.2.3.4", "resolve", "neo.com", int64(nns.A))
|
c.Invoke(t, "1.2.3.4", "resolve", "neo.com", int64(nns.A))
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -28,19 +27,20 @@ import (
|
||||||
// Executor is a wrapper over chain state.
|
// Executor is a wrapper over chain state.
|
||||||
type Executor struct {
|
type Executor struct {
|
||||||
Chain blockchainer.Blockchainer
|
Chain blockchainer.Blockchainer
|
||||||
Committee *wallet.Account
|
Committee Signer
|
||||||
CommitteeHash util.Uint160
|
CommitteeHash util.Uint160
|
||||||
Contracts map[string]*Contract
|
Contracts map[string]*Contract
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExecutor creates new executor instance from provided blockchain and committee.
|
// NewExecutor creates new executor instance from provided blockchain and committee.
|
||||||
func NewExecutor(t *testing.T, bc blockchainer.Blockchainer, committee *wallet.Account) *Executor {
|
func NewExecutor(t *testing.T, bc blockchainer.Blockchainer, committee Signer) *Executor {
|
||||||
require.Equal(t, 1, len(bc.GetConfig().StandbyCommittee))
|
require.Equal(t, 1, len(bc.GetConfig().StandbyCommittee))
|
||||||
|
require.IsType(t, multiSigner{}, committee, "committee must be a multi-signer")
|
||||||
|
|
||||||
return &Executor{
|
return &Executor{
|
||||||
Chain: bc,
|
Chain: bc,
|
||||||
Committee: committee,
|
Committee: committee,
|
||||||
CommitteeHash: committee.Contract.ScriptHash(),
|
CommitteeHash: committee.ScriptHash(),
|
||||||
Contracts: make(map[string]*Contract),
|
Contracts: make(map[string]*Contract),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,57 +74,41 @@ func (e *Executor) NewUnsignedTx(t *testing.T, hash util.Uint160, method string,
|
||||||
|
|
||||||
// NewTx creates new transaction which invokes contract method.
|
// NewTx creates new transaction which invokes contract method.
|
||||||
// Transaction is signed with signer.
|
// Transaction is signed with signer.
|
||||||
func (e *Executor) NewTx(t *testing.T, signer interface{},
|
func (e *Executor) NewTx(t *testing.T, signers []Signer,
|
||||||
hash util.Uint160, method string, args ...interface{}) *transaction.Transaction {
|
hash util.Uint160, method string, args ...interface{}) *transaction.Transaction {
|
||||||
tx := e.NewUnsignedTx(t, hash, method, args...)
|
tx := e.NewUnsignedTx(t, hash, method, args...)
|
||||||
return e.SignTx(t, tx, -1, signer)
|
return e.SignTx(t, tx, -1, signers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignTx signs a transaction using provided signers.
|
// SignTx signs a transaction using provided signers.
|
||||||
// signers can be either *wallet.Account or []*wallet.Account.
|
func (e *Executor) SignTx(t *testing.T, tx *transaction.Transaction, sysFee int64, signers ...Signer) *transaction.Transaction {
|
||||||
func (e *Executor) SignTx(t *testing.T, tx *transaction.Transaction, sysFee int64, signers interface{}) *transaction.Transaction {
|
for _, acc := range signers {
|
||||||
switch s := signers.(type) {
|
|
||||||
case *wallet.Account:
|
|
||||||
tx.Signers = append(tx.Signers, transaction.Signer{
|
tx.Signers = append(tx.Signers, transaction.Signer{
|
||||||
Account: s.Contract.ScriptHash(),
|
Account: acc.ScriptHash(),
|
||||||
Scopes: transaction.Global,
|
|
||||||
})
|
|
||||||
addNetworkFee(e.Chain, tx, s)
|
|
||||||
addSystemFee(e.Chain, tx, sysFee)
|
|
||||||
require.NoError(t, s.SignTx(e.Chain.GetConfig().Magic, tx))
|
|
||||||
case []*wallet.Account:
|
|
||||||
for _, acc := range s {
|
|
||||||
tx.Signers = append(tx.Signers, transaction.Signer{
|
|
||||||
Account: acc.Contract.ScriptHash(),
|
|
||||||
Scopes: transaction.Global,
|
Scopes: transaction.Global,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for _, acc := range s {
|
addNetworkFee(e.Chain, tx, signers...)
|
||||||
addNetworkFee(e.Chain, tx, acc)
|
|
||||||
}
|
|
||||||
addSystemFee(e.Chain, tx, sysFee)
|
addSystemFee(e.Chain, tx, sysFee)
|
||||||
for _, acc := range s {
|
|
||||||
|
for _, acc := range signers {
|
||||||
require.NoError(t, acc.SignTx(e.Chain.GetConfig().Magic, tx))
|
require.NoError(t, acc.SignTx(e.Chain.GetConfig().Magic, tx))
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
panic("invalid signer")
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccount returns new account holding 100.0 GAS. This method advances the chain
|
// NewAccount returns new signer holding 100.0 GAS. This method advances the chain
|
||||||
// by one block with a transfer transaction.
|
// by one block with a transfer transaction.
|
||||||
func (e *Executor) NewAccount(t *testing.T) *wallet.Account {
|
func (e *Executor) NewAccount(t *testing.T) Signer {
|
||||||
acc, err := wallet.NewAccount()
|
acc, err := wallet.NewAccount()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tx := e.NewTx(t, e.Committee,
|
tx := e.NewTx(t, []Signer{e.Committee},
|
||||||
e.NativeHash(t, nativenames.Gas), "transfer",
|
e.NativeHash(t, nativenames.Gas), "transfer",
|
||||||
e.Committee.Contract.ScriptHash(), acc.Contract.ScriptHash(), int64(100_0000_0000), nil)
|
e.Committee.ScriptHash(), acc.Contract.ScriptHash(), int64(100_0000_0000), nil)
|
||||||
e.AddNewBlock(t, tx)
|
e.AddNewBlock(t, tx)
|
||||||
e.CheckHalt(t, tx.Hash())
|
e.CheckHalt(t, tx.Hash())
|
||||||
return acc
|
return NewSingleSigner(acc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeployContract compiles and deploys contract to bc.
|
// DeployContract compiles and deploys contract to bc.
|
||||||
|
@ -174,7 +158,7 @@ func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Co
|
||||||
tx.Nonce = nonce()
|
tx.Nonce = nonce()
|
||||||
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||||
tx.Signers = []transaction.Signer{{
|
tx.Signers = []transaction.Signer{{
|
||||||
Account: e.Committee.Contract.ScriptHash(),
|
Account: e.Committee.ScriptHash(),
|
||||||
Scopes: transaction.Global,
|
Scopes: transaction.Global,
|
||||||
}}
|
}}
|
||||||
addNetworkFee(bc, tx, e.Committee)
|
addNetworkFee(bc, tx, e.Committee)
|
||||||
|
@ -191,12 +175,14 @@ func addSystemFee(bc blockchainer.Blockchainer, tx *transaction.Transaction, sys
|
||||||
tx.SystemFee = v.GasConsumed()
|
tx.SystemFee = v.GasConsumed()
|
||||||
}
|
}
|
||||||
|
|
||||||
func addNetworkFee(bc blockchainer.Blockchainer, tx *transaction.Transaction, sender *wallet.Account) {
|
func addNetworkFee(bc blockchainer.Blockchainer, tx *transaction.Transaction, signers ...Signer) {
|
||||||
baseFee := bc.GetPolicer().GetBaseExecFee()
|
baseFee := bc.GetPolicer().GetBaseExecFee()
|
||||||
size := io.GetVarSize(tx)
|
size := io.GetVarSize(tx)
|
||||||
netFee, sizeDelta := fee.Calculate(baseFee, sender.Contract.Script)
|
for _, sgr := range signers {
|
||||||
|
netFee, sizeDelta := fee.Calculate(baseFee, sgr.Script())
|
||||||
tx.NetworkFee += netFee
|
tx.NetworkFee += netFee
|
||||||
size += sizeDelta
|
size += sizeDelta
|
||||||
|
}
|
||||||
tx.NetworkFee += int64(size) * bc.FeePerByte()
|
tx.NetworkFee += int64(size) * bc.FeePerByte()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,9 +191,9 @@ func (e *Executor) NewUnsignedBlock(t *testing.T, txs ...*transaction.Transactio
|
||||||
lastBlock := e.TopBlock(t)
|
lastBlock := e.TopBlock(t)
|
||||||
b := &block.Block{
|
b := &block.Block{
|
||||||
Header: block.Header{
|
Header: block.Header{
|
||||||
NextConsensus: e.Committee.Contract.ScriptHash(),
|
NextConsensus: e.Committee.ScriptHash(),
|
||||||
Script: transaction.Witness{
|
Script: transaction.Witness{
|
||||||
VerificationScript: e.Committee.Contract.Script,
|
VerificationScript: e.Committee.Script(),
|
||||||
},
|
},
|
||||||
Timestamp: lastBlock.Timestamp + 1,
|
Timestamp: lastBlock.Timestamp + 1,
|
||||||
},
|
},
|
||||||
|
@ -233,8 +219,8 @@ func (e *Executor) AddNewBlock(t *testing.T, txs ...*transaction.Transaction) *b
|
||||||
|
|
||||||
// SignBlock add validators signature to b.
|
// SignBlock add validators signature to b.
|
||||||
func (e *Executor) SignBlock(b *block.Block) *block.Block {
|
func (e *Executor) SignBlock(b *block.Block) *block.Block {
|
||||||
sign := e.Committee.PrivateKey().SignHashable(uint32(e.Chain.GetConfig().Magic), b)
|
invoc := e.Committee.SignHashable(uint32(e.Chain.GetConfig().Magic), b)
|
||||||
b.Script.InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, sign...)
|
b.Script.InvocationScript = invoc
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
|
@ -30,7 +31,7 @@ func init() {
|
||||||
|
|
||||||
// NewSingle creates new blockchain instance with a single validator and
|
// NewSingle creates new blockchain instance with a single validator and
|
||||||
// setups cleanup functions.
|
// setups cleanup functions.
|
||||||
func NewSingle(t *testing.T) (*core.Blockchain, *wallet.Account) {
|
func NewSingle(t *testing.T) (*core.Blockchain, neotest.Signer) {
|
||||||
protoCfg := config.ProtocolConfiguration{
|
protoCfg := config.ProtocolConfiguration{
|
||||||
Magic: netmode.UnitTestNet,
|
Magic: netmode.UnitTestNet,
|
||||||
SecondsPerBlock: 1,
|
SecondsPerBlock: 1,
|
||||||
|
@ -46,5 +47,5 @@ func NewSingle(t *testing.T) (*core.Blockchain, *wallet.Account) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
go bc.Run()
|
go bc.Run()
|
||||||
t.Cleanup(bc.Close)
|
t.Cleanup(bc.Close)
|
||||||
return bc, committeeAcc
|
return bc, neotest.NewMultiSigner(committeeAcc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
type ContractInvoker struct {
|
type ContractInvoker struct {
|
||||||
*Executor
|
*Executor
|
||||||
Hash util.Uint160
|
Hash util.Uint160
|
||||||
Signer interface{}
|
Signers []Signer
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitteeInvoker creates new ContractInvoker for contract with hash h.
|
// CommitteeInvoker creates new ContractInvoker for contract with hash h.
|
||||||
|
@ -23,7 +23,7 @@ func (e *Executor) CommitteeInvoker(h util.Uint160) *ContractInvoker {
|
||||||
return &ContractInvoker{
|
return &ContractInvoker{
|
||||||
Executor: e,
|
Executor: e,
|
||||||
Hash: h,
|
Hash: h,
|
||||||
Signer: e.Committee,
|
Signers: []Signer{e.Committee},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,16 +39,16 @@ func (c *ContractInvoker) TestInvoke(t *testing.T, method string, args ...interf
|
||||||
return v.Estack(), err
|
return v.Estack(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSigner creates new client with the provided signer.
|
// WithSigners creates new client with the provided signer.
|
||||||
func (c *ContractInvoker) WithSigner(signer interface{}) *ContractInvoker {
|
func (c *ContractInvoker) WithSigners(signers ...Signer) *ContractInvoker {
|
||||||
newC := *c
|
newC := *c
|
||||||
newC.Signer = signer
|
newC.Signers = signers
|
||||||
return &newC
|
return &newC
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareInvoke creates new invocation transaction.
|
// PrepareInvoke creates new invocation transaction.
|
||||||
func (c *ContractInvoker) PrepareInvoke(t *testing.T, method string, args ...interface{}) *transaction.Transaction {
|
func (c *ContractInvoker) PrepareInvoke(t *testing.T, method string, args ...interface{}) *transaction.Transaction {
|
||||||
return c.Executor.NewTx(t, c.Signer, c.Hash, method, args...)
|
return c.Executor.NewTx(t, c.Signers, c.Hash, method, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareInvokeNoSign creates new unsigned invocation transaction.
|
// PrepareInvokeNoSign creates new unsigned invocation transaction.
|
||||||
|
@ -68,7 +68,7 @@ func (c *ContractInvoker) Invoke(t *testing.T, result interface{}, method string
|
||||||
// InvokeWithFeeFail is like InvokeFail but sets custom system fee for the transaction.
|
// InvokeWithFeeFail is like InvokeFail but sets custom system fee for the transaction.
|
||||||
func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee int64, method string, args ...interface{}) util.Uint256 {
|
func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee int64, method string, args ...interface{}) util.Uint256 {
|
||||||
tx := c.PrepareInvokeNoSign(t, method, args...)
|
tx := c.PrepareInvokeNoSign(t, method, args...)
|
||||||
c.Executor.SignTx(t, tx, sysFee, c.Signer)
|
c.Executor.SignTx(t, tx, sysFee, c.Signers...)
|
||||||
c.AddNewBlock(t, tx)
|
c.AddNewBlock(t, tx)
|
||||||
c.CheckFault(t, tx.Hash(), message)
|
c.CheckFault(t, tx.Hash(), message)
|
||||||
return tx.Hash()
|
return tx.Hash()
|
||||||
|
|
124
pkg/neotest/signer.go
Normal file
124
pkg/neotest/signer.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package neotest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
|
"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/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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Signer is a generic interface which can be either simple- or multi-signature signer.
|
||||||
|
type Signer interface {
|
||||||
|
// ScriptHash returns signer script hash.
|
||||||
|
Script() []byte
|
||||||
|
// Script returns signer verification script.
|
||||||
|
ScriptHash() util.Uint160
|
||||||
|
// SignHashable returns invocation script for signing an item.
|
||||||
|
SignHashable(uint32, hash.Hashable) []byte
|
||||||
|
// SignTx signs a transaction.
|
||||||
|
SignTx(netmode.Magic, *transaction.Transaction) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// signer represents simple-signature signer.
|
||||||
|
type signer wallet.Account
|
||||||
|
|
||||||
|
// multiSigner represents single multi-signature signer consisting of provided accounts.
|
||||||
|
type multiSigner []*wallet.Account
|
||||||
|
|
||||||
|
// NewSingleSigner returns multi-signature signer for the provided account.
|
||||||
|
// It must contain exactly as many accounts as needed to sign the script.
|
||||||
|
func NewSingleSigner(acc *wallet.Account) Signer {
|
||||||
|
if !vm.IsSignatureContract(acc.Contract.Script) {
|
||||||
|
panic("account must have simple-signature verification script")
|
||||||
|
}
|
||||||
|
return (*signer)(acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script implements Signer interface.
|
||||||
|
func (s *signer) Script() []byte {
|
||||||
|
return (*wallet.Account)(s).Contract.Script
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptHash implements Signer interface.
|
||||||
|
func (s *signer) ScriptHash() util.Uint160 {
|
||||||
|
return (*wallet.Account)(s).Contract.ScriptHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignHashable implements Signer interface.
|
||||||
|
func (s *signer) SignHashable(magic uint32, item hash.Hashable) []byte {
|
||||||
|
return append([]byte{byte(opcode.PUSHDATA1), 64},
|
||||||
|
(*wallet.Account)(s).PrivateKey().SignHashable(magic, item)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTx implements Signer interface.
|
||||||
|
func (s *signer) SignTx(magic netmode.Magic, tx *transaction.Transaction) error {
|
||||||
|
return (*wallet.Account)(s).SignTx(magic, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiSigner returns multi-signature signer for the provided account.
|
||||||
|
// It must contain at least as many accounts as needed to sign the script.
|
||||||
|
func NewMultiSigner(accs ...*wallet.Account) Signer {
|
||||||
|
if len(accs) == 0 {
|
||||||
|
panic("empty account list")
|
||||||
|
}
|
||||||
|
script := accs[0].Contract.Script
|
||||||
|
m, _, ok := vm.ParseMultiSigContract(script)
|
||||||
|
if !ok {
|
||||||
|
panic("all accounts must have multi-signature verification script")
|
||||||
|
}
|
||||||
|
if len(accs) < m {
|
||||||
|
panic(fmt.Sprintf("verification script requires %d signatures, "+
|
||||||
|
"but only %d accounts were provided", m, len(accs)))
|
||||||
|
}
|
||||||
|
for _, acc := range accs {
|
||||||
|
if !bytes.Equal(script, acc.Contract.Script) {
|
||||||
|
panic("all accounts must have equal verification script")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return multiSigner(accs[:m])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptHash implements Signer interface.
|
||||||
|
func (m multiSigner) ScriptHash() util.Uint160 {
|
||||||
|
return m[0].Contract.ScriptHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script implements Signer interface.
|
||||||
|
func (m multiSigner) Script() []byte {
|
||||||
|
return m[0].Contract.Script
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignHashable implements Signer interface.
|
||||||
|
func (m multiSigner) SignHashable(magic uint32, item hash.Hashable) []byte {
|
||||||
|
var script []byte
|
||||||
|
for _, acc := range m {
|
||||||
|
sign := acc.PrivateKey().SignHashable(magic, item)
|
||||||
|
script = append(script, byte(opcode.PUSHDATA1), 64)
|
||||||
|
script = append(script, sign...)
|
||||||
|
}
|
||||||
|
return script
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTx implements Signer interface.
|
||||||
|
func (m multiSigner) SignTx(magic netmode.Magic, tx *transaction.Transaction) error {
|
||||||
|
invoc := m.SignHashable(uint32(magic), tx)
|
||||||
|
verif := m.Script()
|
||||||
|
for i := range tx.Scripts {
|
||||||
|
if bytes.Equal(tx.Scripts[i].VerificationScript, verif) {
|
||||||
|
tx.Scripts[i].InvocationScript = invoc
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.Scripts = append(tx.Scripts, transaction.Witness{
|
||||||
|
InvocationScript: invoc,
|
||||||
|
VerificationScript: verif,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue