forked from TrueCloudLab/neoneo-go
303 lines
11 KiB
Go
303 lines
11 KiB
Go
package native_test
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
|
"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/vm/stackitem"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newLedgerClient(t *testing.T) *neotest.ContractInvoker {
|
|
bc, acc := chain.NewSingleWithCustomConfig(t, func(cfg *config.ProtocolConfiguration) {
|
|
cfg.MaxTraceableBlocks = 10 // reduce number of traceable blocks for Ledger tests
|
|
})
|
|
e := neotest.NewExecutor(t, bc, acc, acc)
|
|
|
|
return e.CommitteeInvoker(e.NativeHash(t, nativenames.Ledger))
|
|
}
|
|
|
|
func TestLedger_GetTransactionHeight(t *testing.T) {
|
|
c := newLedgerClient(t)
|
|
e := c.Executor
|
|
ledgerInvoker := c.WithSigners(c.Committee)
|
|
|
|
height := 13
|
|
e.GenerateNewBlocks(t, height-1)
|
|
hash := e.InvokeScript(t, []byte{byte(opcode.RET)}, []neotest.Signer{c.Committee})
|
|
|
|
t.Run("good", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, height, "getTransactionHeight", hash)
|
|
})
|
|
t.Run("unknown transaction", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, -1, "getTransactionHeight", util.Uint256{1, 2, 3})
|
|
})
|
|
t.Run("not a hash", func(t *testing.T) {
|
|
ledgerInvoker.InvokeFail(t, "expected []byte of size 32", "getTransactionHeight", []byte{1, 2, 3})
|
|
})
|
|
}
|
|
|
|
func TestLedger_GetTransactionState(t *testing.T) {
|
|
c := newLedgerClient(t)
|
|
e := c.Executor
|
|
ledgerInvoker := c.WithSigners(c.Committee)
|
|
|
|
hash := e.InvokeScript(t, []byte{byte(opcode.RET)}, []neotest.Signer{c.Committee})
|
|
|
|
t.Run("unknown transaction", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, vm.NoneState, "getTransactionVMState", util.Uint256{1, 2, 3})
|
|
})
|
|
t.Run("not a hash", func(t *testing.T) {
|
|
ledgerInvoker.InvokeFail(t, "expected []byte of size 32", "getTransactionVMState", []byte{1, 2, 3})
|
|
})
|
|
t.Run("good: HALT", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, vm.HaltState, "getTransactionVMState", hash)
|
|
})
|
|
t.Run("isn't traceable", func(t *testing.T) {
|
|
// Add more blocks so that tx becomes untraceable.
|
|
e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
|
|
ledgerInvoker.Invoke(t, vm.NoneState, "getTransactionVMState", hash)
|
|
})
|
|
t.Run("good: FAULT", func(t *testing.T) {
|
|
faultedH := e.InvokeScript(t, []byte{byte(opcode.ABORT)}, []neotest.Signer{c.Committee})
|
|
ledgerInvoker.Invoke(t, vm.FaultState, "getTransactionVMState", faultedH)
|
|
})
|
|
}
|
|
|
|
func TestLedger_GetTransaction(t *testing.T) {
|
|
c := newLedgerClient(t)
|
|
e := c.Executor
|
|
ledgerInvoker := c.WithSigners(c.Committee)
|
|
|
|
hash := e.InvokeScript(t, []byte{byte(opcode.RET)}, []neotest.Signer{c.Committee})
|
|
tx, _ := e.GetTransaction(t, hash)
|
|
|
|
t.Run("success", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, []stackitem.Item{
|
|
stackitem.NewByteArray(tx.Hash().BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(tx.Version))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(tx.Nonce))),
|
|
stackitem.NewByteArray(tx.Sender().BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(tx.SystemFee)),
|
|
stackitem.NewBigInteger(big.NewInt(tx.NetworkFee)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(tx.ValidUntilBlock))),
|
|
stackitem.NewByteArray(tx.Script),
|
|
}, "getTransaction", tx.Hash())
|
|
})
|
|
t.Run("isn't traceable", func(t *testing.T) {
|
|
// Add more blocks so that tx becomes untraceable.
|
|
e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
|
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransaction", tx.Hash())
|
|
})
|
|
t.Run("bad hash", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransaction", util.Uint256{})
|
|
})
|
|
}
|
|
|
|
func TestLedger_GetTransactionFromBlock(t *testing.T) {
|
|
c := newLedgerClient(t)
|
|
e := c.Executor
|
|
ledgerInvoker := c.WithSigners(c.Committee)
|
|
|
|
ledgerInvoker.Invoke(t, e.Chain.BlockHeight(), "currentIndex") // Adds a block.
|
|
b := e.GetBlockByIndex(t, int(e.Chain.BlockHeight()))
|
|
|
|
check := func(t testing.TB, stack []stackitem.Item) {
|
|
require.Equal(t, 1, len(stack))
|
|
actual, ok := stack[0].Value().([]stackitem.Item)
|
|
require.True(t, ok)
|
|
require.Equal(t, b.Transactions[0].Hash().BytesBE(), actual[0].Value().([]byte))
|
|
}
|
|
t.Run("good, by hash", func(t *testing.T) {
|
|
ledgerInvoker.InvokeAndCheck(t, check, "getTransactionFromBlock", b.Hash(), int64(0))
|
|
})
|
|
t.Run("good, by index", func(t *testing.T) {
|
|
ledgerInvoker.InvokeAndCheck(t, check, "getTransactionFromBlock", int64(b.Index), int64(0))
|
|
})
|
|
t.Run("bad transaction index", func(t *testing.T) {
|
|
ledgerInvoker.InvokeFail(t, "", "getTransactionFromBlock", b.Hash(), int64(1))
|
|
})
|
|
t.Run("bad block hash (>int64)", func(t *testing.T) {
|
|
ledgerInvoker.InvokeFail(t, "", "getTransactionFromBlock", b.Hash().BytesBE()[:10], int64(0))
|
|
})
|
|
t.Run("invalid block hash (int64)", func(t *testing.T) {
|
|
ledgerInvoker.InvokeFail(t, "", "getTransactionFromBlock", b.Hash().BytesBE()[:6], int64(0))
|
|
})
|
|
t.Run("unknown block hash", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransactionFromBlock", b.Hash().BytesLE(), int64(0))
|
|
})
|
|
t.Run("isn't traceable", func(t *testing.T) {
|
|
e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
|
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransactionFromBlock", b.Hash(), int64(0))
|
|
})
|
|
}
|
|
|
|
func TestLedger_GetBlock(t *testing.T) {
|
|
c := newLedgerClient(t)
|
|
e := c.Executor
|
|
ledgerInvoker := c.WithSigners(c.Committee)
|
|
|
|
ledgerInvoker.Invoke(t, e.Chain.GetHeaderHash(int(e.Chain.BlockHeight())).BytesBE(), "currentHash") // Adds a block.
|
|
b := e.GetBlockByIndex(t, int(e.Chain.BlockHeight()))
|
|
|
|
expected := []stackitem.Item{
|
|
stackitem.NewByteArray(b.Hash().BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(b.Version))),
|
|
stackitem.NewByteArray(b.PrevHash.BytesBE()),
|
|
stackitem.NewByteArray(b.MerkleRoot.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(b.Timestamp))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(b.Nonce))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(b.Index))),
|
|
stackitem.NewByteArray(b.NextConsensus.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(len(b.Transactions)))),
|
|
}
|
|
t.Run("good, by hash", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, expected, "getBlock", b.Hash())
|
|
})
|
|
t.Run("good, by index", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, expected, "getBlock", int64(b.Index))
|
|
})
|
|
t.Run("bad hash", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getBlock", b.Hash().BytesLE())
|
|
})
|
|
t.Run("isn't traceable", func(t *testing.T) {
|
|
e.GenerateNewBlocks(t, int(e.Chain.GetConfig().MaxTraceableBlocks))
|
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getBlock", b.Hash())
|
|
})
|
|
}
|
|
|
|
func TestLedger_GetTransactionSigners(t *testing.T) {
|
|
c := newLedgerClient(t)
|
|
e := c.Executor
|
|
ledgerInvoker := c.WithSigners(c.Committee)
|
|
|
|
txHash := ledgerInvoker.Invoke(t, e.Chain.BlockHeight(), "currentIndex")
|
|
|
|
t.Run("good", func(t *testing.T) {
|
|
s := &transaction.Signer{
|
|
Account: c.CommitteeHash,
|
|
Scopes: transaction.Global,
|
|
}
|
|
bw := io.NewBufBinWriter()
|
|
s.EncodeBinary(bw.BinWriter)
|
|
require.NoError(t, bw.Err)
|
|
expected := stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray(bw.Bytes()),
|
|
stackitem.NewByteArray(s.Account.BytesBE()),
|
|
stackitem.NewBigInteger(big.NewInt(int64(s.Scopes))),
|
|
stackitem.NewArray([]stackitem.Item{}),
|
|
stackitem.NewArray([]stackitem.Item{}),
|
|
stackitem.NewArray([]stackitem.Item{}),
|
|
}),
|
|
})
|
|
ledgerInvoker.Invoke(t, expected, "getTransactionSigners", txHash)
|
|
})
|
|
t.Run("unknown transaction", func(t *testing.T) {
|
|
ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransactionSigners", util.Uint256{1, 2, 3})
|
|
})
|
|
t.Run("not a hash", func(t *testing.T) {
|
|
ledgerInvoker.InvokeFail(t, "expected []byte of size 32", "getTransactionSigners", []byte{1, 2, 3})
|
|
})
|
|
}
|
|
|
|
func TestLedger_GetTransactionSignersInteropAPI(t *testing.T) {
|
|
c := newLedgerClient(t)
|
|
e := c.Executor
|
|
ledgerInvoker := c.WithSigners(c.Committee)
|
|
|
|
// Firstly, add transaction with CalledByEntry rule-based signer scope to the chain.
|
|
tx := e.NewUnsignedTx(t, ledgerInvoker.Hash, "currentIndex")
|
|
tx.Signers = []transaction.Signer{{
|
|
Account: c.Committee.ScriptHash(),
|
|
Scopes: transaction.Rules,
|
|
Rules: []transaction.WitnessRule{
|
|
{
|
|
Action: transaction.WitnessAllow,
|
|
Condition: transaction.ConditionCalledByEntry{},
|
|
},
|
|
},
|
|
}}
|
|
neotest.AddNetworkFee(e.Chain, tx, c.Committee)
|
|
neotest.AddSystemFee(e.Chain, tx, -1)
|
|
require.NoError(t, c.Committee.SignTx(e.Chain.GetConfig().Magic, tx))
|
|
c.AddNewBlock(t, tx)
|
|
c.CheckHalt(t, tx.Hash(), stackitem.Make(e.Chain.BlockHeight()-1))
|
|
|
|
var (
|
|
hashStr string
|
|
accStr string
|
|
txHash = tx.Hash().BytesBE()
|
|
acc = c.Committee.ScriptHash().BytesBE()
|
|
)
|
|
for i := 0; i < util.Uint256Size; i++ {
|
|
hashStr += fmt.Sprintf("%#x", txHash[i])
|
|
if i != util.Uint256Size-1 {
|
|
hashStr += ", "
|
|
}
|
|
}
|
|
for i := 0; i < util.Uint160Size; i++ {
|
|
accStr += fmt.Sprintf("%#x", acc[i])
|
|
if i != util.Uint160Size-1 {
|
|
accStr += ", "
|
|
}
|
|
}
|
|
|
|
// After that ensure interop API allows to retrieve signer with CalledByEntry rule-based scope.
|
|
src := `package callledger
|
|
import (
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
|
)
|
|
func CallLedger(accessValue bool) int {
|
|
signers := ledger.GetTransactionSigners(interop.Hash256{` + hashStr + `})
|
|
if len(signers) != 1 {
|
|
panic("bad length")
|
|
}
|
|
s0 := signers[0]
|
|
expectedAcc := interop.Hash160{` + accStr + `}
|
|
if !util.Equals(string(s0.Account), string(expectedAcc)) {
|
|
panic("bad account")
|
|
}
|
|
if s0.Scopes != ledger.Rules {
|
|
panic("bad signer scope")
|
|
}
|
|
if len(s0.Rules) != 1 {
|
|
panic("bad rules length")
|
|
}
|
|
r0 := s0.Rules[0]
|
|
if r0.Action != ledger.WitnessAllow {
|
|
panic("bad action")
|
|
}
|
|
c0 := r0.Condition
|
|
if c0.Type != ledger.WitnessCalledByEntry {
|
|
panic("bad condition type")
|
|
}
|
|
if accessValue {
|
|
// Panic should occur here, because there's only Type inside the CalledByEntry condition.
|
|
_ = c0.Value
|
|
}
|
|
return 1
|
|
}`
|
|
ctr := neotest.CompileSource(t, c.Committee.ScriptHash(), strings.NewReader(src), &compiler.Options{
|
|
Name: "calledger_contract",
|
|
})
|
|
e.DeployContract(t, ctr, nil)
|
|
|
|
ctrInvoker := e.NewInvoker(ctr.Hash, e.Committee)
|
|
ctrInvoker.Invoke(t, 1, "callLedger", false) // Firstly, don't access CalledByEnrty Condition value => the call should be successful.
|
|
ctrInvoker.InvokeFail(t, `(PICKITEM): unhandled exception: "The value 1 is out of range."`, "callLedger", true) // Then, access the value to ensure it will panic.
|
|
}
|