neoneo-go/pkg/core/blockchain_test.go

343 lines
10 KiB
Go
Raw Normal View History

package core
import (
"testing"
"time"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
"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/wallet"
"github.com/stretchr/testify/assert"
Implemented rpc server method GetRawTransaction (#135) * Added utility function GetVarSize * 1) Added Size method: this implied that Fixed8 implements now the serializable interface. 2) Added few arithmetic operation (Add, Sub, div): this will be used to calculated networkfeeand feePerByte. Changed return value of the Value() method to int instead of int64. Modified fixed8_test accordingly. * Implemented Size or MarshalJSON method. - Structs accepting the Size method implement the serializable interface. - Structs accepting the MarshalJSON method implements the customized json marshaller interface. * Added fee calculation * Implemented rcp server method GetRawTransaction * Updated Tests * Fixed: 1) NewFixed8 will accept as input int64 2) race condition affecting configDeafault, blockchainDefault * Simplified Size calculation * 1) Removed global variable blockchainDefault, configDefault 2) Extended Blockchainer interface to include the methods: References, FeePerByte, SystemFee, NetworkFee 3) Deleted fee_test.go, fee.go. Moved corresponding methods to blockchain_test.go and blockchain.go respectively 4) Amended tx_raw_output.go * Simplified GetVarSize Method * Replaced ValueAtAndType with ValueWithType * Cosmetic changes + Added test case getrawtransaction_7 * Clean up Print statement * Filled up keys * Aligned verbose logic with the C#-neo implementation * Implemented @Kim requests Refactor server_test.go * Small fixes * Fixed verbose logic Added more tests Cosmetic changes * Replaced assert.NoError with require.NoError * Fixed tests by adding context.Background() as argument * Fixed tests
2019-02-20 17:39:32 +00:00
"github.com/stretchr/testify/require"
)
func TestAddHeaders(t *testing.T) {
bc := newTestChain(t)
defer bc.Close()
lastBlock := bc.topBlock.Load().(*block.Block)
h1 := newBlock(bc.config, 1, lastBlock.Hash()).Header()
h2 := newBlock(bc.config, 2, h1.Hash()).Header()
h3 := newBlock(bc.config, 3, h2.Hash()).Header()
require.NoError(t, bc.AddHeaders())
require.NoError(t, bc.AddHeaders(h1, h2))
require.NoError(t, bc.AddHeaders(h2, h3))
assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(0), bc.BlockHeight())
assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash())
// Add them again, they should not be added.
require.NoError(t, bc.AddHeaders(h3, h2, h1))
assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(0), bc.BlockHeight())
assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash())
h4 := newBlock(bc.config, 4, h3.Hash().Reverse()).Header()
h5 := newBlock(bc.config, 5, h4.Hash()).Header()
assert.Error(t, bc.AddHeaders(h4, h5))
assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(0), bc.BlockHeight())
assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash())
h6 := newBlock(bc.config, 4, h3.Hash()).Header()
h6.Script.InvocationScript = nil
assert.Error(t, bc.AddHeaders(h6))
assert.Equal(t, h3.Index, bc.HeaderHeight())
assert.Equal(t, uint32(0), bc.BlockHeight())
assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash())
}
func TestAddBlock(t *testing.T) {
const size = 3
bc := newTestChain(t)
blocks, err := bc.genBlocks(size)
require.NoError(t, err)
lastBlock := blocks[len(blocks)-1]
assert.Equal(t, lastBlock.Index, bc.HeaderHeight())
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
// This one tests persisting blocks, so it does need to persist()
require.NoError(t, bc.persist())
for _, block := range blocks {
2019-11-27 09:23:18 +00:00
key := storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE())
2020-04-07 09:41:12 +00:00
_, err := bc.dao.Store.Get(key)
require.NoErrorf(t, err, "block %s not persisted", block.Hash())
}
assert.Equal(t, lastBlock.Index, bc.BlockHeight())
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
}
func TestScriptFromWitness(t *testing.T) {
witness := &transaction.Witness{}
h := util.Uint160{1, 2, 3}
res, err := ScriptFromWitness(h, witness)
require.NoError(t, err)
require.NotNil(t, res)
witness.VerificationScript = []byte{4, 8, 15, 16, 23, 42}
h = hash.Hash160(witness.VerificationScript)
res, err = ScriptFromWitness(h, witness)
require.NoError(t, err)
require.NotNil(t, res)
h[0] = ^h[0]
res, err = ScriptFromWitness(h, witness)
require.Error(t, err)
require.Nil(t, res)
}
func TestGetHeader(t *testing.T) {
bc := newTestChain(t)
block := bc.newBlock(newMinerTX())
err := bc.AddBlock(block)
assert.Nil(t, err)
// Test unpersisted and persisted access
for i := 0; i < 2; i++ {
hash := block.Hash()
header, err := bc.GetHeader(hash)
require.NoError(t, err)
assert.Equal(t, block.Header(), header)
b2 := bc.newBlock()
_, err = bc.GetHeader(b2.Hash())
assert.Error(t, err)
assert.NoError(t, bc.persist())
}
}
func TestGetBlock(t *testing.T) {
bc := newTestChain(t)
blocks, err := bc.genBlocks(100)
require.NoError(t, err)
// Test unpersisted and persisted access
for j := 0; j < 2; j++ {
for i := 0; i < len(blocks); i++ {
block, err := bc.GetBlock(blocks[i].Hash())
require.NoErrorf(t, err, "can't get block %d: %s, attempt %d", i, err, j)
assert.Equal(t, blocks[i].Index, block.Index)
assert.Equal(t, blocks[i].Hash(), block.Hash())
}
assert.NoError(t, bc.persist())
}
}
func TestHasBlock(t *testing.T) {
bc := newTestChain(t)
blocks, err := bc.genBlocks(50)
require.NoError(t, err)
// Test unpersisted and persisted access
for j := 0; j < 2; j++ {
for i := 0; i < len(blocks); i++ {
assert.True(t, bc.HasBlock(blocks[i].Hash()))
}
newBlock := bc.newBlock()
assert.False(t, bc.HasBlock(newBlock.Hash()))
assert.NoError(t, bc.persist())
}
}
func TestGetTransaction(t *testing.T) {
b1 := getDecodedBlock(t, 1)
block := getDecodedBlock(t, 2)
bc := newTestChain(t)
// Turn verification off, because these blocks are really from some other chain
// and can't be verified, but we don't care about that in this test.
bc.config.VerifyBlocks = false
assert.Nil(t, bc.AddBlock(b1))
assert.Nil(t, bc.AddBlock(block))
// Test unpersisted and persisted access
for j := 0; j < 2; j++ {
tx, height, err := bc.GetTransaction(block.Transactions[0].Hash())
require.Nil(t, err)
assert.Equal(t, block.Index, height)
assert.Equal(t, block.Transactions[0], tx)
assert.Equal(t, 10, io.GetVarSize(tx))
assert.Equal(t, 1, io.GetVarSize(tx.Attributes))
assert.Equal(t, 1, io.GetVarSize(tx.Inputs))
assert.Equal(t, 1, io.GetVarSize(tx.Outputs))
assert.Equal(t, 1, io.GetVarSize(tx.Scripts))
assert.NoError(t, bc.persist())
}
}
func TestGetClaimable(t *testing.T) {
bc := newTestChain(t)
bc.generationAmount = []int{4, 3, 2, 1}
bc.decrementInterval = 2
_, err := bc.genBlocks(10)
require.NoError(t, err)
t.Run("first generation period", func(t *testing.T) {
amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 0, 2)
require.NoError(t, err)
require.EqualValues(t, 8, amount)
require.EqualValues(t, 0, sysfee)
})
t.Run("a number of full periods", func(t *testing.T) {
amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 0, 6)
require.NoError(t, err)
require.EqualValues(t, 4+4+3+3+2+2, amount)
require.EqualValues(t, 0, sysfee)
})
t.Run("start from the 2-nd block", func(t *testing.T) {
amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 1, 7)
require.NoError(t, err)
require.EqualValues(t, 4+3+3+2+2+1, amount)
require.EqualValues(t, 0, sysfee)
})
t.Run("end height after generation has ended", func(t *testing.T) {
amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 1, 10)
require.NoError(t, err)
require.EqualValues(t, 4+3+3+2+2+1+1, amount)
require.EqualValues(t, 0, sysfee)
})
}
func TestClose(t *testing.T) {
defer func() {
r := recover()
assert.NotNil(t, r)
}()
bc := newTestChain(t)
_, err := bc.genBlocks(10)
require.NoError(t, err)
bc.Close()
// It's a hack, but we use internal knowledge of MemoryStore
// implementation which makes it completely unusable (up to panicing)
// after Close().
2020-04-07 09:41:12 +00:00
_ = bc.dao.Store.Put([]byte{0}, []byte{1})
// This should never be executed.
assert.Nil(t, t)
}
func TestSubscriptions(t *testing.T) {
// We use buffering here as a substitute for reader goroutines, events
// get queued up and we read them one by one here.
const chBufSize = 16
blockCh := make(chan *block.Block, chBufSize)
txCh := make(chan *transaction.Transaction, chBufSize)
notificationCh := make(chan *state.NotificationEvent, chBufSize)
executionCh := make(chan *state.AppExecResult, chBufSize)
bc := newTestChain(t)
bc.SubscribeForBlocks(blockCh)
bc.SubscribeForTransactions(txCh)
bc.SubscribeForNotifications(notificationCh)
bc.SubscribeForExecutions(executionCh)
assert.Empty(t, notificationCh)
assert.Empty(t, executionCh)
assert.Empty(t, blockCh)
assert.Empty(t, txCh)
blocks, err := bc.genBlocks(1)
require.NoError(t, err)
assert.Eventually(t, func() bool { return len(blockCh) != 0 && len(txCh) != 0 }, time.Second, 10*time.Millisecond)
assert.Empty(t, notificationCh)
assert.Empty(t, executionCh)
b := <-blockCh
tx := <-txCh
assert.Equal(t, blocks[0], b)
assert.Equal(t, blocks[0].Transactions[0], tx)
assert.Empty(t, blockCh)
assert.Empty(t, txCh)
acc0, err := wallet.NewAccountFromWIF(privNetKeys[0])
require.NoError(t, err)
addr0, err := address.StringToUint160(acc0.Address)
require.NoError(t, err)
script := io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte("yay!"))
emit.Syscall(script.BinWriter, "Neo.Runtime.Notify")
require.NoError(t, script.Err)
txGood1 := transaction.NewInvocationTX(script.Bytes(), 0)
txGood1.AddVerificationHash(addr0)
require.NoError(t, acc0.SignTx(txGood1))
// Reset() reuses the script buffer and we need to keep scripts.
script = io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte("nay!"))
emit.Syscall(script.BinWriter, "Neo.Runtime.Notify")
emit.Opcode(script.BinWriter, opcode.THROW)
require.NoError(t, script.Err)
txBad := transaction.NewInvocationTX(script.Bytes(), 0)
txBad.AddVerificationHash(addr0)
require.NoError(t, acc0.SignTx(txBad))
script = io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte("yay! yay! yay!"))
emit.Syscall(script.BinWriter, "Neo.Runtime.Notify")
require.NoError(t, script.Err)
txGood2 := transaction.NewInvocationTX(script.Bytes(), 0)
txGood2.AddVerificationHash(addr0)
require.NoError(t, acc0.SignTx(txGood2))
txMiner := newMinerTX()
invBlock := newBlock(bc.config, bc.BlockHeight()+1, bc.CurrentHeaderHash(), txMiner, txGood1, txBad, txGood2)
require.NoError(t, bc.AddBlock(invBlock))
require.Eventually(t, func() bool {
return len(blockCh) != 0 && len(txCh) != 0 &&
len(notificationCh) != 0 && len(executionCh) != 0
}, time.Second, 10*time.Millisecond)
b = <-blockCh
require.Equal(t, invBlock, b)
assert.Empty(t, blockCh)
// Follow in-block transaction order.
for _, txExpected := range invBlock.Transactions {
tx = <-txCh
require.Equal(t, txExpected, tx)
if txExpected.Type == transaction.InvocationType {
exec := <-executionCh
require.Equal(t, tx.Hash(), exec.TxHash)
if exec.VMState == "HALT" {
notif := <-notificationCh
inv := tx.Data.(*transaction.InvocationTX)
require.Equal(t, hash.Hash160(inv.Script), notif.ScriptHash)
}
}
}
assert.Empty(t, txCh)
assert.Empty(t, notificationCh)
assert.Empty(t, executionCh)
bc.UnsubscribeFromBlocks(blockCh)
bc.UnsubscribeFromTransactions(txCh)
bc.UnsubscribeFromNotifications(notificationCh)
bc.UnsubscribeFromExecutions(executionCh)
// Ensure that new blocks are processed correctly after unsubscription.
_, err = bc.genBlocks(2 * chBufSize)
require.NoError(t, err)
}