mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-24 09:25:21 +00:00
Merge pull request #418 from nspcc-dev/various-verification-fixes2
Transaction verification fixes, interops and block verification. Fixes #12.
This commit is contained in:
commit
4822c736bb
46 changed files with 3022 additions and 538 deletions
|
@ -49,6 +49,10 @@ type (
|
|||
StandbyValidators []string `yaml:"StandbyValidators"`
|
||||
SeedList []string `yaml:"SeedList"`
|
||||
SystemFee SystemFee `yaml:"SystemFee"`
|
||||
// Whether to verify received blocks.
|
||||
VerifyBlocks bool `yaml:"VerifyBlocks"`
|
||||
// Whether to verify transactions in received blocks.
|
||||
VerifyTransactions bool `yaml:"VerifyTransactions"`
|
||||
}
|
||||
|
||||
// SystemFee fees related to system.
|
||||
|
|
|
@ -27,6 +27,8 @@ ProtocolConfiguration:
|
|||
IssueTransaction: 500
|
||||
PublishTransaction: 500
|
||||
RegisterTransaction: 10000
|
||||
VerifyBlocks: true
|
||||
VerifyTransactions: false
|
||||
|
||||
ApplicationConfiguration:
|
||||
DBConfiguration:
|
||||
|
|
|
@ -15,6 +15,8 @@ ProtocolConfiguration:
|
|||
IssueTransaction: 500
|
||||
PublishTransaction: 500
|
||||
RegisterTransaction: 10000
|
||||
VerifyBlocks: true
|
||||
VerifyTransactions: true
|
||||
|
||||
ApplicationConfiguration:
|
||||
DBConfiguration:
|
||||
|
|
|
@ -12,6 +12,8 @@ ProtocolConfiguration:
|
|||
IssueTransaction: 500
|
||||
PublishTransaction: 500
|
||||
RegisterTransaction: 10000
|
||||
VerifyBlocks: true
|
||||
VerifyTransactions: true
|
||||
|
||||
ApplicationConfiguration:
|
||||
DBConfiguration:
|
||||
|
|
|
@ -12,6 +12,8 @@ ProtocolConfiguration:
|
|||
IssueTransaction: 500
|
||||
PublishTransaction: 500
|
||||
RegisterTransaction: 10000
|
||||
VerifyBlocks: true
|
||||
VerifyTransactions: true
|
||||
|
||||
ApplicationConfiguration:
|
||||
DBConfiguration:
|
||||
|
|
|
@ -12,6 +12,8 @@ ProtocolConfiguration:
|
|||
IssueTransaction: 500
|
||||
PublishTransaction: 500
|
||||
RegisterTransaction: 10000
|
||||
VerifyBlocks: true
|
||||
VerifyTransactions: true
|
||||
|
||||
ApplicationConfiguration:
|
||||
DBConfiguration:
|
||||
|
|
|
@ -18,6 +18,8 @@ ProtocolConfiguration:
|
|||
IssueTransaction: 500
|
||||
PublishTransaction: 500
|
||||
RegisterTransaction: 10000
|
||||
VerifyBlocks: true
|
||||
VerifyTransactions: true
|
||||
|
||||
ApplicationConfiguration:
|
||||
DBConfiguration:
|
||||
|
|
|
@ -27,6 +27,8 @@ ProtocolConfiguration:
|
|||
IssueTransaction: 5
|
||||
PublishTransaction: 5
|
||||
RegisterTransaction: 100
|
||||
VerifyBlocks: true
|
||||
VerifyTransactions: false
|
||||
|
||||
ApplicationConfiguration:
|
||||
DBConfiguration:
|
||||
|
|
|
@ -17,6 +17,8 @@ ProtocolConfiguration:
|
|||
IssueTransaction: 500
|
||||
PublishTransaction: 500
|
||||
RegisterTransaction: 10000
|
||||
VerifyBlocks: true
|
||||
VerifyTransactions: true
|
||||
|
||||
ApplicationConfiguration:
|
||||
DBConfiguration:
|
||||
|
|
|
@ -27,6 +27,17 @@ func (a Assets) commit(b storage.Batch) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// putAssetStateIntoStore puts given asset state into the given store.
|
||||
func putAssetStateIntoStore(s storage.Store, as *AssetState) error {
|
||||
buf := io.NewBufBinWriter()
|
||||
as.EncodeBinary(buf.BinWriter)
|
||||
if buf.Err != nil {
|
||||
return buf.Err
|
||||
}
|
||||
key := storage.AppendPrefix(storage.STAsset, as.ID.Bytes())
|
||||
return s.Put(key, buf.Bytes())
|
||||
}
|
||||
|
||||
// AssetState represents the state of an NEO registered Asset.
|
||||
type AssetState struct {
|
||||
ID util.Uint256
|
||||
|
|
|
@ -3,6 +3,7 @@ package core
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
|
@ -35,3 +36,25 @@ func TestEncodeDecodeAssetState(t *testing.T) {
|
|||
assert.Nil(t, r.Err)
|
||||
assert.Equal(t, asset, assetDecode)
|
||||
}
|
||||
|
||||
func TestPutGetAssetState(t *testing.T) {
|
||||
s := storage.NewMemoryStore()
|
||||
asset := &AssetState{
|
||||
ID: randomUint256(),
|
||||
AssetType: transaction.Token,
|
||||
Name: "super cool token",
|
||||
Amount: util.Fixed8(1000000),
|
||||
Available: util.Fixed8(100),
|
||||
Precision: 8,
|
||||
FeeMode: feeMode,
|
||||
Owner: &keys.PublicKey{},
|
||||
Admin: randomUint160(),
|
||||
Issuer: randomUint160(),
|
||||
Expiration: 10,
|
||||
IsFrozen: false,
|
||||
}
|
||||
assert.NoError(t, putAssetStateIntoStore(s, asset))
|
||||
asRead := getAssetStateFromStore(s, asset.ID)
|
||||
assert.NotNil(t, asRead)
|
||||
assert.Equal(t, asset, asRead)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/Workiva/go-datastructures/queue"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Block represents one block in the chain.
|
||||
|
@ -28,14 +30,18 @@ func (b *Block) Header() *Header {
|
|||
}
|
||||
}
|
||||
|
||||
// rebuildMerkleRoot rebuild the merkleroot of the block.
|
||||
func (b *Block) rebuildMerkleRoot() error {
|
||||
hashes := make([]util.Uint256, len(b.Transactions))
|
||||
for i, tx := range b.Transactions {
|
||||
func merkleTreeFromTransactions(txes []*transaction.Transaction) (*crypto.MerkleTree, error) {
|
||||
hashes := make([]util.Uint256, len(txes))
|
||||
for i, tx := range txes {
|
||||
hashes[i] = tx.Hash()
|
||||
}
|
||||
|
||||
merkle, err := crypto.NewMerkleTree(hashes)
|
||||
return crypto.NewMerkleTree(hashes)
|
||||
}
|
||||
|
||||
// rebuildMerkleRoot rebuild the merkleroot of the block.
|
||||
func (b *Block) rebuildMerkleRoot() error {
|
||||
merkle, err := merkleTreeFromTransactions(b.Transactions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -45,26 +51,29 @@ func (b *Block) rebuildMerkleRoot() error {
|
|||
}
|
||||
|
||||
// Verify the integrity of the block.
|
||||
func (b *Block) Verify(full bool) bool {
|
||||
func (b *Block) Verify() error {
|
||||
// There has to be some transaction inside.
|
||||
if len(b.Transactions) == 0 {
|
||||
return false
|
||||
return errors.New("no transactions")
|
||||
}
|
||||
// The first TX has to be a miner transaction.
|
||||
if b.Transactions[0].Type != transaction.MinerType {
|
||||
return false
|
||||
return fmt.Errorf("the first transaction is %s", b.Transactions[0].Type)
|
||||
}
|
||||
// If the first TX is a minerTX then all others cant.
|
||||
for _, tx := range b.Transactions[1:] {
|
||||
if tx.Type == transaction.MinerType {
|
||||
return false
|
||||
return fmt.Errorf("miner transaction %s is not the first one", tx.Hash().ReverseString())
|
||||
}
|
||||
}
|
||||
// TODO: When full is true, do a full verification.
|
||||
if full {
|
||||
log.Warn("full verification of blocks is not yet implemented")
|
||||
merkle, err := merkleTreeFromTransactions(b.Transactions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return true
|
||||
if !b.MerkleRoot.Equals(merkle.Root()) {
|
||||
return errors.New("MerkleRoot mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewBlockFromTrimmedBytes returns a new block from trimmed data.
|
||||
|
|
|
@ -40,8 +40,11 @@ type BlockBase struct {
|
|||
// Script used to validate the block
|
||||
Script *transaction.Witness `json:"script"`
|
||||
|
||||
// hash of this block, created when binary encoded.
|
||||
// Hash of this block, created when binary encoded (double SHA256).
|
||||
hash util.Uint256
|
||||
|
||||
// Hash of the block used to verify it (single SHA256).
|
||||
verificationHash util.Uint256
|
||||
}
|
||||
|
||||
// Verify verifies the integrity of the BlockBase.
|
||||
|
@ -58,6 +61,16 @@ func (b *BlockBase) Hash() util.Uint256 {
|
|||
return b.hash
|
||||
}
|
||||
|
||||
// VerificationHash returns the hash of the block used to verify it.
|
||||
func (b *BlockBase) VerificationHash() util.Uint256 {
|
||||
if b.verificationHash.Equals(util.Uint256{}) {
|
||||
if b.createHash() != nil {
|
||||
panic("failed to compute hash!")
|
||||
}
|
||||
}
|
||||
return b.verificationHash
|
||||
}
|
||||
|
||||
// DecodeBinary implements Serializable interface.
|
||||
func (b *BlockBase) DecodeBinary(br *io.BinReader) {
|
||||
b.decodeHashableFields(br)
|
||||
|
@ -80,6 +93,16 @@ func (b *BlockBase) EncodeBinary(bw *io.BinWriter) {
|
|||
b.Script.EncodeBinary(bw)
|
||||
}
|
||||
|
||||
// getHashableData returns serialized hashable data of the block.
|
||||
func (b *BlockBase) getHashableData() ([]byte, error) {
|
||||
buf := io.NewBufBinWriter()
|
||||
b.encodeHashableFields(buf.BinWriter)
|
||||
if buf.Err != nil {
|
||||
return nil, buf.Err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// createHash creates the hash of the block.
|
||||
// When calculating the hash value of the block, instead of calculating the entire block,
|
||||
// only first seven fields in the block head will be calculated, which are
|
||||
|
@ -87,12 +110,12 @@ func (b *BlockBase) EncodeBinary(bw *io.BinWriter) {
|
|||
// Since MerkleRoot already contains the hash value of all transactions,
|
||||
// the modification of transaction will influence the hash value of the block.
|
||||
func (b *BlockBase) createHash() error {
|
||||
buf := io.NewBufBinWriter()
|
||||
b.encodeHashableFields(buf.BinWriter)
|
||||
if buf.Err != nil {
|
||||
return buf.Err
|
||||
bb, err := b.getHashableData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.hash = hash.DoubleSha256(buf.Bytes())
|
||||
b.hash = hash.DoubleSha256(bb)
|
||||
b.verificationHash = hash.Sha256(bb)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -76,30 +77,59 @@ func TestTrimmedBlock(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func newDumbBlock() *Block {
|
||||
return &Block{
|
||||
BlockBase: BlockBase{
|
||||
Version: 0,
|
||||
PrevHash: hash.Sha256([]byte("a")),
|
||||
MerkleRoot: hash.Sha256([]byte("b")),
|
||||
Timestamp: uint32(100500),
|
||||
Index: 1,
|
||||
ConsensusData: 1111,
|
||||
NextConsensus: hash.Hash160([]byte("a")),
|
||||
Script: &transaction.Witness{
|
||||
VerificationScript: []byte{0x51}, // PUSH1
|
||||
InvocationScript: []byte{0x61}, // NOP
|
||||
},
|
||||
},
|
||||
Transactions: []*transaction.Transaction{
|
||||
{Type: transaction.MinerType},
|
||||
{Type: transaction.IssueType},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashBlockEqualsHashHeader(t *testing.T) {
|
||||
block := newBlock(0)
|
||||
block := newDumbBlock()
|
||||
|
||||
assert.Equal(t, block.Hash(), block.Header().Hash())
|
||||
}
|
||||
|
||||
func TestBlockVerify(t *testing.T) {
|
||||
block := newBlock(
|
||||
0,
|
||||
newTX(transaction.MinerType),
|
||||
newTX(transaction.IssueType),
|
||||
)
|
||||
assert.True(t, block.Verify(false))
|
||||
block := newDumbBlock()
|
||||
assert.NotNil(t, block.Verify())
|
||||
assert.Nil(t, block.rebuildMerkleRoot())
|
||||
assert.Nil(t, block.Verify())
|
||||
|
||||
block.Transactions = []*transaction.Transaction{
|
||||
{Type: transaction.IssueType},
|
||||
{Type: transaction.MinerType},
|
||||
}
|
||||
assert.False(t, block.Verify(false))
|
||||
assert.NoError(t, block.rebuildMerkleRoot())
|
||||
assert.NotNil(t, block.Verify())
|
||||
|
||||
block.Transactions = []*transaction.Transaction{
|
||||
{Type: transaction.MinerType},
|
||||
{Type: transaction.MinerType},
|
||||
}
|
||||
assert.False(t, block.Verify(false))
|
||||
assert.NoError(t, block.rebuildMerkleRoot())
|
||||
assert.NotNil(t, block.Verify())
|
||||
block.Transactions = []*transaction.Transaction{
|
||||
{Type: transaction.MinerType},
|
||||
{Type: transaction.IssueType},
|
||||
{Type: transaction.IssueType},
|
||||
}
|
||||
assert.NotNil(t, block.Verify())
|
||||
}
|
||||
|
||||
func TestBinBlockDecodeEncode(t *testing.T) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -67,9 +68,6 @@ type Blockchain struct {
|
|||
headersOp chan headersOpFunc
|
||||
headersOpDone chan struct{}
|
||||
|
||||
// Whether we will verify received blocks.
|
||||
verifyBlocks bool
|
||||
|
||||
memPool MemPool
|
||||
}
|
||||
|
||||
|
@ -84,7 +82,6 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration) (*Blockcha
|
|||
memStore: storage.NewMemoryStore(),
|
||||
headersOp: make(chan headersOpFunc),
|
||||
headersOpDone: make(chan struct{}),
|
||||
verifyBlocks: false,
|
||||
memPool: NewMemPool(50000),
|
||||
}
|
||||
|
||||
|
@ -208,17 +205,23 @@ func (bc *Blockchain) AddBlock(block *Block) error {
|
|||
if expectedHeight != block.Index {
|
||||
return fmt.Errorf("expected block %d, but passed block %d", expectedHeight, block.Index)
|
||||
}
|
||||
if bc.verifyBlocks {
|
||||
if !block.Verify(false) {
|
||||
return fmt.Errorf("block %s is invalid", block.Hash())
|
||||
if bc.config.VerifyBlocks {
|
||||
err := block.Verify()
|
||||
if err == nil {
|
||||
err = bc.VerifyBlock(block)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("block %s is invalid: %s", block.Hash().ReverseString(), err)
|
||||
}
|
||||
if bc.config.VerifyTransactions {
|
||||
for _, tx := range block.Transactions {
|
||||
err := bc.Verify(tx)
|
||||
err := bc.VerifyTx(tx, block)
|
||||
if err != nil {
|
||||
return fmt.Errorf("transaction %s failed to verify: %s", tx.Hash().ReverseString(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
headerLen := bc.headerListLen()
|
||||
if int(block.Index) == headerLen {
|
||||
err := bc.AddHeaders(block.Header())
|
||||
|
@ -238,6 +241,7 @@ func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
|
|||
)
|
||||
|
||||
bc.headersOp <- func(headerList *HeaderHashList) {
|
||||
oldlen := headerList.Len()
|
||||
for _, h := range headers {
|
||||
if int(h.Index-1) >= headerList.Len() {
|
||||
err = fmt.Errorf(
|
||||
|
@ -258,7 +262,7 @@ func (bc *Blockchain) AddHeaders(headers ...*Header) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if batch.Len() > 0 {
|
||||
if oldlen != headerList.Len() {
|
||||
if err = bc.memStore.PutBatch(batch); err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -389,11 +393,15 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
|||
case *transaction.EnrollmentTX:
|
||||
case *transaction.StateTX:
|
||||
case *transaction.PublishTX:
|
||||
var properties smartcontract.PropertyState
|
||||
if t.NeedStorage {
|
||||
properties |= smartcontract.HasStorage
|
||||
}
|
||||
contract := &ContractState{
|
||||
Script: t.Script,
|
||||
ParamList: t.ParamList,
|
||||
ReturnType: t.ReturnType,
|
||||
HasStorage: t.NeedStorage,
|
||||
Properties: properties,
|
||||
Name: t.Name,
|
||||
CodeVersion: t.CodeVersion,
|
||||
Author: t.Author,
|
||||
|
@ -403,6 +411,32 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
|||
contracts[contract.ScriptHash()] = contract
|
||||
|
||||
case *transaction.InvocationTX:
|
||||
vm := vm.New(vm.ModeMute)
|
||||
vm.SetCheckedHash(tx.VerificationHash().Bytes())
|
||||
vm.SetScriptGetter(func(hash util.Uint160) []byte {
|
||||
cs := bc.GetContractState(hash)
|
||||
if cs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cs.Script
|
||||
})
|
||||
systemInterop := newInteropContext(0x10, bc, block, tx)
|
||||
vm.RegisterInteropFuncs(systemInterop.getSystemInteropMap())
|
||||
vm.RegisterInteropFuncs(systemInterop.getNeoInteropMap())
|
||||
vm.LoadScript(t.Script)
|
||||
vm.Run()
|
||||
if !vm.HasFailed() {
|
||||
_, err := systemInterop.mem.Persist(bc.memStore)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to persist invocation results")
|
||||
}
|
||||
} else {
|
||||
log.WithFields(log.Fields{
|
||||
"tx": tx.Hash().ReverseString(),
|
||||
"block": block.Index,
|
||||
}).Warn("contract invocation failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,7 +489,7 @@ func (bc *Blockchain) persist(ctx context.Context) error {
|
|||
"persistedKeys": persisted,
|
||||
"headerHeight": bc.HeaderHeight(),
|
||||
"blockHeight": bc.BlockHeight(),
|
||||
"persistedHeight": bc.persistedHeight,
|
||||
"persistedHeight": atomic.LoadUint32(&bc.persistedHeight),
|
||||
"took": time.Since(start),
|
||||
}).Info("blockchain persist completed")
|
||||
}
|
||||
|
@ -505,6 +539,43 @@ func getTransactionFromStore(s storage.Store, hash util.Uint256) (*transaction.T
|
|||
return tx, height, nil
|
||||
}
|
||||
|
||||
// GetStorageItem returns an item from storage.
|
||||
func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *StorageItem {
|
||||
sItem := getStorageItemFromStore(bc.memStore, scripthash, key)
|
||||
if sItem == nil {
|
||||
sItem = getStorageItemFromStore(bc.Store, scripthash, key)
|
||||
}
|
||||
return sItem
|
||||
}
|
||||
|
||||
// GetStorageItems returns all storage items for a given scripthash.
|
||||
func (bc *Blockchain) GetStorageItems(hash util.Uint160) (map[string]*StorageItem, error) {
|
||||
var siMap = make(map[string]*StorageItem)
|
||||
var err error
|
||||
|
||||
saveToMap := func(k, v []byte) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r := io.NewBinReaderFromBuf(v)
|
||||
si := &StorageItem{}
|
||||
si.DecodeBinary(r)
|
||||
if r.Err != nil {
|
||||
err = r.Err
|
||||
return
|
||||
}
|
||||
|
||||
// Cut prefix and hash.
|
||||
siMap[string(k[21:])] = si
|
||||
}
|
||||
bc.memStore.Seek(storage.AppendPrefix(storage.STStorage, hash.BytesReverse()), saveToMap)
|
||||
bc.Store.Seek(storage.AppendPrefix(storage.STStorage, hash.BytesReverse()), saveToMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return siMap, nil
|
||||
}
|
||||
|
||||
// GetBlock returns a Block by the given hash.
|
||||
func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
|
||||
block, err := getBlockFromStore(bc.memStore, hash)
|
||||
|
@ -517,6 +588,13 @@ func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
|
|||
if len(block.Transactions) == 0 {
|
||||
return nil, fmt.Errorf("only header is available")
|
||||
}
|
||||
for _, tx := range block.Transactions {
|
||||
stx, _, err := bc.GetTransaction(tx.Hash())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*tx = *stx
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
|
@ -689,6 +767,15 @@ func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *AccountState {
|
|||
return as
|
||||
}
|
||||
|
||||
// GetUnspentCoinState returns unspent coin state for given tx hash.
|
||||
func (bc *Blockchain) GetUnspentCoinState(hash util.Uint256) *UnspentCoinState {
|
||||
ucs, err := getUnspentCoinStateFromStore(bc.memStore, hash)
|
||||
if err != nil {
|
||||
ucs, _ = getUnspentCoinStateFromStore(bc.Store, hash)
|
||||
}
|
||||
return ucs
|
||||
}
|
||||
|
||||
// GetConfig returns the config stored in the blockchain
|
||||
func (bc *Blockchain) GetConfig() config.ProtocolConfiguration {
|
||||
return bc.config
|
||||
|
@ -756,9 +843,26 @@ func (bc *Blockchain) GetMemPool() MemPool {
|
|||
return bc.memPool
|
||||
}
|
||||
|
||||
// Verify verifies whether a transaction is bonafide or not.
|
||||
// VerifyBlock verifies block against its current state.
|
||||
func (bc *Blockchain) VerifyBlock(block *Block) error {
|
||||
prevHeader, err := bc.GetHeader(block.PrevHash)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to get previous header")
|
||||
}
|
||||
if prevHeader.Index+1 != block.Index {
|
||||
return errors.New("previous header index doesn't match")
|
||||
}
|
||||
if prevHeader.Timestamp >= block.Timestamp {
|
||||
return errors.New("block is not newer than the previous one")
|
||||
}
|
||||
return bc.verifyBlockWitnesses(block, prevHeader)
|
||||
}
|
||||
|
||||
// VerifyTx verifies whether a transaction is bonafide or not. Block parameter
|
||||
// is used for easy interop access and can be omitted for transactions that are
|
||||
// not yet added into any block.
|
||||
// Golang implementation of Verify method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L270).
|
||||
func (bc *Blockchain) Verify(t *transaction.Transaction) error {
|
||||
func (bc *Blockchain) VerifyTx(t *transaction.Transaction, block *Block) error {
|
||||
if io.GetVarSize(t) > transaction.MaxTransactionSize {
|
||||
return errors.Errorf("invalid transaction size = %d. It shoud be less then MaxTransactionSize = %d", io.GetVarSize(t), transaction.MaxTransactionSize)
|
||||
}
|
||||
|
@ -771,11 +875,11 @@ func (bc *Blockchain) Verify(t *transaction.Transaction) error {
|
|||
if IsDoubleSpend(bc.Store, t) {
|
||||
return errors.New("invalid transaction caused by double spending")
|
||||
}
|
||||
if ok := bc.verifyOutputs(t); !ok {
|
||||
return errors.New("invalid transaction's outputs")
|
||||
if err := bc.verifyOutputs(t); err != nil {
|
||||
return errors.Wrap(err, "wrong outputs")
|
||||
}
|
||||
if ok := bc.verifyResults(t); !ok {
|
||||
return errors.New("invalid transaction's results")
|
||||
if err := bc.verifyResults(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, a := range t.Attributes {
|
||||
|
@ -784,7 +888,7 @@ func (bc *Blockchain) Verify(t *transaction.Transaction) error {
|
|||
}
|
||||
}
|
||||
|
||||
return bc.VerifyWitnesses(t)
|
||||
return bc.verifyTxWitnesses(t, block)
|
||||
}
|
||||
|
||||
func (bc *Blockchain) verifyInputs(t *transaction.Transaction) bool {
|
||||
|
@ -799,31 +903,31 @@ func (bc *Blockchain) verifyInputs(t *transaction.Transaction) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (bc *Blockchain) verifyOutputs(t *transaction.Transaction) bool {
|
||||
func (bc *Blockchain) verifyOutputs(t *transaction.Transaction) error {
|
||||
for assetID, outputs := range t.GroupOutputByAssetID() {
|
||||
assetState := bc.GetAssetState(assetID)
|
||||
if assetState == nil {
|
||||
return false
|
||||
return fmt.Errorf("no asset state for %s", assetID.ReverseString())
|
||||
}
|
||||
|
||||
if assetState.Expiration < bc.blockHeight+1 && assetState.AssetType != transaction.GoverningToken && assetState.AssetType != transaction.UtilityToken {
|
||||
return false
|
||||
return fmt.Errorf("asset %s expired", assetID.ReverseString())
|
||||
}
|
||||
|
||||
for _, out := range outputs {
|
||||
if int64(out.Amount)%int64(math.Pow10(8-int(assetState.Precision))) != 0 {
|
||||
return false
|
||||
return fmt.Errorf("output is not compliant with %s asset precision", assetID.ReverseString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *Blockchain) verifyResults(t *transaction.Transaction) bool {
|
||||
results := bc.GetTransationResults(t)
|
||||
func (bc *Blockchain) verifyResults(t *transaction.Transaction) error {
|
||||
results := bc.GetTransactionResults(t)
|
||||
if results == nil {
|
||||
return false
|
||||
return errors.New("tx has no results")
|
||||
}
|
||||
var resultsDestroy []*transaction.Result
|
||||
var resultsIssue []*transaction.Result
|
||||
|
@ -837,43 +941,49 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction) bool {
|
|||
}
|
||||
}
|
||||
if len(resultsDestroy) > 1 {
|
||||
return false
|
||||
return errors.New("tx has more than 1 destroy output")
|
||||
}
|
||||
if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != utilityTokenTX().Hash() {
|
||||
return false
|
||||
return errors.New("tx destroys non-utility token")
|
||||
}
|
||||
sysfee := bc.SystemFee(t)
|
||||
if sysfee.GreaterThan(util.Fixed8(0)) {
|
||||
if len(resultsDestroy) == 0 {
|
||||
return fmt.Errorf("system requires to pay %s fee, but tx pays nothing", sysfee.String())
|
||||
}
|
||||
if resultsDestroy[0].Amount.LessThan(sysfee) {
|
||||
return fmt.Errorf("system requires to pay %s fee, but tx pays %s only", sysfee.String(), resultsDestroy[0].Amount.String())
|
||||
}
|
||||
if bc.SystemFee(t).GreaterThan(util.Fixed8(0)) && (len(resultsDestroy) == 0 || resultsDestroy[0].Amount.LessThan(bc.SystemFee(t))) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch t.Type {
|
||||
case transaction.MinerType, transaction.ClaimType:
|
||||
for _, r := range resultsIssue {
|
||||
if r.AssetID != utilityTokenTX().Hash() {
|
||||
return false
|
||||
return errors.New("miner or claim tx issues non-utility tokens")
|
||||
}
|
||||
}
|
||||
break
|
||||
case transaction.IssueType:
|
||||
for _, r := range resultsIssue {
|
||||
if r.AssetID == utilityTokenTX().Hash() {
|
||||
return false
|
||||
return errors.New("issue tx issues utility tokens")
|
||||
}
|
||||
}
|
||||
break
|
||||
default:
|
||||
if len(resultsIssue) > 0 {
|
||||
return false
|
||||
return errors.New("non issue/miner/claim tx issues tokens")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTransationResults returns the transaction results aggregate by assetID.
|
||||
// GetTransactionResults returns the transaction results aggregate by assetID.
|
||||
// Golang of GetTransationResults method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L207)
|
||||
func (bc *Blockchain) GetTransationResults(t *transaction.Transaction) []*transaction.Result {
|
||||
func (bc *Blockchain) GetTransactionResults(t *transaction.Transaction) []*transaction.Result {
|
||||
var tempResults []*transaction.Result
|
||||
var results []*transaction.Result
|
||||
tempGroupResult := make(map[util.Uint256]util.Fixed8)
|
||||
|
@ -918,7 +1028,8 @@ func (bc *Blockchain) GetTransationResults(t *transaction.Transaction) []*transa
|
|||
// GetScriptHashesForVerifyingClaim returns all ScriptHashes of Claim transaction
|
||||
// which has a different implementation from generic GetScriptHashesForVerifying.
|
||||
func (bc *Blockchain) GetScriptHashesForVerifyingClaim(t *transaction.Transaction) ([]util.Uint160, error) {
|
||||
hashes := make([]util.Uint160, 0)
|
||||
// Avoiding duplicates.
|
||||
hashmap := make(map[util.Uint160]bool)
|
||||
|
||||
claim := t.Data.(*transaction.ClaimTX)
|
||||
clGroups := make(map[util.Uint256][]*transaction.Input)
|
||||
|
@ -934,10 +1045,14 @@ func (bc *Blockchain) GetScriptHashesForVerifyingClaim(t *transaction.Transactio
|
|||
if len(refTx.Outputs) <= int(input.PrevIndex) {
|
||||
return nil, fmt.Errorf("wrong PrevIndex reference")
|
||||
}
|
||||
hashes = append(hashes, refTx.Outputs[input.PrevIndex].ScriptHash)
|
||||
hashmap[refTx.Outputs[input.PrevIndex].ScriptHash] = true
|
||||
}
|
||||
}
|
||||
if len(hashes) > 0 {
|
||||
if len(hashmap) > 0 {
|
||||
hashes := make([]util.Uint160, 0, len(hashmap))
|
||||
for k := range hashmap {
|
||||
hashes = append(hashes, k)
|
||||
}
|
||||
return hashes, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no hashes found")
|
||||
|
@ -997,41 +1112,25 @@ func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([
|
|||
|
||||
}
|
||||
|
||||
// VerifyWitnesses verify the scripts (witnesses) that come with a given
|
||||
// transaction. It can reorder them by ScriptHash, because that's required to
|
||||
// match a slice of script hashes from the Blockchain.
|
||||
// Golang implementation of VerifyWitnesses method in C# (https://github.com/neo-project/neo/blob/master/neo/SmartContract/Helper.cs#L87).
|
||||
// Unfortunately the IVerifiable interface could not be implemented because we can't move the References method in blockchain.go to the transaction.go file
|
||||
func (bc *Blockchain) VerifyWitnesses(t *transaction.Transaction) error {
|
||||
hashes, err := bc.GetScriptHashesForVerifying(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
witnesses := t.Scripts
|
||||
if len(hashes) != len(witnesses) {
|
||||
return errors.Errorf("expected len(hashes) == len(witnesses). got: %d != %d", len(hashes), len(witnesses))
|
||||
}
|
||||
sort.Slice(hashes, func(i, j int) bool { return hashes[i].Less(hashes[j]) })
|
||||
sort.Slice(witnesses, func(i, j int) bool { return witnesses[i].ScriptHash().Less(witnesses[j].ScriptHash()) })
|
||||
for i := 0; i < len(hashes); i++ {
|
||||
verification := witnesses[i].VerificationScript
|
||||
// verifyHashAgainstScript verifies given hash against the given witness.
|
||||
func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transaction.Witness, checkedHash util.Uint256, interopCtx *interopContext) error {
|
||||
verification := witness.VerificationScript
|
||||
|
||||
if len(verification) == 0 {
|
||||
bb := new(bytes.Buffer)
|
||||
err = vm.EmitAppCall(bb, hashes[i], false)
|
||||
err := vm.EmitAppCall(bb, hash, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verification = bb.Bytes()
|
||||
} else {
|
||||
if h := witnesses[i].ScriptHash(); hashes[i] != h {
|
||||
return errors.Errorf("hash mismatch for script #%d", i)
|
||||
if h := witness.ScriptHash(); hash != h {
|
||||
return errors.New("witness hash mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
vm := vm.New(vm.ModeMute)
|
||||
vm.SetCheckedHash(t.VerificationHash().Bytes())
|
||||
vm.SetCheckedHash(checkedHash.Bytes())
|
||||
vm.SetScriptGetter(func(hash util.Uint160) []byte {
|
||||
cs := bc.GetContractState(hash)
|
||||
if cs == nil {
|
||||
|
@ -1039,8 +1138,10 @@ func (bc *Blockchain) VerifyWitnesses(t *transaction.Transaction) error {
|
|||
}
|
||||
return cs.Script
|
||||
})
|
||||
vm.RegisterInteropFuncs(interopCtx.getSystemInteropMap())
|
||||
vm.RegisterInteropFuncs(interopCtx.getNeoInteropMap())
|
||||
vm.LoadScript(verification)
|
||||
vm.LoadScript(witnesses[i].InvocationScript)
|
||||
vm.LoadScript(witness.InvocationScript)
|
||||
vm.Run()
|
||||
if vm.HasFailed() {
|
||||
return errors.Errorf("vm failed to execute the script")
|
||||
|
@ -1057,11 +1158,52 @@ func (bc *Blockchain) VerifyWitnesses(t *transaction.Transaction) error {
|
|||
} else {
|
||||
return errors.Errorf("no result returned from the script")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyTxWitnesses verify the scripts (witnesses) that come with a given
|
||||
// transaction. It can reorder them by ScriptHash, because that's required to
|
||||
// match a slice of script hashes from the Blockchain. Block parameter
|
||||
// is used for easy interop access and can be omitted for transactions that are
|
||||
// not yet added into any block.
|
||||
// Golang implementation of VerifyWitnesses method in C# (https://github.com/neo-project/neo/blob/master/neo/SmartContract/Helper.cs#L87).
|
||||
// Unfortunately the IVerifiable interface could not be implemented because we can't move the References method in blockchain.go to the transaction.go file
|
||||
func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *Block) error {
|
||||
hashes, err := bc.GetScriptHashesForVerifying(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
witnesses := t.Scripts
|
||||
if len(hashes) != len(witnesses) {
|
||||
return errors.Errorf("expected len(hashes) == len(witnesses). got: %d != %d", len(hashes), len(witnesses))
|
||||
}
|
||||
sort.Slice(hashes, func(i, j int) bool { return hashes[i].Less(hashes[j]) })
|
||||
sort.Slice(witnesses, func(i, j int) bool { return witnesses[i].ScriptHash().Less(witnesses[j].ScriptHash()) })
|
||||
interopCtx := newInteropContext(0, bc, block, t)
|
||||
for i := 0; i < len(hashes); i++ {
|
||||
err := bc.verifyHashAgainstScript(hashes[i], witnesses[i], t.VerificationHash(), interopCtx)
|
||||
if err != nil {
|
||||
numStr := fmt.Sprintf("witness #%d", i)
|
||||
return errors.Wrap(err, numStr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyBlockWitnesses is a block-specific implementation of VerifyWitnesses logic.
|
||||
func (bc *Blockchain) verifyBlockWitnesses(block *Block, prevHeader *Header) error {
|
||||
var hash util.Uint160
|
||||
if prevHeader == nil && block.PrevHash.Equals(util.Uint256{}) {
|
||||
hash = block.Script.ScriptHash()
|
||||
} else {
|
||||
hash = prevHeader.NextConsensus
|
||||
}
|
||||
interopCtx := newInteropContext(0, bc, nil, nil)
|
||||
return bc.verifyHashAgainstScript(hash, block.Script, block.VerificationHash(), interopCtx)
|
||||
}
|
||||
|
||||
func hashAndIndexToBytes(h util.Uint256, index uint32) []byte {
|
||||
buf := io.NewBufBinWriter()
|
||||
buf.WriteLE(h.BytesReverse())
|
||||
|
|
|
@ -4,9 +4,7 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/config"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -39,9 +37,9 @@ func TestAddHeaders(t *testing.T) {
|
|||
func TestAddBlock(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
blocks := []*Block{
|
||||
newBlock(1, newTX(transaction.MinerType)),
|
||||
newBlock(2, newTX(transaction.MinerType)),
|
||||
newBlock(3, newTX(transaction.MinerType)),
|
||||
newBlock(1, newMinerTX()),
|
||||
newBlock(2, newMinerTX()),
|
||||
newBlock(3, newMinerTX()),
|
||||
}
|
||||
|
||||
for i := 0; i < len(blocks); i++ {
|
||||
|
@ -70,7 +68,7 @@ func TestAddBlock(t *testing.T) {
|
|||
|
||||
func TestGetHeader(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
block := newBlock(1, newTX(transaction.MinerType))
|
||||
block := newBlock(1, newMinerTX())
|
||||
err := bc.AddBlock(block)
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
@ -103,7 +101,7 @@ func TestGetBlock(t *testing.T) {
|
|||
for i := 0; i < len(blocks); i++ {
|
||||
block, err := bc.GetBlock(blocks[i].Hash())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Fatalf("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())
|
||||
|
@ -138,8 +136,9 @@ func TestGetTransaction(t *testing.T) {
|
|||
block := getDecodedBlock(t, 2)
|
||||
bc := newTestChain(t)
|
||||
|
||||
assert.Nil(t, bc.AddBlock(b1))
|
||||
assert.Nil(t, bc.AddBlock(block))
|
||||
// These are from some kind of different chain, so can't be added via AddBlock().
|
||||
assert.Nil(t, bc.storeBlock(b1))
|
||||
assert.Nil(t, bc.storeBlock(block))
|
||||
|
||||
// Test unpersisted and persisted access
|
||||
for j := 0; j < 2; j++ {
|
||||
|
@ -155,16 +154,3 @@ func TestGetTransaction(t *testing.T) {
|
|||
assert.NoError(t, bc.persist(context.Background()))
|
||||
}
|
||||
}
|
||||
|
||||
func newTestChain(t *testing.T) *Blockchain {
|
||||
cfg, err := config.Load("../../config", config.ModeUnitTestNet)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
chain, err := NewBlockchain(storage.NewMemoryStore(), cfg.ProtocolConfiguration)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go chain.Run(context.Background())
|
||||
return chain
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ type Blockchainer interface {
|
|||
BlockHeight() uint32
|
||||
HeaderHeight() uint32
|
||||
GetBlock(hash util.Uint256) (*Block, error)
|
||||
GetContractState(hash util.Uint160) *ContractState
|
||||
GetHeaderHash(int) util.Uint256
|
||||
GetHeader(hash util.Uint256) (*Header, error)
|
||||
CurrentHeaderHash() util.Uint256
|
||||
|
@ -23,9 +24,13 @@ type Blockchainer interface {
|
|||
HasTransaction(util.Uint256) bool
|
||||
GetAssetState(util.Uint256) *AssetState
|
||||
GetAccountState(util.Uint160) *AccountState
|
||||
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
||||
GetStorageItem(scripthash util.Uint160, key []byte) *StorageItem
|
||||
GetStorageItems(hash util.Uint160) (map[string]*StorageItem, error)
|
||||
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||
GetUnspentCoinState(util.Uint256) *UnspentCoinState
|
||||
References(t *transaction.Transaction) map[transaction.Input]*transaction.Output
|
||||
Feer // fee interface
|
||||
Verify(t *transaction.Transaction) error
|
||||
VerifyTx(*transaction.Transaction, *Block) error
|
||||
GetMemPool() MemPool
|
||||
}
|
||||
|
|
|
@ -16,14 +16,12 @@ type ContractState struct {
|
|||
Script []byte
|
||||
ParamList []smartcontract.ParamType
|
||||
ReturnType smartcontract.ParamType
|
||||
Properties []byte
|
||||
Properties smartcontract.PropertyState
|
||||
Name string
|
||||
CodeVersion string
|
||||
Author string
|
||||
Email string
|
||||
Description string
|
||||
HasStorage bool
|
||||
HasDynamicInvoke bool
|
||||
|
||||
scriptHash util.Uint160
|
||||
}
|
||||
|
@ -44,52 +42,80 @@ func (a Contracts) commit(b storage.Batch) error {
|
|||
}
|
||||
|
||||
// DecodeBinary implements Serializable interface.
|
||||
func (a *ContractState) DecodeBinary(br *io.BinReader) {
|
||||
a.Script = br.ReadBytes()
|
||||
func (cs *ContractState) DecodeBinary(br *io.BinReader) {
|
||||
cs.Script = br.ReadBytes()
|
||||
paramBytes := br.ReadBytes()
|
||||
a.ParamList = make([]smartcontract.ParamType, len(paramBytes))
|
||||
cs.ParamList = make([]smartcontract.ParamType, len(paramBytes))
|
||||
for k := range paramBytes {
|
||||
a.ParamList[k] = smartcontract.ParamType(paramBytes[k])
|
||||
cs.ParamList[k] = smartcontract.ParamType(paramBytes[k])
|
||||
}
|
||||
br.ReadLE(&a.ReturnType)
|
||||
a.Properties = br.ReadBytes()
|
||||
a.Name = br.ReadString()
|
||||
a.CodeVersion = br.ReadString()
|
||||
a.Author = br.ReadString()
|
||||
a.Email = br.ReadString()
|
||||
a.Description = br.ReadString()
|
||||
br.ReadLE(&a.HasStorage)
|
||||
br.ReadLE(&a.HasDynamicInvoke)
|
||||
a.createHash()
|
||||
br.ReadLE(&cs.ReturnType)
|
||||
br.ReadLE(&cs.Properties)
|
||||
cs.Name = br.ReadString()
|
||||
cs.CodeVersion = br.ReadString()
|
||||
cs.Author = br.ReadString()
|
||||
cs.Email = br.ReadString()
|
||||
cs.Description = br.ReadString()
|
||||
cs.createHash()
|
||||
}
|
||||
|
||||
// EncodeBinary implements Serializable interface.
|
||||
func (a *ContractState) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteBytes(a.Script)
|
||||
bw.WriteVarUint(uint64(len(a.ParamList)))
|
||||
for k := range a.ParamList {
|
||||
bw.WriteLE(a.ParamList[k])
|
||||
func (cs *ContractState) EncodeBinary(bw *io.BinWriter) {
|
||||
bw.WriteBytes(cs.Script)
|
||||
bw.WriteVarUint(uint64(len(cs.ParamList)))
|
||||
for k := range cs.ParamList {
|
||||
bw.WriteLE(cs.ParamList[k])
|
||||
}
|
||||
bw.WriteLE(a.ReturnType)
|
||||
bw.WriteBytes(a.Properties)
|
||||
bw.WriteString(a.Name)
|
||||
bw.WriteString(a.CodeVersion)
|
||||
bw.WriteString(a.Author)
|
||||
bw.WriteString(a.Email)
|
||||
bw.WriteString(a.Description)
|
||||
bw.WriteLE(a.HasStorage)
|
||||
bw.WriteLE(a.HasDynamicInvoke)
|
||||
bw.WriteLE(cs.ReturnType)
|
||||
bw.WriteLE(cs.Properties)
|
||||
bw.WriteString(cs.Name)
|
||||
bw.WriteString(cs.CodeVersion)
|
||||
bw.WriteString(cs.Author)
|
||||
bw.WriteString(cs.Email)
|
||||
bw.WriteString(cs.Description)
|
||||
}
|
||||
|
||||
// putContractStateIntoStore puts given contract state into the given store.
|
||||
func putContractStateIntoStore(s storage.Store, cs *ContractState) error {
|
||||
buf := io.NewBufBinWriter()
|
||||
cs.EncodeBinary(buf.BinWriter)
|
||||
if buf.Err != nil {
|
||||
return buf.Err
|
||||
}
|
||||
key := storage.AppendPrefix(storage.STContract, cs.ScriptHash().Bytes())
|
||||
return s.Put(key, buf.Bytes())
|
||||
}
|
||||
|
||||
// deleteContractStateInStore deletes given contract state in the given store.
|
||||
func deleteContractStateInStore(s storage.Store, hash util.Uint160) error {
|
||||
key := storage.AppendPrefix(storage.STContract, hash.Bytes())
|
||||
return s.Delete(key)
|
||||
}
|
||||
|
||||
// ScriptHash returns a contract script hash.
|
||||
func (a *ContractState) ScriptHash() util.Uint160 {
|
||||
if a.scriptHash.Equals(util.Uint160{}) {
|
||||
a.createHash()
|
||||
func (cs *ContractState) ScriptHash() util.Uint160 {
|
||||
if cs.scriptHash.Equals(util.Uint160{}) {
|
||||
cs.createHash()
|
||||
}
|
||||
return a.scriptHash
|
||||
return cs.scriptHash
|
||||
}
|
||||
|
||||
// createHash creates contract script hash.
|
||||
func (a *ContractState) createHash() {
|
||||
a.scriptHash = hash.Hash160(a.Script)
|
||||
func (cs *ContractState) createHash() {
|
||||
cs.scriptHash = hash.Hash160(cs.Script)
|
||||
}
|
||||
|
||||
// HasStorage checks whether the contract has storage property set.
|
||||
func (cs *ContractState) HasStorage() bool {
|
||||
return (cs.Properties & smartcontract.HasStorage) != 0
|
||||
}
|
||||
|
||||
// HasDynamicInvoke checks whether the contract has dynamic invoke property set.
|
||||
func (cs *ContractState) HasDynamicInvoke() bool {
|
||||
return (cs.Properties & smartcontract.HasDynamicInvoke) != 0
|
||||
}
|
||||
|
||||
// IsPayable checks whether the contract has payable property set.
|
||||
func (cs *ContractState) IsPayable() bool {
|
||||
return (cs.Properties & smartcontract.IsPayable) != 0
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package core
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
|
@ -16,14 +17,12 @@ func TestEncodeDecodeContractState(t *testing.T) {
|
|||
Script: script,
|
||||
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
|
||||
ReturnType: smartcontract.BoolType,
|
||||
Properties: []byte("smth"),
|
||||
Properties: smartcontract.HasStorage,
|
||||
Name: "Contracto",
|
||||
CodeVersion: "1.0.0",
|
||||
Author: "Joe Random",
|
||||
Email: "joe@example.com",
|
||||
Description: "Test contract",
|
||||
HasStorage: true,
|
||||
HasDynamicInvoke: false,
|
||||
}
|
||||
|
||||
assert.Equal(t, hash.Hash160(script), contract.ScriptHash())
|
||||
|
@ -37,3 +36,42 @@ func TestEncodeDecodeContractState(t *testing.T) {
|
|||
assert.Equal(t, contract, contractDecoded)
|
||||
assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash())
|
||||
}
|
||||
|
||||
func TestContractStateProperties(t *testing.T) {
|
||||
flaggedContract := ContractState{
|
||||
Properties: smartcontract.HasStorage | smartcontract.HasDynamicInvoke | smartcontract.IsPayable,
|
||||
}
|
||||
nonFlaggedContract := ContractState{
|
||||
ReturnType: smartcontract.BoolType,
|
||||
}
|
||||
assert.Equal(t, true, flaggedContract.HasStorage())
|
||||
assert.Equal(t, true, flaggedContract.HasDynamicInvoke())
|
||||
assert.Equal(t, true, flaggedContract.IsPayable())
|
||||
assert.Equal(t, false, nonFlaggedContract.HasStorage())
|
||||
assert.Equal(t, false, nonFlaggedContract.HasDynamicInvoke())
|
||||
assert.Equal(t, false, nonFlaggedContract.IsPayable())
|
||||
}
|
||||
|
||||
func TestPutGetDeleteContractState(t *testing.T) {
|
||||
s := storage.NewMemoryStore()
|
||||
script := []byte("testscript")
|
||||
|
||||
contract := &ContractState{
|
||||
Script: script,
|
||||
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
|
||||
ReturnType: smartcontract.BoolType,
|
||||
Properties: smartcontract.HasStorage,
|
||||
Name: "Contracto",
|
||||
CodeVersion: "1.0.0",
|
||||
Author: "Joe Random",
|
||||
Email: "joe@example.com",
|
||||
Description: "Test contract",
|
||||
}
|
||||
assert.NoError(t, putContractStateIntoStore(s, contract))
|
||||
csRead := getContractStateFromStore(s, contract.ScriptHash())
|
||||
assert.NotNil(t, csRead)
|
||||
assert.Equal(t, contract, csRead)
|
||||
assert.NoError(t, deleteContractStateInStore(s, contract.ScriptHash()))
|
||||
csRead2 := getContractStateFromStore(s, contract.ScriptHash())
|
||||
assert.Nil(t, csRead2)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -8,46 +9,105 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/config"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var newBlockPrevHash util.Uint256
|
||||
var unitTestNetCfg config.Config
|
||||
|
||||
var privNetKeys = []string{
|
||||
"KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY",
|
||||
"KzfPUYDC9n2yf4fK5ro4C8KMcdeXtFuEnStycbZgX3GomiUsvX6W",
|
||||
"KzgWE3u3EDp13XPXXuTKZxeJ3Gi8Bsm8f9ijY3ZsCKKRvZUo1Cdn",
|
||||
"L2oEXKRAAMiPEZukwR5ho2S6SMeQLhcK9mF71ZnF7GvT8dU4Kkgz",
|
||||
}
|
||||
|
||||
// newTestChain should be called before newBlock invocation to properly setup
|
||||
// global state.
|
||||
func newTestChain(t *testing.T) *Blockchain {
|
||||
var err error
|
||||
unitTestNetCfg, err = config.Load("../../config", config.ModeUnitTestNet)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
chain, err := NewBlockchain(storage.NewMemoryStore(), unitTestNetCfg.ProtocolConfiguration)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go chain.Run(context.Background())
|
||||
zeroHash, err := chain.GetHeader(chain.GetHeaderHash(0))
|
||||
require.Nil(t, err)
|
||||
newBlockPrevHash = zeroHash.Hash()
|
||||
return chain
|
||||
}
|
||||
|
||||
func newBlock(index uint32, txs ...*transaction.Transaction) *Block {
|
||||
validators, _ := getValidators(unitTestNetCfg.ProtocolConfiguration)
|
||||
vlen := len(validators)
|
||||
valScript, _ := smartcontract.CreateMultiSigRedeemScript(
|
||||
vlen-(vlen-1)/3,
|
||||
validators,
|
||||
)
|
||||
witness := &transaction.Witness{
|
||||
VerificationScript: valScript,
|
||||
}
|
||||
b := &Block{
|
||||
BlockBase: BlockBase{
|
||||
Version: 0,
|
||||
PrevHash: hash.Sha256([]byte("a")),
|
||||
MerkleRoot: hash.Sha256([]byte("b")),
|
||||
Timestamp: uint32(time.Now().UTC().Unix()),
|
||||
PrevHash: newBlockPrevHash,
|
||||
Timestamp: uint32(time.Now().UTC().Unix()) + index,
|
||||
Index: index,
|
||||
ConsensusData: 1111,
|
||||
NextConsensus: util.Uint160{},
|
||||
Script: &transaction.Witness{
|
||||
VerificationScript: []byte{0x0},
|
||||
InvocationScript: []byte{0x1},
|
||||
},
|
||||
NextConsensus: witness.ScriptHash(),
|
||||
Script: witness,
|
||||
},
|
||||
Transactions: txs,
|
||||
}
|
||||
|
||||
_ = b.rebuildMerkleRoot()
|
||||
b.createHash()
|
||||
newBlockPrevHash = b.Hash()
|
||||
|
||||
invScript := make([]byte, 0)
|
||||
for _, wif := range privNetKeys {
|
||||
pKey, err := keys.NewPrivateKeyFromWIF(wif)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b, err := b.getHashableData()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sig, err := pKey.Sign(b)
|
||||
if err != nil || len(sig) != 64 {
|
||||
panic(err)
|
||||
}
|
||||
// 0x40 is PUSHBYTES64
|
||||
invScript = append(invScript, 0x40)
|
||||
invScript = append(invScript, sig...)
|
||||
}
|
||||
b.Script.InvocationScript = invScript
|
||||
return b
|
||||
}
|
||||
|
||||
func makeBlocks(n int) []*Block {
|
||||
blocks := make([]*Block, n)
|
||||
for i := 0; i < n; i++ {
|
||||
blocks[i] = newBlock(uint32(i+1), newTX(transaction.MinerType))
|
||||
blocks[i] = newBlock(uint32(i+1), newMinerTX())
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
func newTX(t transaction.TXType) *transaction.Transaction {
|
||||
func newMinerTX() *transaction.Transaction {
|
||||
return &transaction.Transaction{
|
||||
Type: t,
|
||||
Type: transaction.MinerType,
|
||||
Data: &transaction.MinerTX{},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
738
pkg/core/interop_neo.go
Normal file
738
pkg/core/interop_neo.go
Normal file
|
@ -0,0 +1,738 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
gherr "github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxContractScriptSize is the maximum script size for a contract.
|
||||
MaxContractScriptSize = 1024 * 1024
|
||||
// MaxContractParametersNum is the maximum number of parameters for a contract.
|
||||
MaxContractParametersNum = 252
|
||||
// MaxContractStringLen is the maximum length for contract metadata strings.
|
||||
MaxContractStringLen = 252
|
||||
// MaxAssetNameLen is the maximum length of asset name.
|
||||
MaxAssetNameLen = 1024
|
||||
// MaxAssetPrecision is the maximum precision of asset.
|
||||
MaxAssetPrecision = 8
|
||||
// BlocksPerYear is a multiplier for asset renewal.
|
||||
BlocksPerYear = 2000000
|
||||
// DefaultAssetLifetime is the default lifetime of an asset (which differs
|
||||
// from assets created by register tx).
|
||||
DefaultAssetLifetime = 1 + BlocksPerYear
|
||||
)
|
||||
|
||||
// txInOut is used to pushed one key-value pair from References() onto the stack.
|
||||
type txInOut struct {
|
||||
in transaction.Input
|
||||
out transaction.Output
|
||||
}
|
||||
|
||||
// headerGetVersion returns version from the header.
|
||||
func (ic *interopContext) headerGetVersion(v *vm.VM) error {
|
||||
header, err := popHeaderFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(header.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
// headerGetConsensusData returns consensus data from the header.
|
||||
func (ic *interopContext) headerGetConsensusData(v *vm.VM) error {
|
||||
header, err := popHeaderFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(header.ConsensusData)
|
||||
return nil
|
||||
}
|
||||
|
||||
// headerGetMerkleRoot returns version from the header.
|
||||
func (ic *interopContext) headerGetMerkleRoot(v *vm.VM) error {
|
||||
header, err := popHeaderFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(header.MerkleRoot.BytesReverse())
|
||||
return nil
|
||||
}
|
||||
|
||||
// headerGetNextConsensus returns version from the header.
|
||||
func (ic *interopContext) headerGetNextConsensus(v *vm.VM) error {
|
||||
header, err := popHeaderFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(header.NextConsensus.BytesReverse())
|
||||
return nil
|
||||
}
|
||||
|
||||
// txGetAttributes returns current transaction attributes.
|
||||
func (ic *interopContext) txGetAttributes(v *vm.VM) error {
|
||||
txInterface := v.Estack().Pop().Value()
|
||||
tx, ok := txInterface.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return errors.New("value is not a transaction")
|
||||
}
|
||||
if len(tx.Attributes) > vm.MaxArraySize {
|
||||
return errors.New("too many attributes")
|
||||
}
|
||||
attrs := make([]vm.StackItem, 0, len(tx.Attributes))
|
||||
for _, attr := range tx.Attributes {
|
||||
attrs = append(attrs, vm.NewInteropItem(attr))
|
||||
}
|
||||
v.Estack().PushVal(attrs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// txGetInputs returns current transaction inputs.
|
||||
func (ic *interopContext) txGetInputs(v *vm.VM) error {
|
||||
txInterface := v.Estack().Pop().Value()
|
||||
tx, ok := txInterface.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return errors.New("value is not a transaction")
|
||||
}
|
||||
if len(tx.Inputs) > vm.MaxArraySize {
|
||||
return errors.New("too many inputs")
|
||||
}
|
||||
inputs := make([]vm.StackItem, 0, len(tx.Inputs))
|
||||
for _, input := range tx.Inputs {
|
||||
inputs = append(inputs, vm.NewInteropItem(input))
|
||||
}
|
||||
v.Estack().PushVal(inputs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// txGetOutputs returns current transaction outputs.
|
||||
func (ic *interopContext) txGetOutputs(v *vm.VM) error {
|
||||
txInterface := v.Estack().Pop().Value()
|
||||
tx, ok := txInterface.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return errors.New("value is not a transaction")
|
||||
}
|
||||
if len(tx.Outputs) > vm.MaxArraySize {
|
||||
return errors.New("too many outputs")
|
||||
}
|
||||
outputs := make([]vm.StackItem, 0, len(tx.Outputs))
|
||||
for _, output := range tx.Outputs {
|
||||
outputs = append(outputs, vm.NewInteropItem(output))
|
||||
}
|
||||
v.Estack().PushVal(outputs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// txGetReferences returns current transaction references.
|
||||
func (ic *interopContext) txGetReferences(v *vm.VM) error {
|
||||
txInterface := v.Estack().Pop().Value()
|
||||
tx, ok := txInterface.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return fmt.Errorf("type mismatch: %T is not a Transaction", txInterface)
|
||||
}
|
||||
refs := ic.bc.References(tx)
|
||||
if len(refs) > vm.MaxArraySize {
|
||||
return errors.New("too many references")
|
||||
}
|
||||
|
||||
stackrefs := make([]vm.StackItem, 0, len(refs))
|
||||
for k, v := range refs {
|
||||
tio := txInOut{k, *v}
|
||||
stackrefs = append(stackrefs, vm.NewInteropItem(tio))
|
||||
}
|
||||
v.Estack().PushVal(stackrefs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// txGetType returns current transaction type.
|
||||
func (ic *interopContext) txGetType(v *vm.VM) error {
|
||||
txInterface := v.Estack().Pop().Value()
|
||||
tx, ok := txInterface.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return errors.New("value is not a transaction")
|
||||
}
|
||||
v.Estack().PushVal(int(tx.Type))
|
||||
return nil
|
||||
}
|
||||
|
||||
// txGetUnspentCoins returns current transaction unspent coins.
|
||||
func (ic *interopContext) txGetUnspentCoins(v *vm.VM) error {
|
||||
txInterface := v.Estack().Pop().Value()
|
||||
tx, ok := txInterface.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return errors.New("value is not a transaction")
|
||||
}
|
||||
ucs := ic.bc.GetUnspentCoinState(tx.Hash())
|
||||
if ucs == nil {
|
||||
return errors.New("no unspent coin state found")
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(ucs))
|
||||
return nil
|
||||
}
|
||||
|
||||
// txGetWitnesses returns current transaction witnesses.
|
||||
func (ic *interopContext) txGetWitnesses(v *vm.VM) error {
|
||||
txInterface := v.Estack().Pop().Value()
|
||||
tx, ok := txInterface.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return errors.New("value is not a transaction")
|
||||
}
|
||||
if len(tx.Scripts) > vm.MaxArraySize {
|
||||
return errors.New("too many outputs")
|
||||
}
|
||||
scripts := make([]vm.StackItem, 0, len(tx.Scripts))
|
||||
for _, script := range tx.Scripts {
|
||||
scripts = append(scripts, vm.NewInteropItem(script))
|
||||
}
|
||||
v.Estack().PushVal(scripts)
|
||||
return nil
|
||||
}
|
||||
|
||||
// popInputFromVM returns transaction.Input from the first estack element.
|
||||
func popInputFromVM(v *vm.VM) (*transaction.Input, error) {
|
||||
inInterface := v.Estack().Pop().Value()
|
||||
input, ok := inInterface.(*transaction.Input)
|
||||
if !ok {
|
||||
txio, ok := inInterface.(txInOut)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type mismatch: %T is not an Input or txInOut", inInterface)
|
||||
}
|
||||
input = &txio.in
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
// inputGetHash returns hash from the given input.
|
||||
func (ic *interopContext) inputGetHash(v *vm.VM) error {
|
||||
input, err := popInputFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(input.PrevHash.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// inputGetIndex returns index from the given input.
|
||||
func (ic *interopContext) inputGetIndex(v *vm.VM) error {
|
||||
input, err := popInputFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(input.PrevIndex)
|
||||
return nil
|
||||
}
|
||||
|
||||
// popOutputFromVM returns transaction.Input from the first estack element.
|
||||
func popOutputFromVM(v *vm.VM) (*transaction.Output, error) {
|
||||
outInterface := v.Estack().Pop().Value()
|
||||
output, ok := outInterface.(*transaction.Output)
|
||||
if !ok {
|
||||
txio, ok := outInterface.(txInOut)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type mismatch: %T is not an Output or txInOut", outInterface)
|
||||
}
|
||||
output = &txio.out
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// outputGetAssetId returns asset ID from the given output.
|
||||
func (ic *interopContext) outputGetAssetID(v *vm.VM) error {
|
||||
output, err := popOutputFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(output.AssetID.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// outputGetScriptHash returns scripthash from the given output.
|
||||
func (ic *interopContext) outputGetScriptHash(v *vm.VM) error {
|
||||
output, err := popOutputFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(output.ScriptHash.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// outputGetValue returns value (amount) from the given output.
|
||||
func (ic *interopContext) outputGetValue(v *vm.VM) error {
|
||||
output, err := popOutputFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(int64(output.Amount))
|
||||
return nil
|
||||
}
|
||||
|
||||
// attrGetData returns tx attribute data.
|
||||
func (ic *interopContext) attrGetData(v *vm.VM) error {
|
||||
attrInterface := v.Estack().Pop().Value()
|
||||
attr, ok := attrInterface.(*transaction.Attribute)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an attribute", attr)
|
||||
}
|
||||
v.Estack().PushVal(attr.Data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// attrGetData returns tx attribute usage field.
|
||||
func (ic *interopContext) attrGetUsage(v *vm.VM) error {
|
||||
attrInterface := v.Estack().Pop().Value()
|
||||
attr, ok := attrInterface.(*transaction.Attribute)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an attribute", attr)
|
||||
}
|
||||
v.Estack().PushVal(int(attr.Usage))
|
||||
return nil
|
||||
}
|
||||
|
||||
// bcGetAccount returns or creates an account.
|
||||
func (ic *interopContext) bcGetAccount(v *vm.VM) error {
|
||||
accbytes := v.Estack().Pop().Bytes()
|
||||
acchash, err := util.Uint160DecodeBytes(accbytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acc := ic.bc.GetAccountState(acchash)
|
||||
if acc == nil {
|
||||
acc = NewAccountState(acchash)
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(acc))
|
||||
return nil
|
||||
}
|
||||
|
||||
// bcGetAsset returns an asset.
|
||||
func (ic *interopContext) bcGetAsset(v *vm.VM) error {
|
||||
asbytes := v.Estack().Pop().Bytes()
|
||||
ashash, err := util.Uint256DecodeBytes(asbytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
as := ic.bc.GetAssetState(ashash)
|
||||
if as == nil {
|
||||
return errors.New("asset not found")
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(as))
|
||||
return nil
|
||||
}
|
||||
|
||||
// accountGetBalance returns balance for a given account.
|
||||
func (ic *interopContext) accountGetBalance(v *vm.VM) error {
|
||||
accInterface := v.Estack().Pop().Value()
|
||||
acc, ok := accInterface.(*AccountState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an account state", acc)
|
||||
}
|
||||
asbytes := v.Estack().Pop().Bytes()
|
||||
ashash, err := util.Uint256DecodeBytes(asbytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
balance, ok := acc.Balances[ashash]
|
||||
if !ok {
|
||||
balance = util.Fixed8(0)
|
||||
}
|
||||
v.Estack().PushVal(int64(balance))
|
||||
return nil
|
||||
}
|
||||
|
||||
// accountGetScriptHash returns script hash of a given account.
|
||||
func (ic *interopContext) accountGetScriptHash(v *vm.VM) error {
|
||||
accInterface := v.Estack().Pop().Value()
|
||||
acc, ok := accInterface.(*AccountState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an account state", acc)
|
||||
}
|
||||
v.Estack().PushVal(acc.ScriptHash.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// accountGetVotes returns votes of a given account.
|
||||
func (ic *interopContext) accountGetVotes(v *vm.VM) error {
|
||||
accInterface := v.Estack().Pop().Value()
|
||||
acc, ok := accInterface.(*AccountState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an account state", acc)
|
||||
}
|
||||
if len(acc.Votes) > vm.MaxArraySize {
|
||||
return errors.New("too many votes")
|
||||
}
|
||||
votes := make([]vm.StackItem, 0, len(acc.Votes))
|
||||
for _, key := range acc.Votes {
|
||||
votes = append(votes, vm.NewByteArrayItem(key.Bytes()))
|
||||
}
|
||||
v.Estack().PushVal(votes)
|
||||
return nil
|
||||
}
|
||||
|
||||
// accountIsStandard checks whether given account is standard.
|
||||
func (ic *interopContext) accountIsStandard(v *vm.VM) error {
|
||||
accbytes := v.Estack().Pop().Bytes()
|
||||
acchash, err := util.Uint160DecodeBytes(accbytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
contract := ic.bc.GetContractState(acchash)
|
||||
res := contract == nil || vm.IsStandardContract(contract.Script)
|
||||
v.Estack().PushVal(res)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
// storageFind finds stored key-value pair.
|
||||
func (ic *interopContext) storageFind(v *vm.VM) error {
|
||||
stcInterface := v.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*StorageContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||
}
|
||||
err := ic.checkStorageContext(stc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prefix := string(v.Estack().Pop().Bytes())
|
||||
siMap, err := ic.bc.GetStorageItems(stc.ScriptHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range siMap {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
_ = v
|
||||
panic("TODO")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
// createContractStateFromVM pops all contract state elements from the VM
|
||||
// evaluation stack, does a lot of checks and returns ContractState if it
|
||||
// succeedes.
|
||||
func (ic *interopContext) createContractStateFromVM(v *vm.VM) (*ContractState, error) {
|
||||
if ic.trigger != 0x10 {
|
||||
return nil, errors.New("can't create contract when not triggered by an application")
|
||||
}
|
||||
script := v.Estack().Pop().Bytes()
|
||||
if len(script) > MaxContractScriptSize {
|
||||
return nil, errors.New("the script is too big")
|
||||
}
|
||||
paramBytes := v.Estack().Pop().Bytes()
|
||||
if len(paramBytes) > MaxContractParametersNum {
|
||||
return nil, errors.New("too many parameters for a script")
|
||||
}
|
||||
paramList := make([]smartcontract.ParamType, len(paramBytes))
|
||||
for k, v := range paramBytes {
|
||||
paramList[k] = smartcontract.ParamType(v)
|
||||
}
|
||||
retType := smartcontract.ParamType(v.Estack().Pop().BigInt().Int64())
|
||||
properties := smartcontract.PropertyState(v.Estack().Pop().BigInt().Int64())
|
||||
name := v.Estack().Pop().Bytes()
|
||||
if len(name) > MaxContractStringLen {
|
||||
return nil, errors.New("too big name")
|
||||
}
|
||||
version := v.Estack().Pop().Bytes()
|
||||
if len(version) > MaxContractStringLen {
|
||||
return nil, errors.New("too big version")
|
||||
}
|
||||
author := v.Estack().Pop().Bytes()
|
||||
if len(author) > MaxContractStringLen {
|
||||
return nil, errors.New("too big author")
|
||||
}
|
||||
email := v.Estack().Pop().Bytes()
|
||||
if len(email) > MaxContractStringLen {
|
||||
return nil, errors.New("too big email")
|
||||
}
|
||||
desc := v.Estack().Pop().Bytes()
|
||||
if len(desc) > MaxContractStringLen {
|
||||
return nil, errors.New("too big description")
|
||||
}
|
||||
contract := &ContractState{
|
||||
Script: script,
|
||||
ParamList: paramList,
|
||||
ReturnType: retType,
|
||||
Properties: properties,
|
||||
Name: string(name),
|
||||
CodeVersion: string(version),
|
||||
Author: string(author),
|
||||
Email: string(email),
|
||||
Description: string(desc),
|
||||
}
|
||||
return contract, nil
|
||||
}
|
||||
|
||||
// contractCreate creates a contract.
|
||||
func (ic *interopContext) contractCreate(v *vm.VM) error {
|
||||
newcontract, err := ic.createContractStateFromVM(v)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
contract := ic.bc.GetContractState(newcontract.ScriptHash())
|
||||
if contract == nil {
|
||||
contract = newcontract
|
||||
err := putContractStateIntoStore(ic.mem, contract)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(contract))
|
||||
return nil
|
||||
}
|
||||
|
||||
// contractGetScript returns a script associated with a contract.
|
||||
func (ic *interopContext) contractGetScript(v *vm.VM) error {
|
||||
csInterface := v.Estack().Pop().Value()
|
||||
cs, ok := csInterface.(*ContractState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a contract state", cs)
|
||||
}
|
||||
v.Estack().PushVal(cs.Script)
|
||||
return nil
|
||||
}
|
||||
|
||||
// contractIsPayable returns whether contract is payable.
|
||||
func (ic *interopContext) contractIsPayable(v *vm.VM) error {
|
||||
csInterface := v.Estack().Pop().Value()
|
||||
cs, ok := csInterface.(*ContractState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a contract state", cs)
|
||||
}
|
||||
v.Estack().PushVal(cs.IsPayable())
|
||||
return nil
|
||||
}
|
||||
|
||||
// contractMigrate migrates a contract.
|
||||
func (ic *interopContext) contractMigrate(v *vm.VM) error {
|
||||
newcontract, err := ic.createContractStateFromVM(v)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
contract := ic.bc.GetContractState(newcontract.ScriptHash())
|
||||
if contract == nil {
|
||||
contract = newcontract
|
||||
err := putContractStateIntoStore(ic.mem, contract)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if contract.HasStorage() {
|
||||
hash := getContextScriptHash(v, 0)
|
||||
siMap, err := ic.bc.GetStorageItems(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range siMap {
|
||||
v.IsConst = false
|
||||
_ = putStorageItemIntoStore(ic.mem, hash, []byte(k), v)
|
||||
}
|
||||
}
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(contract))
|
||||
return ic.contractDestroy(v)
|
||||
}
|
||||
|
||||
// assetCreate creates an asset.
|
||||
func (ic *interopContext) assetCreate(v *vm.VM) error {
|
||||
if ic.trigger != 0x10 {
|
||||
return errors.New("can't create asset when not triggered by an application")
|
||||
}
|
||||
atype := transaction.AssetType(v.Estack().Pop().BigInt().Int64())
|
||||
switch atype {
|
||||
case transaction.Currency, transaction.Share, transaction.Invoice, transaction.Token:
|
||||
// ok
|
||||
default:
|
||||
return fmt.Errorf("wrong asset type: %x", atype)
|
||||
}
|
||||
name := string(v.Estack().Pop().Bytes())
|
||||
if len(name) > MaxAssetNameLen {
|
||||
return errors.New("too big name")
|
||||
}
|
||||
amount := util.Fixed8(v.Estack().Pop().BigInt().Int64())
|
||||
if amount == util.Fixed8(0) {
|
||||
return errors.New("asset amount can't be zero")
|
||||
}
|
||||
if amount < -util.Satoshi() {
|
||||
return errors.New("asset amount can't be negative (except special -Satoshi value")
|
||||
}
|
||||
if atype == transaction.Invoice && amount != -util.Satoshi() {
|
||||
return errors.New("invoice assets can only have -Satoshi amount")
|
||||
}
|
||||
precision := byte(v.Estack().Pop().BigInt().Int64())
|
||||
if precision > MaxAssetPrecision {
|
||||
return fmt.Errorf("can't have asset precision of more than %d", MaxAssetPrecision)
|
||||
}
|
||||
if atype == transaction.Share && precision != 0 {
|
||||
return errors.New("share assets can only have zero precision")
|
||||
}
|
||||
if amount != -util.Satoshi() && (int64(amount)%int64(math.Pow10(int(MaxAssetPrecision-precision))) != 0) {
|
||||
return errors.New("given asset amount has fractional component")
|
||||
}
|
||||
owner := &keys.PublicKey{}
|
||||
err := owner.DecodeBytes(v.Estack().Pop().Bytes())
|
||||
if err != nil {
|
||||
return gherr.Wrap(err, "failed to get owner key")
|
||||
}
|
||||
if owner.IsInfinity() {
|
||||
return errors.New("can't have infinity as an owner key")
|
||||
}
|
||||
witnessOk, err := ic.checkKeyedWitness(owner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !witnessOk {
|
||||
return errors.New("witness check didn't succeed")
|
||||
}
|
||||
admin, err := util.Uint160DecodeBytes(v.Estack().Pop().Bytes())
|
||||
if err != nil {
|
||||
return gherr.Wrap(err, "failed to get admin")
|
||||
}
|
||||
issuer, err := util.Uint160DecodeBytes(v.Estack().Pop().Bytes())
|
||||
if err != nil {
|
||||
return gherr.Wrap(err, "failed to get issuer")
|
||||
}
|
||||
asset := &AssetState{
|
||||
ID: ic.tx.Hash(),
|
||||
AssetType: atype,
|
||||
Name: name,
|
||||
Amount: amount,
|
||||
Precision: precision,
|
||||
Owner: owner,
|
||||
Admin: admin,
|
||||
Issuer: issuer,
|
||||
Expiration: ic.bc.BlockHeight() + DefaultAssetLifetime,
|
||||
}
|
||||
err = putAssetStateIntoStore(ic.mem, asset)
|
||||
if err != nil {
|
||||
return gherr.Wrap(err, "failed to store asset")
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(asset))
|
||||
return nil
|
||||
}
|
||||
|
||||
// assetGetAdmin returns asset admin.
|
||||
func (ic *interopContext) assetGetAdmin(v *vm.VM) error {
|
||||
asInterface := v.Estack().Pop().Value()
|
||||
as, ok := asInterface.(*AssetState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an asset state", as)
|
||||
}
|
||||
v.Estack().PushVal(as.Admin.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// assetGetAmount returns the overall amount of asset available.
|
||||
func (ic *interopContext) assetGetAmount(v *vm.VM) error {
|
||||
asInterface := v.Estack().Pop().Value()
|
||||
as, ok := asInterface.(*AssetState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an asset state", as)
|
||||
}
|
||||
v.Estack().PushVal(int64(as.Amount))
|
||||
return nil
|
||||
}
|
||||
|
||||
// assetGetAssetId returns the id of an asset.
|
||||
func (ic *interopContext) assetGetAssetID(v *vm.VM) error {
|
||||
asInterface := v.Estack().Pop().Value()
|
||||
as, ok := asInterface.(*AssetState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an asset state", as)
|
||||
}
|
||||
v.Estack().PushVal(as.ID.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// assetGetAssetType returns type of an asset.
|
||||
func (ic *interopContext) assetGetAssetType(v *vm.VM) error {
|
||||
asInterface := v.Estack().Pop().Value()
|
||||
as, ok := asInterface.(*AssetState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an asset state", as)
|
||||
}
|
||||
v.Estack().PushVal(int(as.AssetType))
|
||||
return nil
|
||||
}
|
||||
|
||||
// assetGetAvailable returns available (not yet issued) amount of asset.
|
||||
func (ic *interopContext) assetGetAvailable(v *vm.VM) error {
|
||||
asInterface := v.Estack().Pop().Value()
|
||||
as, ok := asInterface.(*AssetState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an asset state", as)
|
||||
}
|
||||
v.Estack().PushVal(int(as.Available))
|
||||
return nil
|
||||
}
|
||||
|
||||
// assetGetIssuer returns issuer of an asset.
|
||||
func (ic *interopContext) assetGetIssuer(v *vm.VM) error {
|
||||
asInterface := v.Estack().Pop().Value()
|
||||
as, ok := asInterface.(*AssetState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an asset state", as)
|
||||
}
|
||||
v.Estack().PushVal(as.Issuer.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// assetGetOwner returns owner of an asset.
|
||||
func (ic *interopContext) assetGetOwner(v *vm.VM) error {
|
||||
asInterface := v.Estack().Pop().Value()
|
||||
as, ok := asInterface.(*AssetState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an asset state", as)
|
||||
}
|
||||
v.Estack().PushVal(as.Owner.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// assetGetPrecision returns precision used to measure this asset.
|
||||
func (ic *interopContext) assetGetPrecision(v *vm.VM) error {
|
||||
asInterface := v.Estack().Pop().Value()
|
||||
as, ok := asInterface.(*AssetState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an asset state", as)
|
||||
}
|
||||
v.Estack().PushVal(int(as.Precision))
|
||||
return nil
|
||||
}
|
||||
|
||||
// assetRenew updates asset expiration date.
|
||||
func (ic *interopContext) assetRenew(v *vm.VM) error {
|
||||
if ic.trigger != 0x10 {
|
||||
return errors.New("can't create asset when not triggered by an application")
|
||||
}
|
||||
asInterface := v.Estack().Pop().Value()
|
||||
as, ok := asInterface.(*AssetState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not an asset state", as)
|
||||
}
|
||||
years := byte(v.Estack().Pop().BigInt().Int64())
|
||||
// Not sure why C# code regets an asset from the Store, but we also do it.
|
||||
asset := ic.bc.GetAssetState(as.ID)
|
||||
if asset == nil {
|
||||
return errors.New("can't renew non-existent asset")
|
||||
}
|
||||
if asset.Expiration < ic.bc.BlockHeight()+1 {
|
||||
asset.Expiration = ic.bc.BlockHeight() + 1
|
||||
}
|
||||
expiration := uint64(asset.Expiration) + uint64(years)*BlocksPerYear
|
||||
if expiration > math.MaxUint32 {
|
||||
expiration = math.MaxUint32
|
||||
}
|
||||
asset.Expiration = uint32(expiration)
|
||||
err := putAssetStateIntoStore(ic.mem, asset)
|
||||
if err != nil {
|
||||
return gherr.Wrap(err, "failed to store asset")
|
||||
}
|
||||
v.Estack().PushVal(expiration)
|
||||
return nil
|
||||
}
|
583
pkg/core/interop_system.go
Normal file
583
pkg/core/interop_system.go
Normal file
|
@ -0,0 +1,583 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
gherr "github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxStorageKeyLen is the maximum length of a key for storage items.
|
||||
MaxStorageKeyLen = 1024
|
||||
)
|
||||
|
||||
// StorageContext contains storing script hash and read/write flag, it's used as
|
||||
// a context for storage manipulation functions.
|
||||
type StorageContext struct {
|
||||
ScriptHash util.Uint160
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
// getBlockHashFromElement converts given vm.Element to block hash using given
|
||||
// Blockchainer if needed. Interop functions accept both block numbers and
|
||||
// block hashes as parameters, thus this function is needed.
|
||||
func getBlockHashFromElement(bc Blockchainer, element *vm.Element) (util.Uint256, error) {
|
||||
var hash util.Uint256
|
||||
hashbytes := element.Bytes()
|
||||
if len(hashbytes) <= 5 {
|
||||
hashint := element.BigInt().Int64()
|
||||
if hashint < 0 || hashint > math.MaxUint32 {
|
||||
return hash, errors.New("bad block index")
|
||||
}
|
||||
hash = bc.GetHeaderHash(int(hashint))
|
||||
} else {
|
||||
return util.Uint256DecodeReverseBytes(hashbytes)
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
// bcGetBlock returns current block.
|
||||
func (ic *interopContext) bcGetBlock(v *vm.VM) error {
|
||||
hash, err := getBlockHashFromElement(ic.bc, v.Estack().Pop())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, err := ic.bc.GetBlock(hash)
|
||||
if err != nil {
|
||||
v.Estack().PushVal([]byte{})
|
||||
} else {
|
||||
v.Estack().PushVal(vm.NewInteropItem(block))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bcGetContract returns contract.
|
||||
func (ic *interopContext) bcGetContract(v *vm.VM) error {
|
||||
hashbytes := v.Estack().Pop().Bytes()
|
||||
hash, err := util.Uint160DecodeBytes(hashbytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cs := ic.bc.GetContractState(hash)
|
||||
if cs == nil {
|
||||
v.Estack().PushVal([]byte{})
|
||||
} else {
|
||||
v.Estack().PushVal(vm.NewInteropItem(cs))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bcGetHeader returns block header.
|
||||
func (ic *interopContext) bcGetHeader(v *vm.VM) error {
|
||||
hash, err := getBlockHashFromElement(ic.bc, v.Estack().Pop())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header, err := ic.bc.GetHeader(hash)
|
||||
if err != nil {
|
||||
v.Estack().PushVal([]byte{})
|
||||
} else {
|
||||
v.Estack().PushVal(vm.NewInteropItem(header))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bcGetHeight returns blockchain height.
|
||||
func (ic *interopContext) bcGetHeight(v *vm.VM) error {
|
||||
v.Estack().PushVal(ic.bc.BlockHeight())
|
||||
return nil
|
||||
}
|
||||
|
||||
// getTransactionAndHeight gets parameter from the vm evaluation stack and
|
||||
// returns transaction and its height if it's present in the blockchain.
|
||||
func getTransactionAndHeight(bc Blockchainer, v *vm.VM) (*transaction.Transaction, uint32, error) {
|
||||
hashbytes := v.Estack().Pop().Bytes()
|
||||
hash, err := util.Uint256DecodeReverseBytes(hashbytes)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return bc.GetTransaction(hash)
|
||||
}
|
||||
|
||||
// bcGetTransaction returns transaction.
|
||||
func (ic *interopContext) bcGetTransaction(v *vm.VM) error {
|
||||
tx, _, err := getTransactionAndHeight(ic.bc, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(tx))
|
||||
return nil
|
||||
}
|
||||
|
||||
// bcGetTransactionHeight returns transaction height.
|
||||
func (ic *interopContext) bcGetTransactionHeight(v *vm.VM) error {
|
||||
_, h, err := getTransactionAndHeight(ic.bc, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(h)
|
||||
return nil
|
||||
}
|
||||
|
||||
// popHeaderFromVM returns pointer to Header or error. It's main feature is
|
||||
// proper treatment of Block structure, because C# code implicitly assumes
|
||||
// that header APIs can also operate on blocks.
|
||||
func popHeaderFromVM(v *vm.VM) (*Header, error) {
|
||||
iface := v.Estack().Pop().Value()
|
||||
header, ok := iface.(*Header)
|
||||
if !ok {
|
||||
block, ok := iface.(*Block)
|
||||
if !ok {
|
||||
return nil, errors.New("value is not a header or block")
|
||||
}
|
||||
return block.Header(), nil
|
||||
}
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// headerGetIndex returns block index from the header.
|
||||
func (ic *interopContext) headerGetIndex(v *vm.VM) error {
|
||||
header, err := popHeaderFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(header.Index)
|
||||
return nil
|
||||
}
|
||||
|
||||
// headerGetHash returns header hash of the passed header.
|
||||
func (ic *interopContext) headerGetHash(v *vm.VM) error {
|
||||
header, err := popHeaderFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(header.Hash().BytesReverse())
|
||||
return nil
|
||||
}
|
||||
|
||||
// headerGetPrevHash returns previous header hash of the passed header.
|
||||
func (ic *interopContext) headerGetPrevHash(v *vm.VM) error {
|
||||
header, err := popHeaderFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(header.PrevHash.BytesReverse())
|
||||
return nil
|
||||
}
|
||||
|
||||
// headerGetTimestamp returns timestamp of the passed header.
|
||||
func (ic *interopContext) headerGetTimestamp(v *vm.VM) error {
|
||||
header, err := popHeaderFromVM(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(header.Timestamp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// blockGetTransactionCount returns transactions count in the given block.
|
||||
func (ic *interopContext) blockGetTransactionCount(v *vm.VM) error {
|
||||
blockInterface := v.Estack().Pop().Value()
|
||||
block, ok := blockInterface.(*Block)
|
||||
if !ok {
|
||||
return errors.New("value is not a block")
|
||||
}
|
||||
v.Estack().PushVal(len(block.Transactions))
|
||||
return nil
|
||||
}
|
||||
|
||||
// blockGetTransactions returns transactions from the given block.
|
||||
func (ic *interopContext) blockGetTransactions(v *vm.VM) error {
|
||||
blockInterface := v.Estack().Pop().Value()
|
||||
block, ok := blockInterface.(*Block)
|
||||
if !ok {
|
||||
return errors.New("value is not a block")
|
||||
}
|
||||
if len(block.Transactions) > vm.MaxArraySize {
|
||||
return errors.New("too many transactions")
|
||||
}
|
||||
txes := make([]vm.StackItem, 0, len(block.Transactions))
|
||||
for _, tx := range block.Transactions {
|
||||
txes = append(txes, vm.NewInteropItem(tx))
|
||||
}
|
||||
v.Estack().PushVal(txes)
|
||||
return nil
|
||||
}
|
||||
|
||||
// blockGetTransaction returns transaction with the given number from the given
|
||||
// block.
|
||||
func (ic *interopContext) blockGetTransaction(v *vm.VM) error {
|
||||
blockInterface := v.Estack().Pop().Value()
|
||||
block, ok := blockInterface.(*Block)
|
||||
if !ok {
|
||||
return errors.New("value is not a block")
|
||||
}
|
||||
index := v.Estack().Pop().BigInt().Int64()
|
||||
if index < 0 || index >= int64(len(block.Transactions)) {
|
||||
return errors.New("wrong transaction index")
|
||||
}
|
||||
tx := block.Transactions[index]
|
||||
v.Estack().PushVal(vm.NewInteropItem(tx))
|
||||
return nil
|
||||
}
|
||||
|
||||
// txGetHash returns transaction's hash.
|
||||
func (ic *interopContext) txGetHash(v *vm.VM) error {
|
||||
txInterface := v.Estack().Pop().Value()
|
||||
tx, ok := txInterface.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return errors.New("value is not a transaction")
|
||||
}
|
||||
v.Estack().PushVal(tx.Hash().BytesReverse())
|
||||
return nil
|
||||
}
|
||||
|
||||
// engineGetScriptContainer returns transaction that contains the script being
|
||||
// run.
|
||||
func (ic *interopContext) engineGetScriptContainer(v *vm.VM) error {
|
||||
v.Estack().PushVal(vm.NewInteropItem(ic.tx))
|
||||
return nil
|
||||
}
|
||||
|
||||
// pushContextScriptHash returns script hash of the invocation stack element
|
||||
// number n.
|
||||
func getContextScriptHash(v *vm.VM, n int) util.Uint160 {
|
||||
ctxIface := v.Istack().Peek(n).Value()
|
||||
ctx := ctxIface.(*vm.Context)
|
||||
return hash.Hash160(ctx.Program())
|
||||
}
|
||||
|
||||
// pushContextScriptHash pushes to evaluation stack the script hash of the
|
||||
// invocation stack element number n.
|
||||
func pushContextScriptHash(v *vm.VM, n int) error {
|
||||
h := getContextScriptHash(v, n)
|
||||
v.Estack().PushVal(h.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// engineGetExecutingScriptHash returns executing script hash.
|
||||
func (ic *interopContext) engineGetExecutingScriptHash(v *vm.VM) error {
|
||||
return pushContextScriptHash(v, 0)
|
||||
}
|
||||
|
||||
// engineGetCallingScriptHash returns calling script hash.
|
||||
func (ic *interopContext) engineGetCallingScriptHash(v *vm.VM) error {
|
||||
return pushContextScriptHash(v, 1)
|
||||
}
|
||||
|
||||
// engineGetEntryScriptHash returns entry script hash.
|
||||
func (ic *interopContext) engineGetEntryScriptHash(v *vm.VM) error {
|
||||
return pushContextScriptHash(v, v.Istack().Len()-1)
|
||||
}
|
||||
|
||||
// runtimePlatform returns the name of the platform.
|
||||
func (ic *interopContext) runtimePlatform(v *vm.VM) error {
|
||||
v.Estack().PushVal([]byte("NEO"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeGetTrigger returns the script trigger.
|
||||
func (ic *interopContext) runtimeGetTrigger(v *vm.VM) error {
|
||||
v.Estack().PushVal(ic.trigger)
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkHashedWitness checks given hash against current list of script hashes
|
||||
// for verifying in the interop context.
|
||||
func (ic *interopContext) checkHashedWitness(hash util.Uint160) (bool, error) {
|
||||
hashes, err := ic.bc.GetScriptHashesForVerifying(ic.tx)
|
||||
if err != nil {
|
||||
return false, gherr.Wrap(err, "failed to get script hashes")
|
||||
}
|
||||
for _, v := range hashes {
|
||||
if hash.Equals(v) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// checkKeyedWitness checks hash of signature check contract with a given public
|
||||
// key against current list of script hashes for verifying in the interop context.
|
||||
func (ic *interopContext) checkKeyedWitness(key *keys.PublicKey) (bool, error) {
|
||||
script, err := smartcontract.CreateSignatureRedeemScript(key)
|
||||
if err != nil {
|
||||
return false, gherr.Wrap(err, "failed to create signature script for a key")
|
||||
}
|
||||
return ic.checkHashedWitness(hash.Hash160(script))
|
||||
}
|
||||
|
||||
// runtimeCheckWitness should check witnesses.
|
||||
func (ic *interopContext) runtimeCheckWitness(v *vm.VM) error {
|
||||
var res bool
|
||||
var err error
|
||||
|
||||
hashOrKey := v.Estack().Pop().Bytes()
|
||||
hash, err := util.Uint160DecodeBytes(hashOrKey)
|
||||
if err != nil {
|
||||
key := &keys.PublicKey{}
|
||||
err = key.DecodeBytes(hashOrKey)
|
||||
if err != nil {
|
||||
return errors.New("parameter given is neither a key nor a hash")
|
||||
}
|
||||
res, err = ic.checkKeyedWitness(key)
|
||||
} else {
|
||||
res, err = ic.checkHashedWitness(hash)
|
||||
}
|
||||
if err != nil {
|
||||
return gherr.Wrap(err, "failed to check")
|
||||
}
|
||||
v.Estack().PushVal(res)
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeNotify should pass stack item to the notify plugin to handle it, but
|
||||
// in neo-go the only meaningful thing to do here is to log.
|
||||
func (ic *interopContext) runtimeNotify(v *vm.VM) error {
|
||||
msg := fmt.Sprintf("%q", v.Estack().Pop().Bytes())
|
||||
log.Infof("script %s notifies: %s", getContextScriptHash(v, 0), msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeLog log the message passed.
|
||||
func (ic *interopContext) runtimeLog(v *vm.VM) error {
|
||||
msg := fmt.Sprintf("%q", v.Estack().Pop().Bytes())
|
||||
log.Infof("script %s logs: %s", getContextScriptHash(v, 0), msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeGetTime returns timestamp of the block being verified, or the latest
|
||||
// one in the blockchain if no block is given to interopContext.
|
||||
func (ic *interopContext) runtimeGetTime(v *vm.VM) error {
|
||||
var header *Header
|
||||
if ic.block == nil {
|
||||
var err error
|
||||
header, err = ic.bc.GetHeader(ic.bc.CurrentBlockHash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
header = ic.block.Header()
|
||||
}
|
||||
v.Estack().PushVal(header.Timestamp)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
// runtimeSerialize should serialize given stack item.
|
||||
func (ic *interopContext) runtimeSerialize(v *vm.VM) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
// runtimeDeserialize should deserialize given stack item.
|
||||
func (ic *interopContext) runtimeDeserialize(v *vm.VM) error {
|
||||
panic("TODO")
|
||||
}
|
||||
*/
|
||||
func (ic *interopContext) checkStorageContext(stc *StorageContext) error {
|
||||
contract := ic.bc.GetContractState(stc.ScriptHash)
|
||||
if contract == nil {
|
||||
return errors.New("no contract found")
|
||||
}
|
||||
if !contract.HasStorage() {
|
||||
return errors.New("contract can't have storage")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// storageDelete deletes stored key-value pair.
|
||||
func (ic *interopContext) storageDelete(v *vm.VM) error {
|
||||
if ic.trigger != 0x10 && ic.trigger != 0x11 {
|
||||
return errors.New("can't delete when the trigger is not application")
|
||||
}
|
||||
stcInterface := v.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*StorageContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||
}
|
||||
if stc.ReadOnly {
|
||||
return errors.New("StorageContext is read only")
|
||||
}
|
||||
err := ic.checkStorageContext(stc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := v.Estack().Pop().Bytes()
|
||||
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
|
||||
if si == nil {
|
||||
si = ic.bc.GetStorageItem(stc.ScriptHash, key)
|
||||
}
|
||||
if si != nil && si.IsConst {
|
||||
return errors.New("storage item is constant")
|
||||
}
|
||||
return deleteStorageItemInStore(ic.mem, stc.ScriptHash, key)
|
||||
}
|
||||
|
||||
// storageGet returns stored key-value pair.
|
||||
func (ic *interopContext) storageGet(v *vm.VM) error {
|
||||
stcInterface := v.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*StorageContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||
}
|
||||
err := ic.checkStorageContext(stc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := v.Estack().Pop().Bytes()
|
||||
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
|
||||
if si == nil {
|
||||
si = ic.bc.GetStorageItem(stc.ScriptHash, key)
|
||||
}
|
||||
if si != nil && si.Value != nil {
|
||||
v.Estack().PushVal(si.Value)
|
||||
} else {
|
||||
v.Estack().PushVal([]byte{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// storageGetContext returns storage context (scripthash).
|
||||
func (ic *interopContext) storageGetContext(v *vm.VM) error {
|
||||
sc := &StorageContext{
|
||||
ScriptHash: getContextScriptHash(v, 0),
|
||||
ReadOnly: false,
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(sc))
|
||||
return nil
|
||||
}
|
||||
|
||||
// storageGetReadOnlyContext returns read-only context (scripthash).
|
||||
func (ic *interopContext) storageGetReadOnlyContext(v *vm.VM) error {
|
||||
sc := &StorageContext{
|
||||
ScriptHash: getContextScriptHash(v, 0),
|
||||
ReadOnly: true,
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(sc))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ic *interopContext) putWithContextAndFlags(stc *StorageContext, key []byte, value []byte, isConst bool) error {
|
||||
if ic.trigger != 0x10 && ic.trigger != 0x11 {
|
||||
return errors.New("can't delete when the trigger is not application")
|
||||
}
|
||||
if len(key) > MaxStorageKeyLen {
|
||||
return errors.New("key is too big")
|
||||
}
|
||||
if stc.ReadOnly {
|
||||
return errors.New("StorageContext is read only")
|
||||
}
|
||||
err := ic.checkStorageContext(stc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
si := getStorageItemFromStore(ic.mem, stc.ScriptHash, key)
|
||||
if si == nil {
|
||||
si = ic.bc.GetStorageItem(stc.ScriptHash, key)
|
||||
if si == nil {
|
||||
si = &StorageItem{}
|
||||
}
|
||||
}
|
||||
if si.IsConst {
|
||||
return errors.New("storage item exists and is read-only")
|
||||
}
|
||||
si.Value = value
|
||||
si.IsConst = isConst
|
||||
return putStorageItemIntoStore(ic.mem, stc.ScriptHash, key, si)
|
||||
}
|
||||
|
||||
// storagePutInternal is a unified implementation of storagePut and storagePutEx.
|
||||
func (ic *interopContext) storagePutInternal(v *vm.VM, getFlag bool) error {
|
||||
stcInterface := v.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*StorageContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||
}
|
||||
key := v.Estack().Pop().Bytes()
|
||||
value := v.Estack().Pop().Bytes()
|
||||
var flag int
|
||||
if getFlag {
|
||||
flag = int(v.Estack().Pop().BigInt().Int64())
|
||||
}
|
||||
return ic.putWithContextAndFlags(stc, key, value, flag == 1)
|
||||
}
|
||||
|
||||
// storagePut puts key-value pair into the storage.
|
||||
func (ic *interopContext) storagePut(v *vm.VM) error {
|
||||
return ic.storagePutInternal(v, false)
|
||||
}
|
||||
|
||||
// storagePutEx puts key-value pair with given flags into the storage.
|
||||
func (ic *interopContext) storagePutEx(v *vm.VM) error {
|
||||
return ic.storagePutInternal(v, true)
|
||||
}
|
||||
|
||||
// storageContextAsReadOnly sets given context to read-only mode.
|
||||
func (ic *interopContext) storageContextAsReadOnly(v *vm.VM) error {
|
||||
stcInterface := v.Estack().Pop().Value()
|
||||
stc, ok := stcInterface.(*StorageContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a StorageContext", stcInterface)
|
||||
}
|
||||
if !stc.ReadOnly {
|
||||
stx := &StorageContext{
|
||||
ScriptHash: stc.ScriptHash,
|
||||
ReadOnly: true,
|
||||
}
|
||||
stc = stx
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(stc))
|
||||
return nil
|
||||
}
|
||||
|
||||
// contractDestroy destroys a contract.
|
||||
func (ic *interopContext) contractDestroy(v *vm.VM) error {
|
||||
if ic.trigger != 0x10 {
|
||||
return errors.New("can't destroy contract when not triggered by application")
|
||||
}
|
||||
hash := getContextScriptHash(v, 0)
|
||||
cs := ic.bc.GetContractState(hash)
|
||||
if cs == nil {
|
||||
return nil
|
||||
}
|
||||
err := deleteContractStateInStore(ic.mem, hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cs.HasStorage() {
|
||||
siMap, err := ic.bc.GetStorageItems(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k := range siMap {
|
||||
_ = deleteStorageItemInStore(ic.mem, hash, []byte(k))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// contractGetStorageContext retrieves StorageContext of a contract.
|
||||
func (ic *interopContext) contractGetStorageContext(v *vm.VM) error {
|
||||
csInterface := v.Estack().Pop().Value()
|
||||
cs, ok := csInterface.(*ContractState)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a contract state", cs)
|
||||
}
|
||||
if getContractStateFromStore(ic.mem, cs.ScriptHash()) == nil {
|
||||
return fmt.Errorf("contract was not created in this transaction")
|
||||
}
|
||||
stc := &StorageContext{
|
||||
ScriptHash: cs.ScriptHash(),
|
||||
}
|
||||
v.Estack().PushVal(vm.NewInteropItem(stc))
|
||||
return nil
|
||||
}
|
218
pkg/core/interops.go
Normal file
218
pkg/core/interops.go
Normal file
|
@ -0,0 +1,218 @@
|
|||
package core
|
||||
|
||||
/*
|
||||
Interops are designed to run under VM's execute() panic protection, so it's OK
|
||||
for them to do things like
|
||||
smth := v.Estack().Pop().Bytes()
|
||||
even though technically Pop() can return a nil pointer.
|
||||
*/
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
type interopContext struct {
|
||||
bc Blockchainer
|
||||
trigger byte
|
||||
block *Block
|
||||
tx *transaction.Transaction
|
||||
mem *storage.MemoryStore
|
||||
}
|
||||
|
||||
func newInteropContext(trigger byte, bc Blockchainer, block *Block, tx *transaction.Transaction) *interopContext {
|
||||
mem := storage.NewMemoryStore()
|
||||
return &interopContext{bc, trigger, block, tx, mem}
|
||||
}
|
||||
|
||||
// All lists are sorted, keep 'em this way, please.
|
||||
|
||||
// getSystemInteropMap returns interop mappings for System namespace.
|
||||
func (ic *interopContext) getSystemInteropMap() map[string]vm.InteropFuncPrice {
|
||||
return map[string]vm.InteropFuncPrice{
|
||||
"System.Block.GetTransaction": {Func: ic.blockGetTransaction, Price: 1},
|
||||
"System.Block.GetTransactionCount": {Func: ic.blockGetTransactionCount, Price: 1},
|
||||
"System.Block.GetTransactions": {Func: ic.blockGetTransactions, Price: 1},
|
||||
"System.Blockchain.GetBlock": {Func: ic.bcGetBlock, Price: 200},
|
||||
"System.Blockchain.GetContract": {Func: ic.bcGetContract, Price: 100},
|
||||
"System.Blockchain.GetHeader": {Func: ic.bcGetHeader, Price: 100},
|
||||
"System.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1},
|
||||
"System.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 200},
|
||||
"System.Blockchain.GetTransactionHeight": {Func: ic.bcGetTransactionHeight, Price: 100},
|
||||
"System.Contract.Destroy": {Func: ic.contractDestroy, Price: 1},
|
||||
"System.Contract.GetStorageContext": {Func: ic.contractGetStorageContext, Price: 1},
|
||||
"System.ExecutionEngine.GetCallingScriptHash": {Func: ic.engineGetCallingScriptHash, Price: 1},
|
||||
"System.ExecutionEngine.GetEntryScriptHash": {Func: ic.engineGetEntryScriptHash, Price: 1},
|
||||
"System.ExecutionEngine.GetExecutingScriptHash": {Func: ic.engineGetExecutingScriptHash, Price: 1},
|
||||
"System.ExecutionEngine.GetScriptContainer": {Func: ic.engineGetScriptContainer, Price: 1},
|
||||
"System.Header.GetHash": {Func: ic.headerGetHash, Price: 1},
|
||||
"System.Header.GetIndex": {Func: ic.headerGetIndex, Price: 1},
|
||||
"System.Header.GetPrevHash": {Func: ic.headerGetPrevHash, Price: 1},
|
||||
"System.Header.GetTimestamp": {Func: ic.headerGetTimestamp, Price: 1},
|
||||
"System.Runtime.CheckWitness": {Func: ic.runtimeCheckWitness, Price: 200},
|
||||
"System.Runtime.GetTime": {Func: ic.runtimeGetTime, Price: 1},
|
||||
"System.Runtime.GetTrigger": {Func: ic.runtimeGetTrigger, Price: 1},
|
||||
"System.Runtime.Log": {Func: ic.runtimeLog, Price: 1},
|
||||
"System.Runtime.Notify": {Func: ic.runtimeNotify, Price: 1},
|
||||
"System.Runtime.Platform": {Func: ic.runtimePlatform, Price: 1},
|
||||
"System.Storage.Delete": {Func: ic.storageDelete, Price: 100},
|
||||
"System.Storage.Get": {Func: ic.storageGet, Price: 100},
|
||||
"System.Storage.GetContext": {Func: ic.storageGetContext, Price: 1},
|
||||
"System.Storage.GetReadOnlyContext": {Func: ic.storageGetReadOnlyContext, Price: 1},
|
||||
"System.Storage.Put": {Func: ic.storagePut, Price: 0}, // These don't have static price in C# code.
|
||||
"System.Storage.PutEx": {Func: ic.storagePutEx, Price: 0},
|
||||
"System.StorageContext.AsReadOnly": {Func: ic.storageContextAsReadOnly, Price: 1},
|
||||
"System.Transaction.GetHash": {Func: ic.txGetHash, Price: 1},
|
||||
// "System.Runtime.Deserialize": {Func: ic.runtimeDeserialize, Price: 1},
|
||||
// "System.Runtime.Serialize": {Func: ic.runtimeSerialize, Price: 1},
|
||||
}
|
||||
}
|
||||
|
||||
// getSystemInteropMap returns interop mappings for Neo and (legacy) AntShares namespaces.
|
||||
func (ic *interopContext) getNeoInteropMap() map[string]vm.InteropFuncPrice {
|
||||
return map[string]vm.InteropFuncPrice{
|
||||
"Neo.Account.GetBalance": {Func: ic.accountGetBalance, Price: 1},
|
||||
"Neo.Account.GetScriptHash": {Func: ic.accountGetScriptHash, Price: 1},
|
||||
"Neo.Account.GetVotes": {Func: ic.accountGetVotes, Price: 1},
|
||||
"Neo.Account.IsStandard": {Func: ic.accountIsStandard, Price: 100},
|
||||
"Neo.Asset.Create": {Func: ic.assetCreate, Price: 0},
|
||||
"Neo.Asset.GetAdmin": {Func: ic.assetGetAdmin, Price: 1},
|
||||
"Neo.Asset.GetAmount": {Func: ic.assetGetAmount, Price: 1},
|
||||
"Neo.Asset.GetAssetId": {Func: ic.assetGetAssetID, Price: 1},
|
||||
"Neo.Asset.GetAssetType": {Func: ic.assetGetAssetType, Price: 1},
|
||||
"Neo.Asset.GetAvailable": {Func: ic.assetGetAvailable, Price: 1},
|
||||
"Neo.Asset.GetIssuer": {Func: ic.assetGetIssuer, Price: 1},
|
||||
"Neo.Asset.GetOwner": {Func: ic.assetGetOwner, Price: 1},
|
||||
"Neo.Asset.GetPrecision": {Func: ic.assetGetPrecision, Price: 1},
|
||||
"Neo.Asset.Renew": {Func: ic.assetRenew, Price: 0},
|
||||
"Neo.Attribute.GetData": {Func: ic.attrGetData, Price: 1},
|
||||
"Neo.Attribute.GetUsage": {Func: ic.attrGetUsage, Price: 1},
|
||||
"Neo.Block.GetTransaction": {Func: ic.blockGetTransaction, Price: 1},
|
||||
"Neo.Block.GetTransactionCount": {Func: ic.blockGetTransactionCount, Price: 1},
|
||||
"Neo.Block.GetTransactions": {Func: ic.blockGetTransactions, Price: 1},
|
||||
"Neo.Blockchain.GetAccount": {Func: ic.bcGetAccount, Price: 100},
|
||||
"Neo.Blockchain.GetAsset": {Func: ic.bcGetAsset, Price: 100},
|
||||
"Neo.Blockchain.GetBlock": {Func: ic.bcGetBlock, Price: 200},
|
||||
"Neo.Blockchain.GetContract": {Func: ic.bcGetContract, Price: 100},
|
||||
"Neo.Blockchain.GetHeader": {Func: ic.bcGetHeader, Price: 100},
|
||||
"Neo.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1},
|
||||
"Neo.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100},
|
||||
"Neo.Blockchain.GetTransactionHeight": {Func: ic.bcGetTransactionHeight, Price: 100},
|
||||
"Neo.Contract.Create": {Func: ic.contractCreate, Price: 0},
|
||||
"Neo.Contract.Destroy": {Func: ic.contractDestroy, Price: 1},
|
||||
"Neo.Contract.GetScript": {Func: ic.contractGetScript, Price: 1},
|
||||
"Neo.Contract.GetStorageContext": {Func: ic.contractGetStorageContext, Price: 1},
|
||||
"Neo.Contract.IsPayable": {Func: ic.contractIsPayable, Price: 1},
|
||||
"Neo.Contract.Migrate": {Func: ic.contractMigrate, Price: 0},
|
||||
"Neo.Header.GetConsensusData": {Func: ic.headerGetConsensusData, Price: 1},
|
||||
"Neo.Header.GetHash": {Func: ic.headerGetHash, Price: 1},
|
||||
"Neo.Header.GetIndex": {Func: ic.headerGetIndex, Price: 1},
|
||||
"Neo.Header.GetMerkleRoot": {Func: ic.headerGetMerkleRoot, Price: 1},
|
||||
"Neo.Header.GetNextConsensus": {Func: ic.headerGetNextConsensus, Price: 1},
|
||||
"Neo.Header.GetPrevHash": {Func: ic.headerGetPrevHash, Price: 1},
|
||||
"Neo.Header.GetTimestamp": {Func: ic.headerGetTimestamp, Price: 1},
|
||||
"Neo.Header.GetVersion": {Func: ic.headerGetVersion, Price: 1},
|
||||
"Neo.Input.GetHash": {Func: ic.inputGetHash, Price: 1},
|
||||
"Neo.Input.GetIndex": {Func: ic.inputGetIndex, Price: 1},
|
||||
"Neo.Output.GetAssetId": {Func: ic.outputGetAssetID, Price: 1},
|
||||
"Neo.Output.GetScriptHash": {Func: ic.outputGetScriptHash, Price: 1},
|
||||
"Neo.Output.GetValue": {Func: ic.outputGetValue, Price: 1},
|
||||
"Neo.Runtime.CheckWitness": {Func: ic.runtimeCheckWitness, Price: 200},
|
||||
"Neo.Runtime.GetTime": {Func: ic.runtimeGetTime, Price: 1},
|
||||
"Neo.Runtime.GetTrigger": {Func: ic.runtimeGetTrigger, Price: 1},
|
||||
"Neo.Runtime.Log": {Func: ic.runtimeLog, Price: 1},
|
||||
"Neo.Runtime.Notify": {Func: ic.runtimeNotify, Price: 1},
|
||||
"Neo.Storage.Delete": {Func: ic.storageDelete, Price: 100},
|
||||
"Neo.Storage.Get": {Func: ic.storageGet, Price: 100},
|
||||
"Neo.Storage.GetContext": {Func: ic.storageGetContext, Price: 1},
|
||||
"Neo.Storage.GetReadOnlyContext": {Func: ic.storageGetReadOnlyContext, Price: 1},
|
||||
"Neo.Storage.Put": {Func: ic.storagePut, Price: 0},
|
||||
"Neo.StorageContext.AsReadOnly": {Func: ic.storageContextAsReadOnly, Price: 1},
|
||||
"Neo.Transaction.GetAttributes": {Func: ic.txGetAttributes, Price: 1},
|
||||
"Neo.Transaction.GetHash": {Func: ic.txGetHash, Price: 1},
|
||||
"Neo.Transaction.GetInputs": {Func: ic.txGetInputs, Price: 1},
|
||||
"Neo.Transaction.GetOutputs": {Func: ic.txGetOutputs, Price: 1},
|
||||
"Neo.Transaction.GetReferences": {Func: ic.txGetReferences, Price: 200},
|
||||
"Neo.Transaction.GetType": {Func: ic.txGetType, Price: 1},
|
||||
"Neo.Transaction.GetUnspentCoins": {Func: ic.txGetUnspentCoins, Price: 200},
|
||||
"Neo.Transaction.GetWitnesses": {Func: ic.txGetWitnesses, Price: 200},
|
||||
// "Neo.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200},
|
||||
// "Neo.Enumerator.Concat": {Func: ic.enumeratorConcat, Price: 1},
|
||||
// "Neo.Enumerator.Create": {Func: ic.enumeratorCreate, Price: 1},
|
||||
// "Neo.Enumerator.Next": {Func: ic.enumeratorNext, Price: 1},
|
||||
// "Neo.Enumerator.Value": {Func: ic.enumeratorValue, Price: 1},
|
||||
// "Neo.InvocationTransaction.GetScript": {ic.invocationTx_GetScript, 1},
|
||||
// "Neo.Iterator.Concat": {Func: ic.iteratorConcat, Price: 1},
|
||||
// "Neo.Iterator.Create": {Func: ic.iteratorCreate, Price: 1},
|
||||
// "Neo.Iterator.Key": {Func: ic.iteratorKey, Price: 1},
|
||||
// "Neo.Iterator.Keys": {Func: ic.iteratorKeys, Price: 1},
|
||||
// "Neo.Iterator.Values": {Func: ic.iteratorValues, Price: 1},
|
||||
// "Neo.Runtime.Deserialize": {Func: ic.runtimeDeserialize, Price: 1},
|
||||
// "Neo.Runtime.Serialize": {Func: ic.runtimeSerialize, Price: 1},
|
||||
// "Neo.Storage.Find": {Func: ic.storageFind, Price: 1},
|
||||
// "Neo.Witness.GetVerificationScript": {Func: ic.witnessGetVerificationScript, Price: 100},
|
||||
|
||||
// Aliases.
|
||||
// "Neo.Iterator.Next": {Func: ic.enumeratorNext, Price: 1},
|
||||
// "Neo.Iterator.Value": {Func: ic.enumeratorValue, Price: 1},
|
||||
|
||||
// Old compatibility APIs.
|
||||
"AntShares.Account.GetBalance": {Func: ic.accountGetBalance, Price: 1},
|
||||
"AntShares.Account.GetScriptHash": {Func: ic.accountGetScriptHash, Price: 1},
|
||||
"AntShares.Account.GetVotes": {Func: ic.accountGetVotes, Price: 1},
|
||||
"AntShares.Asset.Create": {Func: ic.assetCreate, Price: 0},
|
||||
"AntShares.Asset.GetAdmin": {Func: ic.assetGetAdmin, Price: 1},
|
||||
"AntShares.Asset.GetAmount": {Func: ic.assetGetAmount, Price: 1},
|
||||
"AntShares.Asset.GetAssetId": {Func: ic.assetGetAssetID, Price: 1},
|
||||
"AntShares.Asset.GetAssetType": {Func: ic.assetGetAssetType, Price: 1},
|
||||
"AntShares.Asset.GetAvailable": {Func: ic.assetGetAvailable, Price: 1},
|
||||
"AntShares.Asset.GetIssuer": {Func: ic.assetGetIssuer, Price: 1},
|
||||
"AntShares.Asset.GetOwner": {Func: ic.assetGetOwner, Price: 1},
|
||||
"AntShares.Asset.GetPrecision": {Func: ic.assetGetPrecision, Price: 1},
|
||||
"AntShares.Asset.Renew": {Func: ic.assetRenew, Price: 0},
|
||||
"AntShares.Attribute.GetData": {Func: ic.attrGetData, Price: 1},
|
||||
"AntShares.Attribute.GetUsage": {Func: ic.attrGetUsage, Price: 1},
|
||||
"AntShares.Block.GetTransaction": {Func: ic.blockGetTransaction, Price: 1},
|
||||
"AntShares.Block.GetTransactionCount": {Func: ic.blockGetTransactionCount, Price: 1},
|
||||
"AntShares.Block.GetTransactions": {Func: ic.blockGetTransactions, Price: 1},
|
||||
"AntShares.Blockchain.GetAccount": {Func: ic.bcGetAccount, Price: 100},
|
||||
"AntShares.Blockchain.GetAsset": {Func: ic.bcGetAsset, Price: 100},
|
||||
"AntShares.Blockchain.GetBlock": {Func: ic.bcGetBlock, Price: 200},
|
||||
"AntShares.Blockchain.GetContract": {Func: ic.bcGetContract, Price: 100},
|
||||
"AntShares.Blockchain.GetHeader": {Func: ic.bcGetHeader, Price: 100},
|
||||
"AntShares.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1},
|
||||
"AntShares.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100},
|
||||
"AntShares.Contract.Create": {Func: ic.contractCreate, Price: 0},
|
||||
"AntShares.Contract.Destroy": {Func: ic.contractDestroy, Price: 1},
|
||||
"AntShares.Contract.GetScript": {Func: ic.contractGetScript, Price: 1},
|
||||
"AntShares.Contract.GetStorageContext": {Func: ic.contractGetStorageContext, Price: 1},
|
||||
"AntShares.Contract.Migrate": {Func: ic.contractMigrate, Price: 0},
|
||||
"AntShares.Header.GetConsensusData": {Func: ic.headerGetConsensusData, Price: 1},
|
||||
"AntShares.Header.GetHash": {Func: ic.headerGetHash, Price: 1},
|
||||
"AntShares.Header.GetMerkleRoot": {Func: ic.headerGetMerkleRoot, Price: 1},
|
||||
"AntShares.Header.GetNextConsensus": {Func: ic.headerGetNextConsensus, Price: 1},
|
||||
"AntShares.Header.GetPrevHash": {Func: ic.headerGetPrevHash, Price: 1},
|
||||
"AntShares.Header.GetTimestamp": {Func: ic.headerGetTimestamp, Price: 1},
|
||||
"AntShares.Header.GetVersion": {Func: ic.headerGetVersion, Price: 1},
|
||||
"AntShares.Input.GetHash": {Func: ic.inputGetHash, Price: 1},
|
||||
"AntShares.Input.GetIndex": {Func: ic.inputGetIndex, Price: 1},
|
||||
"AntShares.Output.GetAssetId": {Func: ic.outputGetAssetID, Price: 1},
|
||||
"AntShares.Output.GetScriptHash": {Func: ic.outputGetScriptHash, Price: 1},
|
||||
"AntShares.Output.GetValue": {Func: ic.outputGetValue, Price: 1},
|
||||
"AntShares.Runtime.CheckWitness": {Func: ic.runtimeCheckWitness, Price: 200},
|
||||
"AntShares.Runtime.Log": {Func: ic.runtimeLog, Price: 1},
|
||||
"AntShares.Runtime.Notify": {Func: ic.runtimeNotify, Price: 1},
|
||||
"AntShares.Storage.Delete": {Func: ic.storageDelete, Price: 100},
|
||||
"AntShares.Storage.Get": {Func: ic.storageGet, Price: 100},
|
||||
"AntShares.Storage.GetContext": {Func: ic.storageGetContext, Price: 1},
|
||||
"AntShares.Storage.Put": {Func: ic.storagePut, Price: 0},
|
||||
"AntShares.Transaction.GetAttributes": {Func: ic.txGetAttributes, Price: 1},
|
||||
"AntShares.Transaction.GetHash": {Func: ic.txGetHash, Price: 1},
|
||||
"AntShares.Transaction.GetInputs": {Func: ic.txGetInputs, Price: 1},
|
||||
"AntShares.Transaction.GetOutputs": {Func: ic.txGetOutputs, Price: 1},
|
||||
"AntShares.Transaction.GetReferences": {Func: ic.txGetReferences, Price: 200},
|
||||
"AntShares.Transaction.GetType": {Func: ic.txGetType, Price: 1},
|
||||
// "AntShares.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200},
|
||||
}
|
||||
}
|
|
@ -71,12 +71,26 @@ func (s *BoltDBStore) Get(key []byte) (val []byte, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Delete implements the Store interface.
|
||||
func (s *BoltDBStore) Delete(key []byte) error {
|
||||
return s.db.Update(func(tx *bbolt.Tx) error {
|
||||
b := tx.Bucket(Bucket)
|
||||
return b.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
// PutBatch implements the Store interface.
|
||||
func (s *BoltDBStore) PutBatch(batch Batch) error {
|
||||
return s.db.Batch(func(tx *bbolt.Tx) error {
|
||||
b := tx.Bucket(Bucket)
|
||||
for k, v := range batch.(*MemoryBatch).m {
|
||||
err := b.Put(*k, v)
|
||||
for k, v := range batch.(*MemoryBatch).mem {
|
||||
err := b.Put([]byte(k), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for k := range batch.(*MemoryBatch).del {
|
||||
err := b.Delete([]byte(k))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -3,83 +3,12 @@ package storage
|
|||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBoltDBBatch(t *testing.T) {
|
||||
boltDB := BoltDBStore{}
|
||||
want := &MemoryBatch{m: map[*[]byte][]byte{}}
|
||||
if got := boltDB.Batch(); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("BoltDB Batch() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoltDBBatch_Len(t *testing.T) {
|
||||
batch := &MemoryBatch{m: map[*[]byte][]byte{}}
|
||||
want := len(map[*[]byte][]byte{})
|
||||
assert.Equal(t, want, batch.Len())
|
||||
}
|
||||
|
||||
func TestBoltDBBatch_PutBatchAndGet(t *testing.T) {
|
||||
key := []byte("foo")
|
||||
keycopy := make([]byte, len(key))
|
||||
copy(keycopy, key)
|
||||
value := []byte("bar")
|
||||
valuecopy := make([]byte, len(value))
|
||||
copy(valuecopy, value)
|
||||
boltDBStore := openStore(t)
|
||||
batch := boltDBStore.Batch()
|
||||
|
||||
batch.Put(keycopy, valuecopy)
|
||||
copy(valuecopy, key)
|
||||
copy(keycopy, value)
|
||||
errPut := boltDBStore.PutBatch(batch)
|
||||
assert.Nil(t, errPut, "Error while PutBatch")
|
||||
|
||||
result, err := boltDBStore.Get(key)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, value, result)
|
||||
|
||||
require.NoError(t, boltDBStore.Close())
|
||||
}
|
||||
|
||||
func TestBoltDBBatch_PutAndGet(t *testing.T) {
|
||||
key := []byte("foo")
|
||||
value := []byte("bar")
|
||||
|
||||
boltDBStore := openStore(t)
|
||||
|
||||
errPut := boltDBStore.Put(key, value)
|
||||
assert.Nil(t, errPut, "Error while Put")
|
||||
|
||||
result, err := boltDBStore.Get(key)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, value, result)
|
||||
|
||||
require.NoError(t, boltDBStore.Close())
|
||||
}
|
||||
|
||||
func TestBoltDBStore_Seek(t *testing.T) {
|
||||
key := []byte("foo")
|
||||
value := []byte("bar")
|
||||
|
||||
boltDBStore := openStore(t)
|
||||
|
||||
errPut := boltDBStore.Put(key, value)
|
||||
assert.Nil(t, errPut, "Error while Put")
|
||||
|
||||
boltDBStore.Seek(key, func(k, v []byte) {
|
||||
assert.Equal(t, value, v)
|
||||
})
|
||||
|
||||
require.NoError(t, boltDBStore.Close())
|
||||
}
|
||||
|
||||
func openStore(t *testing.T) *BoltDBStore {
|
||||
func newBoltStoreForTesting(t *testing.T) Store {
|
||||
testFileName := "test_bolt_db"
|
||||
file, err := ioutil.TempFile("", testFileName)
|
||||
defer func() {
|
||||
|
|
|
@ -48,6 +48,11 @@ func (s *LevelDBStore) Get(key []byte) ([]byte, error) {
|
|||
return value, err
|
||||
}
|
||||
|
||||
// Delete implements the Store interface.
|
||||
func (s *LevelDBStore) Delete(key []byte) error {
|
||||
return s.db.Delete(key, nil)
|
||||
}
|
||||
|
||||
// PutBatch implements the Store interface.
|
||||
func (s *LevelDBStore) PutBatch(batch Batch) error {
|
||||
lvldbBatch := batch.(*leveldb.Batch)
|
||||
|
|
|
@ -1 +1,41 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type tempLevelDB struct {
|
||||
LevelDBStore
|
||||
dir string
|
||||
}
|
||||
|
||||
func (tldb *tempLevelDB) Close() error {
|
||||
err := tldb.LevelDBStore.Close()
|
||||
// Make test fail if failed to cleanup, even though technically it's
|
||||
// not a LevelDBStore problem.
|
||||
osErr := os.RemoveAll(tldb.dir)
|
||||
if osErr != nil {
|
||||
return osErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func newLevelDBForTesting(t *testing.T) Store {
|
||||
ldbDir, err := ioutil.TempDir(os.TempDir(), "testleveldb")
|
||||
require.Nil(t, err, "failed to setup temporary directory")
|
||||
|
||||
dbConfig := DBConfiguration{
|
||||
Type: "leveldb",
|
||||
LevelDBOptions: LevelDBOptions{
|
||||
DataDirectoryPath: ldbDir,
|
||||
},
|
||||
}
|
||||
newLevelStore, err := NewLevelDBStore(dbConfig.LevelDBOptions)
|
||||
require.Nil(t, err, "NewLevelDBStore error")
|
||||
tldb := &tempLevelDB{LevelDBStore: *newLevelStore, dir: ldbDir}
|
||||
return tldb
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
@ -11,31 +10,30 @@ import (
|
|||
type MemoryStore struct {
|
||||
mut sync.RWMutex
|
||||
mem map[string][]byte
|
||||
// A map, not a slice, to avoid duplicates.
|
||||
del map[string]bool
|
||||
}
|
||||
|
||||
// MemoryBatch a in-memory batch compatible with MemoryStore.
|
||||
type MemoryBatch struct {
|
||||
m map[*[]byte][]byte
|
||||
MemoryStore
|
||||
}
|
||||
|
||||
// Put implements the Batch interface.
|
||||
func (b *MemoryBatch) Put(k, v []byte) {
|
||||
vcopy := make([]byte, len(v))
|
||||
copy(vcopy, v)
|
||||
kcopy := make([]byte, len(k))
|
||||
copy(kcopy, k)
|
||||
b.m[&kcopy] = vcopy
|
||||
_ = b.MemoryStore.Put(k, v)
|
||||
}
|
||||
|
||||
// Len implements the Batch interface.
|
||||
func (b *MemoryBatch) Len() int {
|
||||
return len(b.m)
|
||||
// Delete implements Batch interface.
|
||||
func (b *MemoryBatch) Delete(k []byte) {
|
||||
_ = b.MemoryStore.Delete(k)
|
||||
}
|
||||
|
||||
// NewMemoryStore creates a new MemoryStore object.
|
||||
func NewMemoryStore() *MemoryStore {
|
||||
return &MemoryStore{
|
||||
mem: make(map[string][]byte),
|
||||
del: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,16 +41,42 @@ func NewMemoryStore() *MemoryStore {
|
|||
func (s *MemoryStore) Get(key []byte) ([]byte, error) {
|
||||
s.mut.RLock()
|
||||
defer s.mut.RUnlock()
|
||||
if val, ok := s.mem[makeKey(key)]; ok {
|
||||
if val, ok := s.mem[string(key)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return nil, ErrKeyNotFound
|
||||
}
|
||||
|
||||
// put puts a key-value pair into the store, it's supposed to be called
|
||||
// with mutex locked.
|
||||
func (s *MemoryStore) put(key string, value []byte) {
|
||||
s.mem[key] = value
|
||||
delete(s.del, key)
|
||||
}
|
||||
|
||||
// Put implements the Store interface. Never returns an error.
|
||||
func (s *MemoryStore) Put(key, value []byte) error {
|
||||
newKey := string(key)
|
||||
vcopy := make([]byte, len(value))
|
||||
copy(vcopy, value)
|
||||
s.mut.Lock()
|
||||
s.mem[makeKey(key)] = value
|
||||
s.put(newKey, vcopy)
|
||||
s.mut.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// drop deletes a key-valu pair from the store, it's supposed to be called
|
||||
// with mutex locked.
|
||||
func (s *MemoryStore) drop(key string) {
|
||||
s.del[key] = true
|
||||
delete(s.mem, key)
|
||||
}
|
||||
|
||||
// Delete implements Store interface. Never returns an error.
|
||||
func (s *MemoryStore) Delete(key []byte) error {
|
||||
newKey := string(key)
|
||||
s.mut.Lock()
|
||||
s.drop(newKey)
|
||||
s.mut.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
@ -60,8 +84,13 @@ func (s *MemoryStore) Put(key, value []byte) error {
|
|||
// PutBatch implements the Store interface. Never returns an error.
|
||||
func (s *MemoryStore) PutBatch(batch Batch) error {
|
||||
b := batch.(*MemoryBatch)
|
||||
for k, v := range b.m {
|
||||
_ = s.Put(*k, v)
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
for k := range b.del {
|
||||
s.drop(k)
|
||||
}
|
||||
for k, v := range b.mem {
|
||||
s.put(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -69,9 +98,8 @@ func (s *MemoryStore) PutBatch(batch Batch) error {
|
|||
// Seek implements the Store interface.
|
||||
func (s *MemoryStore) Seek(key []byte, f func(k, v []byte)) {
|
||||
for k, v := range s.mem {
|
||||
if strings.Contains(k, hex.EncodeToString(key)) {
|
||||
decodeString, _ := hex.DecodeString(k)
|
||||
f(decodeString, v)
|
||||
if strings.HasPrefix(k, string(key)) {
|
||||
f([]byte(k), v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,9 +111,7 @@ func (s *MemoryStore) Batch() Batch {
|
|||
|
||||
// newMemoryBatch returns new memory batch.
|
||||
func newMemoryBatch() *MemoryBatch {
|
||||
return &MemoryBatch{
|
||||
m: make(map[*[]byte][]byte),
|
||||
}
|
||||
return &MemoryBatch{MemoryStore: *NewMemoryStore()}
|
||||
}
|
||||
|
||||
// Persist flushes all the MemoryStore contents into the (supposedly) persistent
|
||||
|
@ -94,18 +120,22 @@ func (s *MemoryStore) Persist(ps Store) (int, error) {
|
|||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
batch := ps.Batch()
|
||||
keys := 0
|
||||
keys, dkeys := 0, 0
|
||||
for k, v := range s.mem {
|
||||
kb, _ := hex.DecodeString(k)
|
||||
batch.Put(kb, v)
|
||||
batch.Put([]byte(k), v)
|
||||
keys++
|
||||
}
|
||||
for k := range s.del {
|
||||
batch.Delete([]byte(k))
|
||||
dkeys++
|
||||
}
|
||||
var err error
|
||||
if keys != 0 {
|
||||
if keys != 0 || dkeys != 0 {
|
||||
err = ps.PutBatch(batch)
|
||||
}
|
||||
if err == nil {
|
||||
s.mem = make(map[string][]byte)
|
||||
s.del = make(map[string]bool)
|
||||
}
|
||||
return keys, err
|
||||
}
|
||||
|
@ -114,11 +144,8 @@ func (s *MemoryStore) Persist(ps Store) (int, error) {
|
|||
// error.
|
||||
func (s *MemoryStore) Close() error {
|
||||
s.mut.Lock()
|
||||
s.del = nil
|
||||
s.mem = nil
|
||||
s.mut.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeKey(k []byte) string {
|
||||
return hex.EncodeToString(k)
|
||||
}
|
||||
|
|
|
@ -4,78 +4,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetPut(t *testing.T) {
|
||||
var (
|
||||
s = NewMemoryStore()
|
||||
key = []byte("sparse")
|
||||
value = []byte("rocks")
|
||||
)
|
||||
|
||||
if err := s.Put(key, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newVal, err := s.Get(key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, value, newVal)
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func TestKeyNotExist(t *testing.T) {
|
||||
var (
|
||||
s = NewMemoryStore()
|
||||
key = []byte("sparse")
|
||||
)
|
||||
|
||||
_, err := s.Get(key)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, err.Error(), "key not found")
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func TestPutBatch(t *testing.T) {
|
||||
var (
|
||||
s = NewMemoryStore()
|
||||
key = []byte("sparse")
|
||||
value = []byte("rocks")
|
||||
batch = s.Batch()
|
||||
)
|
||||
|
||||
batch.Put(key, value)
|
||||
|
||||
if err := s.PutBatch(batch); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newVal, err := s.Get(key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, value, newVal)
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func TestMemoryStore_Seek(t *testing.T) {
|
||||
var (
|
||||
s = NewMemoryStore()
|
||||
key = []byte("sparse")
|
||||
value = []byte("rocks")
|
||||
)
|
||||
|
||||
if err := s.Put(key, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s.Seek(key, func(k, v []byte) {
|
||||
assert.Equal(t, value, v)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMemoryStorePersist(t *testing.T) {
|
||||
// temporary Store
|
||||
ts := NewMemoryStore()
|
||||
|
@ -125,4 +55,20 @@ func TestMemoryStorePersist(t *testing.T) {
|
|||
c, err = ts.Persist(ps)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 0, c)
|
||||
// test persisting deletions
|
||||
err = ts.Delete([]byte("key"))
|
||||
assert.Equal(t, nil, err)
|
||||
c, err = ts.Persist(ps)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 0, c)
|
||||
v, err = ps.Get([]byte("key"))
|
||||
assert.Equal(t, ErrKeyNotFound, err)
|
||||
assert.Equal(t, []byte(nil), v)
|
||||
v, err = ps.Get([]byte("key2"))
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, []byte("value2"), v)
|
||||
}
|
||||
|
||||
func newMemoryStoreForTesting(t *testing.T) Store {
|
||||
return NewMemoryStore()
|
||||
}
|
||||
|
|
|
@ -48,6 +48,12 @@ func (s *RedisStore) Get(k []byte) ([]byte, error) {
|
|||
return []byte(val), nil
|
||||
}
|
||||
|
||||
// Delete implements the Store interface.
|
||||
func (s *RedisStore) Delete(k []byte) error {
|
||||
s.client.Del(string(k))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put implements the Store interface.
|
||||
func (s *RedisStore) Put(k, v []byte) error {
|
||||
s.client.Set(string(k), string(v), 0)
|
||||
|
@ -57,8 +63,11 @@ func (s *RedisStore) Put(k, v []byte) error {
|
|||
// PutBatch implements the Store interface.
|
||||
func (s *RedisStore) PutBatch(b Batch) error {
|
||||
pipe := s.client.Pipeline()
|
||||
for k, v := range b.(*MemoryBatch).m {
|
||||
pipe.Set(string(*k), v, 0)
|
||||
for k, v := range b.(*MemoryBatch).mem {
|
||||
pipe.Set(k, v, 0)
|
||||
}
|
||||
for k := range b.(*MemoryBatch).del {
|
||||
pipe.Del(k)
|
||||
}
|
||||
_, err := pipe.Exec()
|
||||
return err
|
||||
|
|
|
@ -4,109 +4,18 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/alicebob/miniredis"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewRedisStore(t *testing.T) {
|
||||
redisMock, redisStore := prepareRedisMock(t)
|
||||
key := []byte("testKey")
|
||||
value := []byte("testValue")
|
||||
err := redisStore.Put(key, value)
|
||||
assert.Nil(t, err, "NewRedisStore Put error")
|
||||
|
||||
result, err := redisStore.Get(key)
|
||||
assert.Nil(t, err, "NewRedisStore Get error")
|
||||
|
||||
assert.Equal(t, value, result)
|
||||
require.NoError(t, redisStore.Close())
|
||||
redisMock.Close()
|
||||
}
|
||||
|
||||
func TestRedisBatch_Len(t *testing.T) {
|
||||
want := len(map[string]string{})
|
||||
b := &MemoryBatch{
|
||||
m: map[*[]byte][]byte{},
|
||||
}
|
||||
assert.Equal(t, len(b.m), want)
|
||||
}
|
||||
|
||||
func TestRedisStore_GetAndPut(t *testing.T) {
|
||||
prepareRedisMock(t)
|
||||
type args struct {
|
||||
k []byte
|
||||
v []byte
|
||||
kToLook []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{"TestRedisStore_Get_Strings",
|
||||
args{
|
||||
k: []byte("foo"),
|
||||
v: []byte("bar"),
|
||||
kToLook: []byte("foo"),
|
||||
},
|
||||
[]byte("bar"),
|
||||
false,
|
||||
},
|
||||
{"TestRedisStore_Get_Negative_Strings",
|
||||
args{
|
||||
k: []byte("foo"),
|
||||
v: []byte("bar"),
|
||||
kToLook: []byte("wrong"),
|
||||
},
|
||||
[]byte(nil),
|
||||
true,
|
||||
},
|
||||
}
|
||||
redisMock, redisStore := prepareRedisMock(t)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := redisStore.Put(tt.args.k, tt.args.v)
|
||||
assert.Nil(t, err, "Got error while Put operation processing")
|
||||
got, err := redisStore.Get(tt.args.kToLook)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
redisMock.FlushDB()
|
||||
})
|
||||
}
|
||||
require.NoError(t, redisStore.Close())
|
||||
redisMock.Close()
|
||||
}
|
||||
|
||||
func TestRedisStore_PutBatch(t *testing.T) {
|
||||
batch := &MemoryBatch{m: map[*[]byte][]byte{&[]byte{'f', 'o', 'o', '1'}: []byte("bar1")}}
|
||||
mock, redisStore := prepareRedisMock(t)
|
||||
err := redisStore.PutBatch(batch)
|
||||
assert.Nil(t, err, "Error while PutBatch")
|
||||
result, err := redisStore.Get([]byte("foo1"))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte("bar1"), result)
|
||||
require.NoError(t, redisStore.Close())
|
||||
mock.Close()
|
||||
}
|
||||
|
||||
func TestRedisStore_Seek(t *testing.T) {
|
||||
mock, redisStore := prepareRedisMock(t)
|
||||
redisStore.Seek([]byte("foo"), func(k, v []byte) {
|
||||
assert.Equal(t, []byte("bar"), v)
|
||||
})
|
||||
require.NoError(t, redisStore.Close())
|
||||
mock.Close()
|
||||
type mockedRedisStore struct {
|
||||
RedisStore
|
||||
mini *miniredis.Miniredis
|
||||
}
|
||||
|
||||
func prepareRedisMock(t *testing.T) (*miniredis.Miniredis, *RedisStore) {
|
||||
miniRedis, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Errorf("MiniRedis mock creation error = %v", err)
|
||||
}
|
||||
require.Nil(t, err, "MiniRedis mock creation error")
|
||||
|
||||
_ = miniRedis.Set("foo", "bar")
|
||||
|
||||
dbConfig := DBConfiguration{
|
||||
|
@ -118,9 +27,18 @@ func prepareRedisMock(t *testing.T) (*miniredis.Miniredis, *RedisStore) {
|
|||
},
|
||||
}
|
||||
newRedisStore, err := NewRedisStore(dbConfig.RedisDBOptions)
|
||||
if err != nil {
|
||||
t.Errorf("NewRedisStore() error = %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
require.Nil(t, err, "NewRedisStore() error")
|
||||
return miniRedis, newRedisStore
|
||||
}
|
||||
|
||||
func (mrs *mockedRedisStore) Close() error {
|
||||
err := mrs.RedisStore.Close()
|
||||
mrs.mini.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func newRedisStoreForTesting(t *testing.T) Store {
|
||||
mock, rs := prepareRedisMock(t)
|
||||
mrs := &mockedRedisStore{RedisStore: *rs, mini: mock}
|
||||
return mrs
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ type (
|
|||
// information.
|
||||
Store interface {
|
||||
Batch() Batch
|
||||
Delete(k []byte) error
|
||||
Get([]byte) ([]byte, error)
|
||||
Put(k, v []byte) error
|
||||
PutBatch(Batch) error
|
||||
|
@ -43,8 +44,8 @@ type (
|
|||
// Each Store implementation is responsible of casting a Batch
|
||||
// to its appropriate type.
|
||||
Batch interface {
|
||||
Delete(k []byte)
|
||||
Put(k, v []byte)
|
||||
Len() int
|
||||
}
|
||||
|
||||
// KeyPrefix is a constant byte added as a prefix for each key
|
||||
|
|
239
pkg/core/storage/storeandbatch_test.go
Normal file
239
pkg/core/storage/storeandbatch_test.go
Normal file
|
@ -0,0 +1,239 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type dbSetup struct {
|
||||
name string
|
||||
create func(*testing.T) Store
|
||||
}
|
||||
|
||||
type dbTestFunction func(*testing.T, Store)
|
||||
|
||||
func testStoreClose(t *testing.T, s Store) {
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func testStorePutAndGet(t *testing.T, s Store) {
|
||||
key := []byte("foo")
|
||||
value := []byte("bar")
|
||||
|
||||
require.NoError(t, s.Put(key, value))
|
||||
|
||||
result, err := s.Get(key)
|
||||
assert.Nil(t, err)
|
||||
require.Equal(t, value, result)
|
||||
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func testStoreGetNonExistent(t *testing.T, s Store) {
|
||||
key := []byte("sparse")
|
||||
|
||||
_, err := s.Get(key)
|
||||
assert.Equal(t, err, ErrKeyNotFound)
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func testStorePutBatch(t *testing.T, s Store) {
|
||||
var (
|
||||
key = []byte("foo")
|
||||
value = []byte("bar")
|
||||
batch = s.Batch()
|
||||
)
|
||||
// Test that key and value are copied when batching.
|
||||
keycopy := make([]byte, len(key))
|
||||
copy(keycopy, key)
|
||||
valuecopy := make([]byte, len(value))
|
||||
copy(valuecopy, value)
|
||||
|
||||
batch.Put(keycopy, valuecopy)
|
||||
copy(valuecopy, key)
|
||||
copy(keycopy, value)
|
||||
|
||||
require.NoError(t, s.PutBatch(batch))
|
||||
newVal, err := s.Get(key)
|
||||
assert.Nil(t, err)
|
||||
require.Equal(t, value, newVal)
|
||||
assert.Equal(t, value, newVal)
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func testStoreSeek(t *testing.T, s Store) {
|
||||
type kvSeen struct {
|
||||
key []byte
|
||||
val []byte
|
||||
seen bool
|
||||
}
|
||||
var (
|
||||
// Given this prefix...
|
||||
goodprefix = []byte{'f'}
|
||||
// these pairs should be found...
|
||||
goodkvs = []kvSeen{
|
||||
{[]byte("foo"), []byte("bar"), false},
|
||||
{[]byte("faa"), []byte("bra"), false},
|
||||
{[]byte("foox"), []byte("barx"), false},
|
||||
}
|
||||
// and these should be not.
|
||||
badkvs = []kvSeen{
|
||||
{[]byte("doo"), []byte("pow"), false},
|
||||
{[]byte("mew"), []byte("qaz"), false},
|
||||
}
|
||||
)
|
||||
|
||||
for _, v := range goodkvs {
|
||||
require.NoError(t, s.Put(v.key, v.val))
|
||||
}
|
||||
for _, v := range badkvs {
|
||||
require.NoError(t, s.Put(v.key, v.val))
|
||||
}
|
||||
|
||||
numFound := 0
|
||||
s.Seek(goodprefix, func(k, v []byte) {
|
||||
for i := 0; i < len(goodkvs); i++ {
|
||||
if string(k) == string(goodkvs[i].key) {
|
||||
assert.Equal(t, string(goodkvs[i].val), string(v))
|
||||
goodkvs[i].seen = true
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(badkvs); i++ {
|
||||
if string(k) == string(badkvs[i].key) {
|
||||
badkvs[i].seen = true
|
||||
}
|
||||
}
|
||||
numFound++
|
||||
})
|
||||
assert.Equal(t, len(goodkvs), numFound)
|
||||
for i := 0; i < len(goodkvs); i++ {
|
||||
assert.Equal(t, true, goodkvs[i].seen)
|
||||
}
|
||||
for i := 0; i < len(badkvs); i++ {
|
||||
assert.Equal(t, false, badkvs[i].seen)
|
||||
}
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func testStoreDeleteNonExistent(t *testing.T, s Store) {
|
||||
key := []byte("sparse")
|
||||
|
||||
assert.NoError(t, s.Delete(key))
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func testStorePutAndDelete(t *testing.T, s Store) {
|
||||
key := []byte("foo")
|
||||
value := []byte("bar")
|
||||
|
||||
require.NoError(t, s.Put(key, value))
|
||||
|
||||
err := s.Delete(key)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = s.Get(key)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, err, ErrKeyNotFound)
|
||||
|
||||
// Double delete.
|
||||
err = s.Delete(key)
|
||||
assert.Nil(t, err)
|
||||
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func testStorePutBatchWithDelete(t *testing.T, s Store) {
|
||||
var (
|
||||
toBeStored = map[string][]byte{
|
||||
"foo": []byte("bar"),
|
||||
"bar": []byte("baz"),
|
||||
}
|
||||
deletedInBatch = map[string][]byte{
|
||||
"edc": []byte("rfv"),
|
||||
"tgb": []byte("yhn"),
|
||||
}
|
||||
readdedToBatch = map[string][]byte{
|
||||
"yhn": []byte("ujm"),
|
||||
}
|
||||
toBeDeleted = map[string][]byte{
|
||||
"qaz": []byte("wsx"),
|
||||
"qwe": []byte("123"),
|
||||
}
|
||||
toStay = map[string][]byte{
|
||||
"key": []byte("val"),
|
||||
"faa": []byte("bra"),
|
||||
}
|
||||
)
|
||||
for k, v := range toBeDeleted {
|
||||
require.NoError(t, s.Put([]byte(k), v))
|
||||
}
|
||||
for k, v := range toStay {
|
||||
require.NoError(t, s.Put([]byte(k), v))
|
||||
}
|
||||
batch := s.Batch()
|
||||
for k, v := range toBeStored {
|
||||
batch.Put([]byte(k), v)
|
||||
}
|
||||
for k := range toBeDeleted {
|
||||
batch.Delete([]byte(k))
|
||||
}
|
||||
for k, v := range readdedToBatch {
|
||||
batch.Put([]byte(k), v)
|
||||
}
|
||||
for k, v := range deletedInBatch {
|
||||
batch.Put([]byte(k), v)
|
||||
}
|
||||
for k := range deletedInBatch {
|
||||
batch.Delete([]byte(k))
|
||||
}
|
||||
for k := range readdedToBatch {
|
||||
batch.Delete([]byte(k))
|
||||
}
|
||||
for k, v := range readdedToBatch {
|
||||
batch.Put([]byte(k), v)
|
||||
}
|
||||
require.NoError(t, s.PutBatch(batch))
|
||||
toBe := []map[string][]byte{toStay, toBeStored, readdedToBatch}
|
||||
notToBe := []map[string][]byte{deletedInBatch, toBeDeleted}
|
||||
for _, kvs := range toBe {
|
||||
for k, v := range kvs {
|
||||
value, err := s.Get([]byte(k))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, value, v)
|
||||
}
|
||||
}
|
||||
for _, kvs := range notToBe {
|
||||
for k, v := range kvs {
|
||||
_, err := s.Get([]byte(k))
|
||||
assert.Equal(t, ErrKeyNotFound, err, "%s:%s", k, v)
|
||||
}
|
||||
}
|
||||
require.NoError(t, s.Close())
|
||||
}
|
||||
|
||||
func TestAllDBs(t *testing.T) {
|
||||
var DBs = []dbSetup{
|
||||
{"BoltDB", newBoltStoreForTesting},
|
||||
{"LevelDB", newLevelDBForTesting},
|
||||
{"Memory", newMemoryStoreForTesting},
|
||||
{"RedisDB", newRedisStoreForTesting},
|
||||
}
|
||||
var tests = []dbTestFunction{testStoreClose, testStorePutAndGet,
|
||||
testStoreGetNonExistent, testStorePutBatch, testStoreSeek,
|
||||
testStoreDeleteNonExistent, testStorePutAndDelete,
|
||||
testStorePutBatchWithDelete}
|
||||
for _, db := range DBs {
|
||||
for _, test := range tests {
|
||||
s := db.create(t)
|
||||
twrapper := func(t *testing.T) {
|
||||
test(t, s)
|
||||
}
|
||||
fname := runtime.FuncForPC(reflect.ValueOf(test).Pointer()).Name()
|
||||
t.Run(db.name+"/"+fname, twrapper)
|
||||
}
|
||||
}
|
||||
}
|
64
pkg/core/storage_item.go
Normal file
64
pkg/core/storage_item.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// StorageItem is the value to be stored with read-only flag.
|
||||
type StorageItem struct {
|
||||
Value []byte
|
||||
IsConst bool
|
||||
}
|
||||
|
||||
// makeStorageItemKey returns a key used to store StorageItem in the DB.
|
||||
func makeStorageItemKey(scripthash util.Uint160, key []byte) []byte {
|
||||
return storage.AppendPrefix(storage.STStorage, append(scripthash.BytesReverse(), key...))
|
||||
}
|
||||
|
||||
// getStorageItemFromStore returns StorageItem if it exists in the given Store.
|
||||
func getStorageItemFromStore(s storage.Store, scripthash util.Uint160, key []byte) *StorageItem {
|
||||
b, err := s.Get(makeStorageItemKey(scripthash, key))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
r := io.NewBinReaderFromBuf(b)
|
||||
|
||||
si := &StorageItem{}
|
||||
si.DecodeBinary(r)
|
||||
if r.Err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return si
|
||||
}
|
||||
|
||||
// putStorageItemIntoStore puts given StorageItem for given script with given
|
||||
// key into the given Store.
|
||||
func putStorageItemIntoStore(s storage.Store, scripthash util.Uint160, key []byte, si *StorageItem) error {
|
||||
buf := io.NewBufBinWriter()
|
||||
si.EncodeBinary(buf.BinWriter)
|
||||
if buf.Err != nil {
|
||||
return buf.Err
|
||||
}
|
||||
return s.Put(makeStorageItemKey(scripthash, key), buf.Bytes())
|
||||
}
|
||||
|
||||
// deleteStorageItemInStore drops storage item for the given script with the
|
||||
// given key from the Store.
|
||||
func deleteStorageItemInStore(s storage.Store, scripthash util.Uint160, key []byte) error {
|
||||
return s.Delete(makeStorageItemKey(scripthash, key))
|
||||
}
|
||||
|
||||
// EncodeBinary implements Serializable interface.
|
||||
func (si *StorageItem) EncodeBinary(w *io.BinWriter) {
|
||||
w.WriteBytes(si.Value)
|
||||
w.WriteLE(si.IsConst)
|
||||
}
|
||||
|
||||
// DecodeBinary implements Serializable interface.
|
||||
func (si *StorageItem) DecodeBinary(r *io.BinReader) {
|
||||
si.Value = r.ReadBytes()
|
||||
r.ReadLE(&si.IsConst)
|
||||
}
|
26
pkg/core/storage_item_test.go
Normal file
26
pkg/core/storage_item_test.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPutGetDeleteStorageItem(t *testing.T) {
|
||||
s := storage.NewMemoryStore()
|
||||
si := &StorageItem{
|
||||
Value: []byte("smth"),
|
||||
}
|
||||
key := []byte("key")
|
||||
cHash, err := util.Uint160DecodeBytes([]byte("abcdefghijklmnopqrst"))
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, putStorageItemIntoStore(s, cHash, key, si))
|
||||
siRead := getStorageItemFromStore(s, cHash, key)
|
||||
assert.NotNil(t, siRead)
|
||||
assert.Equal(t, si, siRead)
|
||||
assert.NoError(t, deleteStorageItemInStore(s, cHash, key))
|
||||
siRead2 := getStorageItemFromStore(s, cHash, key)
|
||||
assert.Nil(t, siRead2)
|
||||
}
|
|
@ -47,8 +47,8 @@ func NewPrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
|
|||
return &PrivateKey{b}, nil
|
||||
}
|
||||
|
||||
// NewPrivateKeyFromRawBytes returns a NEO PrivateKey from the ASN.1 serialized keys.
|
||||
func NewPrivateKeyFromRawBytes(b []byte) (*PrivateKey, error) {
|
||||
// NewPrivateKeyFromASN1 returns a NEO PrivateKey from the ASN.1 serialized key.
|
||||
func NewPrivateKeyFromASN1(b []byte) (*PrivateKey, error) {
|
||||
privkey, err := x509.ParseECPrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -61,7 +61,7 @@ func NewPublicKeyFromString(s string) (*PublicKey, error) {
|
|||
|
||||
// Bytes returns the byte array representation of the public key.
|
||||
func (p *PublicKey) Bytes() []byte {
|
||||
if p.isInfinity() {
|
||||
if p.IsInfinity() {
|
||||
return []byte{0x00}
|
||||
}
|
||||
|
||||
|
@ -78,8 +78,8 @@ func (p *PublicKey) Bytes() []byte {
|
|||
return append([]byte{prefix}, paddedX...)
|
||||
}
|
||||
|
||||
// NewPublicKeyFromRawBytes returns a NEO PublicKey from the ASN.1 serialized keys.
|
||||
func NewPublicKeyFromRawBytes(data []byte) (*PublicKey, error) {
|
||||
// NewPublicKeyFromASN1 returns a NEO PublicKey from the ASN.1 serialized key.
|
||||
func NewPublicKeyFromASN1(data []byte) (*PublicKey, error) {
|
||||
var (
|
||||
err error
|
||||
pubkey interface{}
|
||||
|
@ -225,14 +225,14 @@ func (p *PublicKey) Verify(signature []byte, hash []byte) bool {
|
|||
return ecdsa.Verify(publicKey, hash, rBytes, sBytes)
|
||||
}
|
||||
|
||||
// isInfinity checks if point P is infinity on EllipticCurve ec.
|
||||
func (p *PublicKey) isInfinity() bool {
|
||||
// IsInfinity checks if the key is infinite (null, basically).
|
||||
func (p *PublicKey) IsInfinity() bool {
|
||||
return p.X == nil && p.Y == nil
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (p *PublicKey) String() string {
|
||||
if p.isInfinity() {
|
||||
if p.IsInfinity() {
|
||||
return "00"
|
||||
}
|
||||
bx := hex.EncodeToString(p.X.Bytes())
|
||||
|
|
|
@ -56,6 +56,9 @@ func (chain testChain) HeaderHeight() uint32 {
|
|||
func (chain testChain) GetBlock(hash util.Uint256) (*core.Block, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
func (chain testChain) GetContractState(hash util.Uint160) *core.ContractState {
|
||||
panic("TODO")
|
||||
}
|
||||
func (chain testChain) GetHeaderHash(int) util.Uint256 {
|
||||
return util.Uint256{}
|
||||
}
|
||||
|
@ -69,6 +72,15 @@ func (chain testChain) GetAssetState(util.Uint256) *core.AssetState {
|
|||
func (chain testChain) GetAccountState(util.Uint160) *core.AccountState {
|
||||
panic("TODO")
|
||||
}
|
||||
func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *core.StorageItem {
|
||||
panic("TODO")
|
||||
}
|
||||
func (chain testChain) GetStorageItems(hash util.Uint160) (map[string]*core.StorageItem, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
func (chain testChain) CurrentHeaderHash() util.Uint256 {
|
||||
return util.Uint256{}
|
||||
}
|
||||
|
@ -85,6 +97,10 @@ func (chain testChain) GetTransaction(util.Uint256) (*transaction.Transaction, u
|
|||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) GetUnspentCoinState(util.Uint256) *core.UnspentCoinState {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) GetMemPool() core.MemPool {
|
||||
panic("TODO")
|
||||
}
|
||||
|
@ -93,7 +109,7 @@ func (chain testChain) IsLowPriority(*transaction.Transaction) bool {
|
|||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) Verify(*transaction.Transaction) error {
|
||||
func (chain testChain) VerifyTx(*transaction.Transaction, *core.Block) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
|
|
|
@ -399,7 +399,7 @@ func (s *Server) RelayTxn(t *transaction.Transaction) RelayReason {
|
|||
if s.chain.HasTransaction(t.Hash()) {
|
||||
return RelayAlreadyExists
|
||||
}
|
||||
if err := s.chain.Verify(t); err != nil {
|
||||
if err := s.chain.VerifyTx(t, nil); err != nil {
|
||||
return RelayInvalid
|
||||
}
|
||||
// TODO: Implement Plugin.CheckPolicy?
|
||||
|
|
|
@ -3,18 +3,16 @@ package rpc
|
|||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/config"
|
||||
"github.com/CityOfZion/neo-go/pkg/core"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/CityOfZion/neo-go/pkg/network"
|
||||
"github.com/CityOfZion/neo-go/pkg/rpc/result"
|
||||
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -142,6 +140,8 @@ type GetAccountStateResponse struct {
|
|||
}
|
||||
|
||||
func initServerWithInMemoryChain(ctx context.Context, t *testing.T) (*core.Blockchain, http.HandlerFunc) {
|
||||
var nBlocks uint32
|
||||
|
||||
net := config.ModeUnitTestNet
|
||||
configPath := "../../config"
|
||||
cfg, err := config.Load(configPath, net)
|
||||
|
@ -152,7 +152,18 @@ func initServerWithInMemoryChain(ctx context.Context, t *testing.T) (*core.Block
|
|||
require.NoError(t, err, "could not create chain")
|
||||
|
||||
go chain.Run(ctx)
|
||||
initBlocks(t, chain)
|
||||
|
||||
f, err := os.Open("testdata/50testblocks.acc")
|
||||
require.Nil(t, err)
|
||||
br := io.NewBinReaderFromIO(f)
|
||||
br.ReadLE(&nBlocks)
|
||||
require.Nil(t, br.Err)
|
||||
for i := 0; i < int(nBlocks); i++ {
|
||||
block := &core.Block{}
|
||||
block.DecodeBinary(br)
|
||||
require.Nil(t, br.Err)
|
||||
require.NoError(t, chain.AddBlock(block))
|
||||
}
|
||||
|
||||
serverConfig := network.NewServerConfig(cfg)
|
||||
server := network.NewServer(serverConfig, chain)
|
||||
|
@ -161,44 +172,3 @@ func initServerWithInMemoryChain(ctx context.Context, t *testing.T) (*core.Block
|
|||
|
||||
return chain, handler
|
||||
}
|
||||
|
||||
func initBlocks(t *testing.T, chain *core.Blockchain) {
|
||||
blocks := makeBlocks(10)
|
||||
for i := 0; i < len(blocks); i++ {
|
||||
require.NoError(t, chain.AddBlock(blocks[i]))
|
||||
}
|
||||
}
|
||||
|
||||
func makeBlocks(n int) []*core.Block {
|
||||
blocks := make([]*core.Block, n)
|
||||
for i := 0; i < n; i++ {
|
||||
blocks[i] = newBlock(uint32(i+1), newTX(transaction.MinerType))
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
func newTX(t transaction.TXType) *transaction.Transaction {
|
||||
return &transaction.Transaction{
|
||||
Type: t,
|
||||
}
|
||||
}
|
||||
|
||||
func newBlock(index uint32, txs ...*transaction.Transaction) *core.Block {
|
||||
b := &core.Block{
|
||||
BlockBase: core.BlockBase{
|
||||
Version: 0,
|
||||
PrevHash: hash.Sha256([]byte("a")),
|
||||
MerkleRoot: hash.Sha256([]byte("b")),
|
||||
Timestamp: uint32(time.Now().UTC().Unix()),
|
||||
Index: index,
|
||||
ConsensusData: 1111,
|
||||
NextConsensus: util.Uint160{},
|
||||
Script: &transaction.Witness{
|
||||
VerificationScript: []byte{0x0},
|
||||
InvocationScript: []byte{0x1},
|
||||
},
|
||||
},
|
||||
Transactions: txs,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
BIN
pkg/rpc/testdata/50testblocks.acc
vendored
Normal file
BIN
pkg/rpc/testdata/50testblocks.acc
vendored
Normal file
Binary file not shown.
|
@ -9,6 +9,21 @@ import (
|
|||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
)
|
||||
|
||||
// CreateSignatureRedeemScript creates a check signature script runnable by VM.
|
||||
func CreateSignatureRedeemScript(key *keys.PublicKey) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := vm.EmitBytes(buf, key.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = vm.EmitOpcode(buf, vm.CHECKSIG)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// CreateMultiSigRedeemScript will create a script runnable by the VM.
|
||||
func CreateMultiSigRedeemScript(m int, publicKeys keys.PublicKeys) ([]byte, error) {
|
||||
if m <= 1 {
|
||||
|
|
|
@ -18,6 +18,17 @@ const (
|
|||
ArrayType
|
||||
)
|
||||
|
||||
// PropertyState represents contract properties (flags).
|
||||
type PropertyState byte
|
||||
|
||||
// List of supported properties.
|
||||
const (
|
||||
NoProperties = 0
|
||||
HasStorage PropertyState = 1 << iota
|
||||
HasDynamicInvoke
|
||||
IsPayable
|
||||
)
|
||||
|
||||
// Parameter represents a smart contract parameter.
|
||||
type Parameter struct {
|
||||
// Type of the parameter
|
||||
|
|
98
pkg/vm/contract_checks.go
Normal file
98
pkg/vm/contract_checks.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
func getNumOfThingsFromInstr(instr Instruction, param []byte) (int, bool) {
|
||||
var nthings int
|
||||
|
||||
switch instr {
|
||||
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8,
|
||||
PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16:
|
||||
nthings = int(instr-PUSH1) + 1
|
||||
case PUSHBYTES1:
|
||||
nthings = int(param[0])
|
||||
case PUSHBYTES2:
|
||||
nthings = int(binary.LittleEndian.Uint16(param))
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
if nthings < 1 || nthings > MaxArraySize {
|
||||
return 0, false
|
||||
}
|
||||
return nthings, true
|
||||
}
|
||||
|
||||
// IsMultiSigContract checks whether the passed script is a multi-signature
|
||||
// contract.
|
||||
func IsMultiSigContract(script []byte) bool {
|
||||
var nsigs, nkeys int
|
||||
|
||||
ctx := NewContext(script)
|
||||
instr, param, err := ctx.Next()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
nsigs, ok := getNumOfThingsFromInstr(instr, param)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
for {
|
||||
instr, param, err = ctx.Next()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if instr != PUSHBYTES33 {
|
||||
break
|
||||
}
|
||||
nkeys++
|
||||
if nkeys > MaxArraySize {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if nkeys < nsigs {
|
||||
return false
|
||||
}
|
||||
nkeys2, ok := getNumOfThingsFromInstr(instr, param)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if nkeys2 != nkeys {
|
||||
return false
|
||||
}
|
||||
instr, _, err = ctx.Next()
|
||||
if err != nil || instr != CHECKMULTISIG {
|
||||
return false
|
||||
}
|
||||
instr, _, err = ctx.Next()
|
||||
if err != nil || instr != RET || ctx.ip != len(script) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSignatureContract checks whether the passed script is a signature check
|
||||
// contract.
|
||||
func IsSignatureContract(script []byte) bool {
|
||||
ctx := NewContext(script)
|
||||
instr, _, err := ctx.Next()
|
||||
if err != nil || instr != PUSHBYTES33 {
|
||||
return false
|
||||
}
|
||||
instr, _, err = ctx.Next()
|
||||
if err != nil || instr != CHECKSIG {
|
||||
return false
|
||||
}
|
||||
instr, _, err = ctx.Next()
|
||||
if err != nil || instr != RET || ctx.ip != len(script) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsStandardContract checks whether the passed script is a signature or
|
||||
// multi-signature contract.
|
||||
func IsStandardContract(script []byte) bool {
|
||||
return IsSignatureContract(script) || IsMultiSigContract(script)
|
||||
}
|
227
pkg/vm/contract_checks_test.go
Normal file
227
pkg/vm/contract_checks_test.go
Normal file
|
@ -0,0 +1,227 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsSignatureContractGood(t *testing.T) {
|
||||
prog := make([]byte, 35)
|
||||
prog[0] = byte(PUSHBYTES33)
|
||||
prog[34] = byte(CHECKSIG)
|
||||
assert.Equal(t, true, IsSignatureContract(prog))
|
||||
assert.Equal(t, true, IsStandardContract(prog))
|
||||
}
|
||||
|
||||
func TestIsSignatureContractBadNoCheckSig(t *testing.T) {
|
||||
prog := make([]byte, 34)
|
||||
prog[0] = byte(PUSHBYTES33)
|
||||
assert.Equal(t, false, IsSignatureContract(prog))
|
||||
assert.Equal(t, false, IsStandardContract(prog))
|
||||
}
|
||||
|
||||
func TestIsSignatureContractBadNoCheckSig2(t *testing.T) {
|
||||
prog := make([]byte, 35)
|
||||
prog[0] = byte(PUSHBYTES33)
|
||||
prog[34] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsSignatureContract(prog))
|
||||
}
|
||||
|
||||
func TestIsSignatureContractBadWrongPush(t *testing.T) {
|
||||
prog := make([]byte, 35)
|
||||
prog[0] = byte(PUSHBYTES32)
|
||||
prog[33] = byte(NOP)
|
||||
prog[34] = byte(CHECKSIG)
|
||||
assert.Equal(t, false, IsSignatureContract(prog))
|
||||
}
|
||||
|
||||
func TestIsSignatureContractBadWrongInstr(t *testing.T) {
|
||||
prog := make([]byte, 30)
|
||||
prog[0] = byte(PUSHBYTES33)
|
||||
assert.Equal(t, false, IsSignatureContract(prog))
|
||||
}
|
||||
|
||||
func TestIsSignatureContractBadExcessiveInstr(t *testing.T) {
|
||||
prog := make([]byte, 36)
|
||||
prog[0] = byte(PUSHBYTES33)
|
||||
prog[34] = byte(CHECKSIG)
|
||||
prog[35] = byte(RET)
|
||||
assert.Equal(t, false, IsSignatureContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractGood(t *testing.T) {
|
||||
prog := make([]byte, 71)
|
||||
prog[0] = byte(PUSH2)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(PUSHBYTES33)
|
||||
prog[69] = byte(PUSH2)
|
||||
prog[70] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, true, IsMultiSigContract(prog))
|
||||
assert.Equal(t, true, IsStandardContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractGoodPushBytes1(t *testing.T) {
|
||||
prog := make([]byte, 73)
|
||||
prog[0] = byte(PUSHBYTES1)
|
||||
prog[1] = 2
|
||||
prog[2] = byte(PUSHBYTES33)
|
||||
prog[36] = byte(PUSHBYTES33)
|
||||
prog[70] = byte(PUSHBYTES1)
|
||||
prog[71] = 2
|
||||
prog[72] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, true, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractGoodPushBytes2(t *testing.T) {
|
||||
prog := make([]byte, 75)
|
||||
prog[0] = byte(PUSHBYTES2)
|
||||
prog[1] = 2
|
||||
prog[3] = byte(PUSHBYTES33)
|
||||
prog[37] = byte(PUSHBYTES33)
|
||||
prog[71] = byte(PUSHBYTES2)
|
||||
prog[72] = 2
|
||||
prog[74] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, true, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadNSigs1(t *testing.T) {
|
||||
prog := make([]byte, 71)
|
||||
prog[0] = byte(PUSH0)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(PUSHBYTES33)
|
||||
prog[69] = byte(PUSH2)
|
||||
prog[70] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
assert.Equal(t, false, IsStandardContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadNSigs2(t *testing.T) {
|
||||
prog := make([]byte, 73)
|
||||
prog[0] = byte(PUSHBYTES2)
|
||||
prog[1] = 0xff
|
||||
prog[2] = 0xff
|
||||
prog[3] = byte(PUSHBYTES33)
|
||||
prog[37] = byte(PUSHBYTES33)
|
||||
prog[71] = byte(PUSH2)
|
||||
prog[72] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadNSigs3(t *testing.T) {
|
||||
prog := make([]byte, 71)
|
||||
prog[0] = byte(PUSH5)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(PUSHBYTES33)
|
||||
prog[69] = byte(PUSH2)
|
||||
prog[70] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadExcessiveNOP1(t *testing.T) {
|
||||
prog := make([]byte, 72)
|
||||
prog[0] = byte(PUSH2)
|
||||
prog[1] = byte(NOP)
|
||||
prog[2] = byte(PUSHBYTES33)
|
||||
prog[36] = byte(PUSHBYTES33)
|
||||
prog[70] = byte(PUSH2)
|
||||
prog[71] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadExcessiveNOP2(t *testing.T) {
|
||||
prog := make([]byte, 72)
|
||||
prog[0] = byte(PUSH2)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(NOP)
|
||||
prog[36] = byte(PUSHBYTES33)
|
||||
prog[70] = byte(PUSH2)
|
||||
prog[71] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadExcessiveNOP3(t *testing.T) {
|
||||
prog := make([]byte, 72)
|
||||
prog[0] = byte(PUSH2)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(PUSHBYTES33)
|
||||
prog[69] = byte(NOP)
|
||||
prog[70] = byte(PUSH2)
|
||||
prog[71] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadExcessiveNOP4(t *testing.T) {
|
||||
prog := make([]byte, 72)
|
||||
prog[0] = byte(PUSH2)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(PUSHBYTES33)
|
||||
prog[69] = byte(PUSH2)
|
||||
prog[70] = byte(NOP)
|
||||
prog[71] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadExcessiveNOP5(t *testing.T) {
|
||||
prog := make([]byte, 72)
|
||||
prog[0] = byte(PUSH2)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(PUSHBYTES33)
|
||||
prog[69] = byte(PUSH2)
|
||||
prog[70] = byte(CHECKMULTISIG)
|
||||
prog[71] = byte(NOP)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadNKeys1(t *testing.T) {
|
||||
prog := make([]byte, 71)
|
||||
prog[0] = byte(PUSH2)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(PUSHBYTES33)
|
||||
prog[69] = byte(PUSH3)
|
||||
prog[70] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadNKeys2(t *testing.T) {
|
||||
prog := make([]byte, 1)
|
||||
prog[0] = byte(PUSH10)
|
||||
key := make([]byte, 33)
|
||||
var asize = uint16(MaxArraySize + 1)
|
||||
for i := 0; i < int(asize); i++ {
|
||||
prog = append(prog, byte(PUSHBYTES33))
|
||||
prog = append(prog, key...)
|
||||
}
|
||||
prog = append(prog, byte(PUSHBYTES2), byte(asize&0xff), byte((asize<<8)&0xff), byte(CHECKMULTISIG))
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadRead1(t *testing.T) {
|
||||
prog := make([]byte, 71)
|
||||
prog[0] = byte(PUSHBYTES75)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(PUSHBYTES33)
|
||||
prog[69] = byte(PUSH2)
|
||||
prog[70] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadRead2(t *testing.T) {
|
||||
prog := make([]byte, 71)
|
||||
prog[0] = byte(PUSH2)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(PUSHBYTES75)
|
||||
prog[69] = byte(PUSH2)
|
||||
prog[70] = byte(CHECKMULTISIG)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
||||
|
||||
func TestIsMultiSigContractBadRead3(t *testing.T) {
|
||||
prog := make([]byte, 71)
|
||||
prog[0] = byte(PUSH2)
|
||||
prog[1] = byte(PUSHBYTES33)
|
||||
prog[35] = byte(PUSHBYTES33)
|
||||
prog[69] = byte(PUSH2)
|
||||
prog[70] = byte(PUSHBYTES1)
|
||||
assert.Equal(t, false, IsMultiSigContract(prog))
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
@ -23,10 +24,26 @@ func makeStackItem(v interface{}) StackItem {
|
|||
return &BigIntegerItem{
|
||||
value: big.NewInt(val),
|
||||
}
|
||||
case uint8:
|
||||
return &BigIntegerItem{
|
||||
value: big.NewInt(int64(val)),
|
||||
}
|
||||
case uint16:
|
||||
return &BigIntegerItem{
|
||||
value: big.NewInt(int64(val)),
|
||||
}
|
||||
case uint32:
|
||||
return &BigIntegerItem{
|
||||
value: big.NewInt(int64(val)),
|
||||
}
|
||||
case uint64:
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, val)
|
||||
bigInt := big.NewInt(0)
|
||||
bigInt.SetBytes(b)
|
||||
return &BigIntegerItem{
|
||||
value: bigInt,
|
||||
}
|
||||
case []byte:
|
||||
return &ByteArrayItem{
|
||||
value: val,
|
||||
|
|
Loading…
Reference in a new issue