Merge pull request #418 from nspcc-dev/various-verification-fixes2

Transaction verification fixes, interops and block verification. Fixes #12.
This commit is contained in:
Roman Khimov 2019-10-15 19:11:00 +03:00 committed by GitHub
commit 4822c736bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 3022 additions and 538 deletions

View file

@ -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.

View file

@ -27,6 +27,8 @@ ProtocolConfiguration:
IssueTransaction: 500
PublishTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: false
ApplicationConfiguration:
DBConfiguration:

View file

@ -15,6 +15,8 @@ ProtocolConfiguration:
IssueTransaction: 500
PublishTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true
ApplicationConfiguration:
DBConfiguration:

View file

@ -12,6 +12,8 @@ ProtocolConfiguration:
IssueTransaction: 500
PublishTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true
ApplicationConfiguration:
DBConfiguration:

View file

@ -12,6 +12,8 @@ ProtocolConfiguration:
IssueTransaction: 500
PublishTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true
ApplicationConfiguration:
DBConfiguration:

View file

@ -12,6 +12,8 @@ ProtocolConfiguration:
IssueTransaction: 500
PublishTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true
ApplicationConfiguration:
DBConfiguration:

View file

@ -18,6 +18,8 @@ ProtocolConfiguration:
IssueTransaction: 500
PublishTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true
ApplicationConfiguration:
DBConfiguration:

View file

@ -27,6 +27,8 @@ ProtocolConfiguration:
IssueTransaction: 5
PublishTransaction: 5
RegisterTransaction: 100
VerifyBlocks: true
VerifyTransactions: false
ApplicationConfiguration:
DBConfiguration:

View file

@ -17,6 +17,8 @@ ProtocolConfiguration:
IssueTransaction: 500
PublishTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true
ApplicationConfiguration:
DBConfiguration:

View file

@ -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

View file

@ -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)
}

View file

@ -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.

View file

@ -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
}

View file

@ -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) {

View file

@ -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,14 +205,20 @@ 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)
}
for _, tx := range block.Transactions {
err := bc.Verify(tx)
if err != nil {
return fmt.Errorf("transaction %s failed to verify: %s", tx.Hash().ReverseString(), err)
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.VerifyTx(tx, block)
if err != nil {
return fmt.Errorf("transaction %s failed to verify: %s", tx.Hash().ReverseString(), err)
}
}
}
}
@ -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")
}
if bc.SystemFee(t).GreaterThan(util.Fixed8(0)) && (len(resultsDestroy) == 0 || resultsDestroy[0].Amount.LessThan(bc.SystemFee(t))) {
return false
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())
}
}
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,12 +1112,63 @@ func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([
}
// VerifyWitnesses verify the scripts (witnesses) that come with a given
// 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, hash, false)
if err != nil {
return err
}
verification = bb.Bytes()
} else {
if h := witness.ScriptHash(); hash != h {
return errors.New("witness hash mismatch")
}
}
vm := vm.New(vm.ModeMute)
vm.SetCheckedHash(checkedHash.Bytes())
vm.SetScriptGetter(func(hash util.Uint160) []byte {
cs := bc.GetContractState(hash)
if cs == nil {
return nil
}
return cs.Script
})
vm.RegisterInteropFuncs(interopCtx.getSystemInteropMap())
vm.RegisterInteropFuncs(interopCtx.getNeoInteropMap())
vm.LoadScript(verification)
vm.LoadScript(witness.InvocationScript)
vm.Run()
if vm.HasFailed() {
return errors.Errorf("vm failed to execute the script")
}
resEl := vm.Estack().Pop()
if resEl != nil {
res, err := resEl.TryBool()
if err != nil {
return err
}
if !res {
return errors.Errorf("signature check failed")
}
} 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.
// 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) VerifyWitnesses(t *transaction.Transaction) error {
func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *Block) error {
hashes, err := bc.GetScriptHashesForVerifying(t)
if err != nil {
return err
@ -1014,54 +1180,30 @@ func (bc *Blockchain) VerifyWitnesses(t *transaction.Transaction) error {
}
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++ {
verification := witnesses[i].VerificationScript
if len(verification) == 0 {
bb := new(bytes.Buffer)
err = vm.EmitAppCall(bb, hashes[i], 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)
}
}
vm := vm.New(vm.ModeMute)
vm.SetCheckedHash(t.VerificationHash().Bytes())
vm.SetScriptGetter(func(hash util.Uint160) []byte {
cs := bc.GetContractState(hash)
if cs == nil {
return nil
}
return cs.Script
})
vm.LoadScript(verification)
vm.LoadScript(witnesses[i].InvocationScript)
vm.Run()
if vm.HasFailed() {
return errors.Errorf("vm failed to execute the script")
}
resEl := vm.Estack().Pop()
if resEl != nil {
res, err := resEl.TryBool()
if err != nil {
return err
}
if !res {
return errors.Errorf("signature check failed")
}
} else {
return errors.Errorf("no result returned from the script")
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())

View file

@ -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
}

View file

@ -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
}

View file

@ -13,17 +13,15 @@ type Contracts map[util.Uint160]*ContractState
// ContractState holds information about a smart contract in the NEO blockchain.
type ContractState struct {
Script []byte
ParamList []smartcontract.ParamType
ReturnType smartcontract.ParamType
Properties []byte
Name string
CodeVersion string
Author string
Email string
Description string
HasStorage bool
HasDynamicInvoke bool
Script []byte
ParamList []smartcontract.ParamType
ReturnType smartcontract.ParamType
Properties smartcontract.PropertyState
Name string
CodeVersion string
Author string
Email string
Description string
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
}

View file

@ -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"
@ -13,17 +14,15 @@ func TestEncodeDecodeContractState(t *testing.T) {
script := []byte("testscript")
contract := &ContractState{
Script: script,
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
ReturnType: smartcontract.BoolType,
Properties: []byte("smth"),
Name: "Contracto",
CodeVersion: "1.0.0",
Author: "Joe Random",
Email: "joe@example.com",
Description: "Test contract",
HasStorage: true,
HasDynamicInvoke: false,
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.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)
}

View file

@ -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
View 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
View 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
View 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},
}
}

View file

@ -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
}

View file

@ -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() {

View file

@ -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)

View file

@ -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
}

View file

@ -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)
}

View file

@ -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()
}

View file

@ -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

View file

@ -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
}

View file

@ -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

View 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
View 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)
}

View 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)
}

View file

@ -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

View file

@ -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())

View file

@ -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")
}

View file

@ -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?

View file

@ -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

Binary file not shown.

View file

@ -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 {

View file

@ -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
View 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)
}

View 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))
}

View file

@ -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,