Merge pull request #943 from nspcc-dev/neo3/transaction/fees

core: add SystemFee and NetworkFee to transaction
This commit is contained in:
Roman Khimov 2020-05-20 23:48:28 +03:00 committed by GitHub
commit c751472852
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 894 additions and 397 deletions

View file

@ -277,6 +277,10 @@ func claimGas(ctx *cli.Context) error {
ScriptHash: scriptHash,
})
err = c.AddNetworkFee(tx, acc)
if err != nil {
return cli.NewExitError(err, 1)
}
_ = acc.SignTx(tx)
if err := c.SendRawTransaction(tx); err != nil {
return cli.NewExitError(err, 1)
@ -538,6 +542,11 @@ func transferAsset(ctx *cli.Context) error {
Position: 1,
})
err = c.AddNetworkFee(tx, acc)
if err != nil {
return cli.NewExitError(err, 1)
}
if outFile := ctx.String("out"); outFile != "" {
priv := acc.PrivateKey()
pub := priv.PublicKey()

View file

@ -500,9 +500,7 @@ func newBlockFromContext(ctx *dbft.Context) block.Block {
Nonce: ctx.Nonce,
}
if len(ctx.TransactionHashes) != 0 {
mt := merkle.NewMerkleTree(append([]util.Uint256{consensusData.Hash()}, ctx.TransactionHashes...)...)
block.Block.MerkleRoot = mt.Root().Hash
}
return block
}

View file

@ -25,7 +25,7 @@ func TestNewService(t *testing.T) {
tx := transaction.NewContractTX()
tx.ValidUntilBlock = 1
addSender(t, tx)
signTx(t, tx)
signTx(t, srv.Chain.FeePerByte(), tx)
require.NoError(t, srv.Chain.PoolTx(tx))
var txx []block.Transaction
@ -45,7 +45,7 @@ func TestService_GetVerified(t *testing.T) {
txs = append(txs, tx)
}
addSender(t, txs...)
signTx(t, txs...)
signTx(t, srv.Chain.FeePerByte(), txs...)
require.NoError(t, srv.Chain.PoolTx(txs[3]))
hashes := []util.Uint256{txs[0].Hash(), txs[1].Hash(), txs[2].Hash()}
@ -124,7 +124,7 @@ func TestService_getTx(t *testing.T) {
tx.Nonce = 1234
tx.ValidUntilBlock = 1
addSender(t, tx)
signTx(t, tx)
signTx(t, srv.Chain.FeePerByte(), tx)
h := tx.Hash()
require.Equal(t, nil, srv.getTx(h))
@ -229,10 +229,7 @@ func newTestChain(t *testing.T) *core.Blockchain {
type feer struct{}
func (fs *feer) NetworkFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
func (fs *feer) IsLowPriority(util.Fixed8) bool { return false }
func (fs *feer) FeePerByte(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
func (fs *feer) SystemFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
var neoOwner = testchain.MultisigScriptHash()
@ -242,7 +239,7 @@ func addSender(t *testing.T, txs ...*transaction.Transaction) {
}
}
func signTx(t *testing.T, txs ...*transaction.Transaction) {
func signTx(t *testing.T, feePerByte util.Fixed8, txs ...*transaction.Transaction) {
validators := make([]*keys.PublicKey, 4)
privNetKeys := make([]*keys.PrivateKey, 4)
for i := 0; i < 4; i++ {
@ -253,6 +250,11 @@ func signTx(t *testing.T, txs ...*transaction.Transaction) {
rawScript, err := smartcontract.CreateMultiSigRedeemScript(3, validators)
require.NoError(t, err)
for _, tx := range txs {
size := io.GetVarSize(tx)
netFee, sizeDelta := core.CalculateNetworkFee(rawScript)
tx.NetworkFee = tx.NetworkFee.Add(netFee)
size += sizeDelta
tx.NetworkFee = tx.NetworkFee.Add(util.Fixed8(int64(size) * int64(feePerByte)))
data := tx.GetSignedPart()
buf := io.NewBufBinWriter()

View file

@ -466,7 +466,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
cache := dao.NewCached(bc.dao)
fee := bc.getSystemFeeAmount(block.PrevHash)
for _, tx := range block.Transactions {
fee += uint32(bc.SystemFee(tx).IntegralValue())
fee += uint32(tx.SystemFee.IntegralValue())
}
if err := cache.StoreAsBlock(block, fee); err != nil {
return err
@ -651,17 +651,27 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
continue
}
op, ok := arr[0].Value().([]byte)
if !ok || string(op) != "transfer" {
if !ok || (string(op) != "transfer" && string(op) != "Transfer") {
continue
}
from, ok := arr[1].Value().([]byte)
var from []byte
fromValue := arr[1].Value()
// we don't have `from` set when we are minting tokens
if fromValue != nil {
from, ok = fromValue.([]byte)
if !ok {
continue
}
to, ok := arr[2].Value().([]byte)
}
var to []byte
toValue := arr[2].Value()
// we don't have `to` set when we are burning tokens
if toValue != nil {
to, ok = toValue.([]byte)
if !ok {
continue
}
}
amount, ok := arr[3].Value().(*big.Int)
if !ok {
bs, ok := arr[3].Value().([]byte)
@ -713,7 +723,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
bc.topBlock.Store(block)
atomic.StoreUint32(&bc.blockHeight, block.Index)
updateBlockHeightMetric(block.Index)
bc.memPool.RemoveStale(bc.isTxStillRelevant)
bc.memPool.RemoveStale(bc.isTxStillRelevant, bc)
return nil
}
@ -807,6 +817,11 @@ func (bc *Blockchain) GetNEP5Balances(acc util.Uint160) *state.NEP5Balances {
return bs
}
// GetUtilityTokenBalance returns utility token (GAS) balance for the acc.
func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) util.Fixed8 {
return util.Fixed8FromInt64(bc.GetNEP5Balances(acc).Trackers[bc.contracts.GAS.Hash].Balance)
}
// LastBatch returns last persisted storage batch.
func (bc *Blockchain) LastBatch() *storage.MemBatch {
return bc.lastBatch
@ -1107,48 +1122,10 @@ func (bc *Blockchain) references(ins []transaction.Input) ([]transaction.InOut,
return references, nil
}
// FeePerByte returns network fee divided by the size of the transaction.
func (bc *Blockchain) FeePerByte(t *transaction.Transaction) util.Fixed8 {
return bc.NetworkFee(t).Div(int64(io.GetVarSize(t)))
}
// NetworkFee returns network fee.
func (bc *Blockchain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
// https://github.com/neo-project/neo/blob/master-2.x/neo/Network/P2P/Payloads/ClaimTransaction.cs#L16
if t.Type == transaction.ClaimType {
return 0
}
inputAmount := util.Fixed8FromInt64(0)
refs, err := bc.References(t)
if err != nil {
return inputAmount
}
for i := range refs {
if refs[i].Out.AssetID == UtilityTokenID() {
inputAmount = inputAmount.Add(refs[i].Out.Amount)
}
}
outputAmount := util.Fixed8FromInt64(0)
for _, txOutput := range t.Outputs {
if txOutput.AssetID == UtilityTokenID() {
outputAmount = outputAmount.Add(txOutput.Amount)
}
}
return inputAmount.Sub(outputAmount).Sub(bc.SystemFee(t))
}
// SystemFee returns system fee.
func (bc *Blockchain) SystemFee(t *transaction.Transaction) util.Fixed8 {
if t.Type == transaction.InvocationType {
inv := t.Data.(*transaction.InvocationTX)
if inv.Version >= 1 {
return inv.Gas
}
}
return bc.GetConfig().SystemFee.TryGetValue(t.Type)
// FeePerByte returns transaction network fee per byte.
// TODO: should be implemented as part of PolicyContract
func (bc *Blockchain) FeePerByte() util.Fixed8 {
return util.Fixed8(1000)
}
// IsLowPriority checks given fee for being less than configured
@ -1199,14 +1176,25 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
if t.ValidUntilBlock <= height || t.ValidUntilBlock > height+transaction.MaxValidUntilBlockIncrement {
return errors.Errorf("transaction has expired. ValidUntilBlock = %d, current height = %d", t.ValidUntilBlock, height)
}
if io.GetVarSize(t) > transaction.MaxTransactionSize {
balance := bc.GetUtilityTokenBalance(t.Sender)
need := t.SystemFee.Add(t.NetworkFee)
if balance.LessThan(need) {
return errors.Errorf("insufficient funds: balance is %v, need: %v", balance, need)
}
size := io.GetVarSize(t)
if size > transaction.MaxTransactionSize {
return errors.Errorf("invalid transaction size = %d. It shoud be less then MaxTransactionSize = %d", io.GetVarSize(t), transaction.MaxTransactionSize)
}
needNetworkFee := util.Fixed8(int64(size) * int64(bc.FeePerByte()))
netFee := t.NetworkFee.Sub(needNetworkFee)
if netFee < 0 {
return errors.Errorf("insufficient funds: net fee is %v, need %v", t.NetworkFee, needNetworkFee)
}
if transaction.HaveDuplicateInputs(t.Inputs) {
return errors.New("invalid transaction's inputs")
}
if block == nil {
if ok := bc.memPool.Verify(t); !ok {
if ok := bc.memPool.Verify(t, bc); !ok {
return errors.New("invalid transaction due to conflicts with the memory pool")
}
}
@ -1382,9 +1370,8 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error {
txSize := io.GetVarSize(t)
maxFree := bc.config.MaxFreeTransactionSize
if maxFree != 0 && txSize > maxFree {
netFee := bc.NetworkFee(t)
if bc.IsLowPriority(netFee) ||
netFee < util.Fixed8FromFloat(bc.config.FeePerExtraByte)*util.Fixed8(txSize-maxFree) {
if bc.IsLowPriority(t.NetworkFee) ||
t.NetworkFee < util.Fixed8FromFloat(bc.config.FeePerExtraByte)*util.Fixed8(txSize-maxFree) {
return ErrPolicy
}
}
@ -1441,7 +1428,7 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction, results []*trans
if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != UtilityTokenID() {
return errors.New("tx destroys non-utility token")
}
sysfee := bc.SystemFee(t)
sysfee := t.SystemFee
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())
@ -1711,6 +1698,16 @@ func (bc *Blockchain) verifyHeaderWitnesses(currHeader, prevHeader *block.Header
return bc.verifyHashAgainstScript(hash, &currHeader.Script, interopCtx, true)
}
// GoverningTokenHash returns the governing token (NEO) native contract hash.
func (bc *Blockchain) GoverningTokenHash() util.Uint160 {
return bc.contracts.NEO.Hash
}
// UtilityTokenHash returns the utility token (GAS) native contract hash.
func (bc *Blockchain) UtilityTokenHash() util.Uint160 {
return bc.contracts.GAS.Hash
}
func hashAndIndexToBytes(h util.Uint256, index uint32) []byte {
buf := io.NewBufBinWriter()
buf.WriteBytes(h.BytesLE())

View file

@ -167,10 +167,23 @@ func TestCreateBasicChain(t *testing.T) {
bc := newTestChain(t)
defer bc.Close()
// Move almost all NEO to one simple account.
txMoveNeo := transaction.NewContractTX()
gasHash := bc.contracts.GAS.Hash
t.Logf("native GAS hash: %v", gasHash)
priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash()
// Move almost all NEO and some nep5 GAS to one simple account.
txMoveNeo := newNEP5Transfer(gasHash, neoOwner, priv0ScriptHash, 1000000000)
txMoveNeo.ValidUntilBlock = validUntilBlock
txMoveNeo.Nonce = getNextNonce()
txMoveNeo.Sender = neoOwner
txMoveNeo.Cosigners = []transaction.Cosigner{{
Account: neoOwner,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
}}
// use output of issue tx from genesis block as an input
genesisBlock, err := bc.GetBlock(bc.GetHeaderHash(0))
@ -181,15 +194,10 @@ func TestCreateBasicChain(t *testing.T) {
PrevHash: h,
PrevIndex: 0,
})
txMoveNeo.Sender = neoOwner
priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash()
txMoveNeo.AddOutput(&transaction.Output{
AssetID: GoverningTokenID(),
Amount: neoAmount,
ScriptHash: priv0.GetScriptHash(),
ScriptHash: priv0ScriptHash,
Position: 0,
})
txMoveNeo.AddOutput(&transaction.Output{
@ -198,7 +206,6 @@ func TestCreateBasicChain(t *testing.T) {
ScriptHash: neoOwner,
Position: 1,
})
txMoveNeo.Data = new(transaction.ContractTX)
require.NoError(t, signTx(bc, txMoveNeo))
b := bc.newBlock(txMoveNeo)
require.NoError(t, bc.AddBlock(b))
@ -233,6 +240,7 @@ func TestCreateBasicChain(t *testing.T) {
Position: 0,
})
txNeoRound.Data = new(transaction.ContractTX)
require.NoError(t, addNetworkFee(bc, txNeoRound, acc0))
require.NoError(t, acc0.SignTx(txNeoRound))
b = bc.newBlock(txNeoRound)
require.NoError(t, bc.AddBlock(b))
@ -257,6 +265,7 @@ func TestCreateBasicChain(t *testing.T) {
ScriptHash: priv0.GetScriptHash(),
Position: 0,
})
require.NoError(t, addNetworkFee(bc, txClaim, acc0))
require.NoError(t, acc0.SignTx(txClaim))
b = bc.newBlock(txClaim)
require.NoError(t, bc.AddBlock(b))
@ -300,6 +309,7 @@ func TestCreateBasicChain(t *testing.T) {
Position: 0,
})
gasOwned -= invFee
require.NoError(t, addNetworkFee(bc, txDeploy, acc0))
require.NoError(t, acc0.SignTx(txDeploy))
b = bc.newBlock(txDeploy)
require.NoError(t, bc.AddBlock(b))
@ -313,6 +323,7 @@ func TestCreateBasicChain(t *testing.T) {
txInv.Nonce = getNextNonce()
txInv.ValidUntilBlock = validUntilBlock
txInv.Sender = priv0ScriptHash
require.NoError(t, addNetworkFee(bc, txInv, acc0))
require.NoError(t, acc0.SignTx(txInv))
b = bc.newBlock(txInv)
require.NoError(t, bc.AddBlock(b))
@ -339,6 +350,7 @@ func TestCreateBasicChain(t *testing.T) {
ScriptHash: priv0.GetScriptHash(),
})
require.NoError(t, addNetworkFee(bc, txNeo0to1, acc0))
require.NoError(t, acc0.SignTx(txNeo0to1))
b = bc.newBlock(txNeo0to1)
require.NoError(t, bc.AddBlock(b))
@ -350,24 +362,45 @@ func TestCreateBasicChain(t *testing.T) {
initTx.Nonce = getNextNonce()
initTx.ValidUntilBlock = validUntilBlock
initTx.Sender = priv0ScriptHash
require.NoError(t, addNetworkFee(bc, initTx, acc0))
require.NoError(t, acc0.SignTx(initTx))
transferTx := newNEP5Transfer(sh, sh, priv0.GetScriptHash(), 1000)
transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock
transferTx.Sender = priv0ScriptHash
transferTx.Cosigners = []transaction.Cosigner{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
},
}
require.NoError(t, addNetworkFee(bc, transferTx, acc0))
require.NoError(t, acc0.SignTx(transferTx))
b = bc.newBlock(initTx, transferTx)
require.NoError(t, bc.AddBlock(b))
t.Logf("recieveRublesTx: %v", transferTx.Hash().StringBE())
transferTx = newNEP5Transfer(sh, priv0.GetScriptHash(), priv1.GetScriptHash(), 123)
transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock
transferTx.Sender = priv0ScriptHash
transferTx.Cosigners = []transaction.Cosigner{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
},
}
require.NoError(t, addNetworkFee(bc, transferTx, acc0))
require.NoError(t, acc0.SignTx(transferTx))
b = bc.newBlock(transferTx)
require.NoError(t, bc.AddBlock(b))
t.Logf("sendRublesTx: %v", transferTx.Hash().StringBE())
if saveChain {
outStream, err := os.Create(prefix + "testblocks.acc")
@ -407,6 +440,7 @@ func TestCreateBasicChain(t *testing.T) {
Position: 0,
})
txNeoRound.Data = new(transaction.ContractTX)
require.NoError(t, addNetworkFee(bc, txNeoRound, acc0))
require.NoError(t, acc0.SignTx(txNeoRound))
bw := io.NewBufBinWriter()
txNeoRound.EncodeBinary(bw.BinWriter)
@ -439,6 +473,11 @@ func signTx(bc *Blockchain, txs ...*transaction.Transaction) error {
return errors.Wrap(err, "fail to sign tx")
}
for _, tx := range txs {
size := io.GetVarSize(tx)
netFee, sizeDelta := CalculateNetworkFee(rawScript)
tx.NetworkFee = tx.NetworkFee.Add(netFee)
size += sizeDelta
tx.NetworkFee = tx.NetworkFee.Add(util.Fixed8(int64(size) * int64(bc.FeePerByte())))
data := tx.GetSignedPart()
tx.Scripts = []transaction.Witness{{
InvocationScript: testchain.Sign(data),
@ -447,3 +486,20 @@ func signTx(bc *Blockchain, txs ...*transaction.Transaction) error {
}
return nil
}
func addNetworkFee(bc *Blockchain, tx *transaction.Transaction, sender *wallet.Account) error {
size := io.GetVarSize(tx)
netFee, sizeDelta := CalculateNetworkFee(sender.Contract.Script)
tx.NetworkFee += netFee
size += sizeDelta
for _, cosigner := range tx.Cosigners {
contract := bc.GetContractState(cosigner.Account)
if contract != nil {
netFee, sizeDelta = CalculateNetworkFee(contract.Script)
tx.NetworkFee += netFee
size += sizeDelta
}
}
tx.NetworkFee += util.Fixed8(int64(size) * int64(bc.FeePerByte()))
return nil
}

View file

@ -1,14 +1,12 @@
package mempool
import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// Feer is an interface that abstract the implementation of the fee calculation.
type Feer interface {
NetworkFee(t *transaction.Transaction) util.Fixed8
IsLowPriority(util.Fixed8) bool
FeePerByte(t *transaction.Transaction) util.Fixed8
SystemFee(t *transaction.Transaction) util.Fixed8
FeePerByte() util.Fixed8
GetUtilityTokenBalance(util.Uint160) util.Fixed8
}

View file

@ -27,8 +27,6 @@ var (
type item struct {
txn *transaction.Transaction
timeStamp time.Time
perByteFee util.Fixed8
netFee util.Fixed8
isLowPrio bool
}
@ -41,6 +39,13 @@ type TxWithFee struct {
Fee util.Fixed8
}
// utilityBalanceAndFees stores sender's balance and overall fees of
// sender's transactions which are currently in mempool
type utilityBalanceAndFees struct {
balance util.Fixed8
feeSum util.Fixed8
}
// Pool stores the unconfirms transactions.
type Pool struct {
lock sync.RWMutex
@ -48,6 +53,7 @@ type Pool struct {
verifiedTxes items
inputs []*transaction.Input
claims []*transaction.Input
fees map[util.Uint160]utilityBalanceAndFees
capacity int
}
@ -88,11 +94,11 @@ func (p *item) CompareTo(otherP *item) int {
}
// Fees sorted ascending.
if ret := p.perByteFee.CompareTo(otherP.perByteFee); ret != 0 {
if ret := p.txn.FeePerByte().CompareTo(otherP.txn.FeePerByte()); ret != 0 {
return ret
}
if ret := p.netFee.CompareTo(otherP.netFee); ret != 0 {
if ret := p.txn.NetworkFee.CompareTo(otherP.txn.NetworkFee); ret != 0 {
return ret
}
@ -158,17 +164,47 @@ func dropInputFromSortedSlice(slice *[]*transaction.Input, input *transaction.In
*slice = (*slice)[:len(*slice)-1]
}
// tryAddSendersFee tries to add system fee and network fee to the total sender`s fee in mempool
// and returns false if sender has not enough GAS to pay
func (mp *Pool) tryAddSendersFee(tx *transaction.Transaction, feer Feer) bool {
if !mp.checkBalanceAndUpdate(tx, feer) {
return false
}
mp.addSendersFee(tx)
return true
}
// checkBalanceAndUpdate returns true in case when sender has enough GAS to pay for
// the transaction and sets sender's balance value in mempool in case if it was not set
func (mp *Pool) checkBalanceAndUpdate(tx *transaction.Transaction, feer Feer) bool {
senderFee, ok := mp.fees[tx.Sender]
if !ok {
senderFee.balance = feer.GetUtilityTokenBalance(tx.Sender)
mp.fees[tx.Sender] = senderFee
}
needFee := senderFee.feeSum + tx.SystemFee + tx.NetworkFee
if senderFee.balance < needFee {
return false
}
return true
}
// addSendersFee adds system fee and network fee to the total sender`s fee in mempool
func (mp *Pool) addSendersFee(tx *transaction.Transaction) {
senderFee := mp.fees[tx.Sender]
senderFee.feeSum += tx.SystemFee + tx.NetworkFee
mp.fees[tx.Sender] = senderFee
}
// Add tries to add given transaction to the Pool.
func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
var pItem = &item{
txn: t,
timeStamp: time.Now().UTC(),
perByteFee: fee.FeePerByte(t),
netFee: fee.NetworkFee(t),
}
pItem.isLowPrio = fee.IsLowPriority(pItem.netFee)
pItem.isLowPrio = fee.IsLowPriority(pItem.txn.NetworkFee)
mp.lock.Lock()
if !mp.checkTxConflicts(t) {
if !mp.checkTxConflicts(t, fee) {
mp.lock.Unlock()
return ErrConflict
}
@ -206,6 +242,7 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
copy(mp.verifiedTxes[n+1:], mp.verifiedTxes[n:])
mp.verifiedTxes[n] = pItem
}
mp.addSendersFee(pItem.txn)
// For lots of inputs it might be easier to push them all and sort
// afterwards, but that requires benchmarking.
@ -241,6 +278,9 @@ func (mp *Pool) Remove(hash util.Uint256) {
} else if num == len(mp.verifiedTxes)-1 {
mp.verifiedTxes = mp.verifiedTxes[:num]
}
senderFee := mp.fees[it.txn.Sender]
senderFee.feeSum -= it.txn.SystemFee + it.txn.NetworkFee
mp.fees[it.txn.Sender] = senderFee
for i := range it.txn.Inputs {
dropInputFromSortedSlice(&mp.inputs, &it.txn.Inputs[i])
}
@ -258,15 +298,16 @@ func (mp *Pool) Remove(hash util.Uint256) {
// RemoveStale filters verified transactions through the given function keeping
// only the transactions for which it returns a true result. It's used to quickly
// drop part of the mempool that is now invalid after the block acceptance.
func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool) {
func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer) {
mp.lock.Lock()
// We can reuse already allocated slice
// because items are iterated one-by-one in increasing order.
newVerifiedTxes := mp.verifiedTxes[:0]
newInputs := mp.inputs[:0]
newClaims := mp.claims[:0]
mp.fees = make(map[util.Uint160]utilityBalanceAndFees) // it'd be nice to reuse existing map, but we can't easily clear it
for _, itm := range mp.verifiedTxes {
if isOK(itm.txn) {
if isOK(itm.txn) && mp.tryAddSendersFee(itm.txn, feer) {
newVerifiedTxes = append(newVerifiedTxes, itm)
for i := range itm.txn.Inputs {
newInputs = append(newInputs, &itm.txn.Inputs[i])
@ -299,6 +340,7 @@ func NewMemPool(capacity int) Pool {
verifiedMap: make(map[util.Uint256]*item),
verifiedTxes: make([]*item, 0, capacity),
capacity: capacity,
fees: make(map[util.Uint160]utilityBalanceAndFees),
}
}
@ -307,7 +349,7 @@ func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, util.F
mp.lock.RLock()
defer mp.lock.RUnlock()
if pItem, ok := mp.verifiedMap[hash]; ok {
return pItem.txn, pItem.netFee, ok
return pItem.txn, pItem.txn.NetworkFee, ok
}
return nil, 0, false
@ -323,7 +365,7 @@ func (mp *Pool) GetVerifiedTransactions() []TxWithFee {
for i := range mp.verifiedTxes {
t[i].Tx = mp.verifiedTxes[i].txn
t[i].Fee = mp.verifiedTxes[i].netFee
t[i].Fee = mp.verifiedTxes[i].txn.NetworkFee
}
return t
@ -342,10 +384,13 @@ func areInputsInPool(inputs []transaction.Input, pool []*transaction.Input) bool
}
// checkTxConflicts is an internal unprotected version of Verify.
func (mp *Pool) checkTxConflicts(tx *transaction.Transaction) bool {
func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) bool {
if areInputsInPool(tx.Inputs, mp.inputs) {
return false
}
if !mp.checkBalanceAndUpdate(tx, fee) {
return false
}
switch tx.Type {
case transaction.ClaimType:
claim := tx.Data.(*transaction.ClaimTX)
@ -368,8 +413,8 @@ func (mp *Pool) checkTxConflicts(tx *transaction.Transaction) bool {
// Verify verifies if the inputs of a transaction tx are already used in any other transaction in the memory pool.
// If yes, the transaction tx is not a valid transaction and the function return false.
// If no, the transaction tx is a valid transaction and the function return true.
func (mp *Pool) Verify(tx *transaction.Transaction) bool {
func (mp *Pool) Verify(tx *transaction.Transaction, feer Feer) bool {
mp.lock.RLock()
defer mp.lock.RUnlock()
return mp.checkTxConflicts(tx)
return mp.checkTxConflicts(tx, feer)
}

View file

@ -13,25 +13,19 @@ import (
type FeerStub struct {
lowPriority bool
sysFee util.Fixed8
netFee util.Fixed8
perByteFee util.Fixed8
}
func (fs *FeerStub) NetworkFee(*transaction.Transaction) util.Fixed8 {
return fs.netFee
feePerByte util.Fixed8
}
func (fs *FeerStub) IsLowPriority(util.Fixed8) bool {
return fs.lowPriority
}
func (fs *FeerStub) FeePerByte(*transaction.Transaction) util.Fixed8 {
return fs.perByteFee
func (fs *FeerStub) FeePerByte() util.Fixed8 {
return fs.feePerByte
}
func (fs *FeerStub) SystemFee(*transaction.Transaction) util.Fixed8 {
return fs.sysFee
func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) util.Fixed8 {
return util.Fixed8FromInt64(10000)
}
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
@ -121,7 +115,7 @@ func TestMemPoolAddRemoveWithInputsAndClaims(t *testing.T) {
return false
}
return true
})
}, &FeerStub{})
assert.Equal(t, len(txm1.Inputs), len(mp.inputs))
assert.True(t, sort.SliceIsSorted(mp.inputs, mpLessInputs))
assert.Equal(t, len(claim2.Claims), len(mp.claims))
@ -134,24 +128,24 @@ func TestMemPoolVerifyInputs(t *testing.T) {
tx.Nonce = 1
inhash1 := random.Uint256()
tx.Inputs = append(tx.Inputs, transaction.Input{PrevHash: inhash1, PrevIndex: 0})
require.Equal(t, true, mp.Verify(tx))
require.Equal(t, true, mp.Verify(tx, &FeerStub{}))
require.NoError(t, mp.Add(tx, &FeerStub{}))
tx2 := transaction.NewContractTX()
tx2.Nonce = 2
inhash2 := random.Uint256()
tx2.Inputs = append(tx2.Inputs, transaction.Input{PrevHash: inhash2, PrevIndex: 0})
require.Equal(t, true, mp.Verify(tx2))
require.Equal(t, true, mp.Verify(tx2, &FeerStub{}))
require.NoError(t, mp.Add(tx2, &FeerStub{}))
tx3 := transaction.NewContractTX()
tx3.Nonce = 3
// Different index number, but the same PrevHash as in tx1.
tx3.Inputs = append(tx3.Inputs, transaction.Input{PrevHash: inhash1, PrevIndex: 1})
require.Equal(t, true, mp.Verify(tx3))
require.Equal(t, true, mp.Verify(tx3, &FeerStub{}))
// The same input as in tx2.
tx3.Inputs = append(tx3.Inputs, transaction.Input{PrevHash: inhash2, PrevIndex: 0})
require.Equal(t, false, mp.Verify(tx3))
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))
require.Error(t, mp.Add(tx3, &FeerStub{}))
}
@ -166,30 +160,30 @@ func TestMemPoolVerifyClaims(t *testing.T) {
claim1.Claims = append(claim1.Claims, transaction.Input{PrevHash: hash1, PrevIndex: uint16(i)})
claim1.Claims = append(claim1.Claims, transaction.Input{PrevHash: hash2, PrevIndex: uint16(i)})
}
require.Equal(t, true, mp.Verify(tx1))
require.Equal(t, true, mp.Verify(tx1, &FeerStub{}))
require.NoError(t, mp.Add(tx1, &FeerStub{}))
tx2, claim2 := newClaimTX()
for i := 0; i < 10; i++ {
claim2.Claims = append(claim2.Claims, transaction.Input{PrevHash: hash2, PrevIndex: uint16(i + 10)})
}
require.Equal(t, true, mp.Verify(tx2))
require.Equal(t, true, mp.Verify(tx2, &FeerStub{}))
require.NoError(t, mp.Add(tx2, &FeerStub{}))
tx3, claim3 := newClaimTX()
claim3.Claims = append(claim3.Claims, transaction.Input{PrevHash: hash1, PrevIndex: 0})
require.Equal(t, false, mp.Verify(tx3))
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))
require.Error(t, mp.Add(tx3, &FeerStub{}))
}
func TestMemPoolVerifyIssue(t *testing.T) {
mp := NewMemPool(50)
tx1 := newIssueTX()
require.Equal(t, true, mp.Verify(tx1))
require.Equal(t, true, mp.Verify(tx1, &FeerStub{}))
require.NoError(t, mp.Add(tx1, &FeerStub{}))
tx2 := newIssueTX()
require.Equal(t, false, mp.Verify(tx2))
require.Equal(t, false, mp.Verify(tx2, &FeerStub{}))
require.Error(t, mp.Add(tx2, &FeerStub{}))
}
@ -235,18 +229,27 @@ func TestOverCapacity(t *testing.T) {
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
// Fees are also prioritized.
fs.netFee = util.Fixed8FromFloat(0.0001)
for i := 0; i < mempoolSize-1; i++ {
tx := transaction.NewContractTX()
tx.Attributes = append(tx.Attributes, transaction.Attribute{
Usage: transaction.Hash1,
Data: util.Uint256{1, 2, 3, 4}.BytesBE(),
})
tx.NetworkFee = util.Fixed8FromFloat(0.0001)
tx.Nonce = txcnt
txcnt++
// size is 84, networkFee is 0.0001 => feePerByte is 0.00000119
require.NoError(t, mp.Add(tx, fs))
require.Equal(t, mempoolSize, mp.Count())
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
}
// Less prioritized txes are not allowed anymore.
fs.netFee = util.Fixed8FromFloat(0.00001)
tx := transaction.NewContractTX()
tx.Attributes = append(tx.Attributes, transaction.Attribute{
Usage: transaction.Hash1,
Data: util.Uint256{1, 2, 3, 4}.BytesBE(),
})
tx.NetworkFee = util.Fixed8FromFloat(0.00001)
tx.Nonce = txcnt
txcnt++
require.Error(t, mp.Add(tx, fs))
@ -257,10 +260,12 @@ func TestOverCapacity(t *testing.T) {
require.True(t, mp.ContainsKey(claim.Hash()))
// Low net fee, but higher per-byte fee is still a better combination.
fs.perByteFee = util.Fixed8FromFloat(0.001)
tx = transaction.NewContractTX()
tx.Nonce = txcnt
tx.NetworkFee = util.Fixed8FromFloat(0.00007)
txcnt++
// size is 51 (no attributes), networkFee is 0.00007 (<0.0001)
// => feePerByte is 0.00000137 (>0.00000119)
require.NoError(t, mp.Add(tx, fs))
require.Equal(t, mempoolSize, mp.Count())
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
@ -334,7 +339,7 @@ func TestRemoveStale(t *testing.T) {
}
}
return false
})
}, &FeerStub{})
require.Equal(t, mempoolSize/2, mp.Count())
verTxes := mp.GetVerifiedTransactions()
for _, txf := range verTxes {
@ -342,3 +347,76 @@ func TestRemoveStale(t *testing.T) {
require.Contains(t, txes2, txf.Tx)
}
}
func TestMemPoolFees(t *testing.T) {
mp := NewMemPool(10)
sender0 := util.Uint160{1, 2, 3}
tx0 := transaction.NewContractTX()
tx0.NetworkFee = util.Fixed8FromInt64(11000)
tx0.Sender = sender0
// insufficient funds to add transaction, but balance should be stored
require.Equal(t, false, mp.Verify(tx0, &FeerStub{}))
require.Error(t, mp.Add(tx0, &FeerStub{}))
require.Equal(t, 1, len(mp.fees))
require.Equal(t, utilityBalanceAndFees{
balance: util.Fixed8FromInt64(10000),
feeSum: 0,
}, mp.fees[sender0])
// no problems with adding another transaction with lower fee
tx1 := transaction.NewContractTX()
tx1.NetworkFee = util.Fixed8FromInt64(7000)
tx1.Sender = sender0
require.NoError(t, mp.Add(tx1, &FeerStub{}))
require.Equal(t, 1, len(mp.fees))
require.Equal(t, utilityBalanceAndFees{
balance: util.Fixed8FromInt64(10000),
feeSum: util.Fixed8FromInt64(7000),
}, mp.fees[sender0])
// balance shouldn't change after adding one more transaction
tx2 := transaction.NewContractTX()
tx2.NetworkFee = util.Fixed8FromFloat(3000)
tx2.Sender = sender0
require.NoError(t, mp.Add(tx2, &FeerStub{}))
require.Equal(t, 2, len(mp.verifiedTxes))
require.Equal(t, 1, len(mp.fees))
require.Equal(t, utilityBalanceAndFees{
balance: util.Fixed8FromInt64(10000),
feeSum: util.Fixed8FromInt64(10000),
}, mp.fees[sender0])
// can't add more transactions as we don't have enough GAS
tx3 := transaction.NewContractTX()
tx3.NetworkFee = util.Fixed8FromFloat(0.5)
tx3.Sender = sender0
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))
require.Error(t, mp.Add(tx3, &FeerStub{}))
require.Equal(t, 1, len(mp.fees))
require.Equal(t, utilityBalanceAndFees{
balance: util.Fixed8FromInt64(10000),
feeSum: util.Fixed8FromInt64(10000),
}, mp.fees[sender0])
// check whether sender's fee updates correctly
mp.RemoveStale(func(t *transaction.Transaction) bool {
if t == tx2 {
return true
}
return false
}, &FeerStub{})
require.Equal(t, 1, len(mp.fees))
require.Equal(t, utilityBalanceAndFees{
balance: util.Fixed8FromInt64(10000),
feeSum: util.Fixed8FromFloat(3000),
}, mp.fees[sender0])
// there should be nothing left
mp.RemoveStale(func(t *transaction.Transaction) bool {
if t == tx3 {
return true
}
return false
}, &FeerStub{})
require.Equal(t, 0, len(mp.fees))
}

View file

@ -2,6 +2,7 @@ package native
import (
"errors"
"fmt"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
@ -28,21 +29,22 @@ const initialGAS = 30000000
// NewGAS returns GAS native contract.
func NewGAS() *GAS {
g := &GAS{}
nep5 := newNEP5Native(gasSyscallName)
nep5.name = "GAS"
nep5.symbol = "gas"
nep5.decimals = 8
nep5.factor = GASFactor
nep5.onPersist = chainOnPersist(g.onPersist, g.OnPersist)
nep5.incBalance = g.increaseBalance
g := &GAS{nep5TokenNative: *nep5}
g.nep5TokenNative = *nep5
desc := newDescriptor("getSysFeeAmount", smartcontract.IntegerType,
manifest.NewParameter("index", smartcontract.IntegerType))
md := newMethodAndPrice(g.getSysFeeAmount, 1, smartcontract.NoneFlag)
g.AddMethod(md, desc, true)
g.onPersist = chainOnPersist(g.onPersist, g.OnPersist)
g.incBalance = g.increaseBalance
return g
}
@ -79,15 +81,20 @@ func (g *GAS) Initialize(ic *interop.Context) error {
// OnPersist implements Contract interface.
func (g *GAS) OnPersist(ic *interop.Context) error {
//for _ ,tx := range ic.block.Transactions {
// g.burn(ic, tx.Sender, tx.SystemFee + tx.NetworkFee)
//}
//validators := g.NEO.getNextBlockValidators(ic)
//var netFee util.Fixed8
//for _, tx := range ic.block.Transactions {
// netFee += tx.NetworkFee
//}
//g.mint(ic, <primary>, netFee)
for _, tx := range ic.Block.Transactions {
absAmount := big.NewInt(int64(tx.SystemFee + tx.NetworkFee))
g.burn(ic, tx.Sender, absAmount)
}
validators, err := g.NEO.GetValidatorsInternal(ic.Chain, ic.DAO)
if err != nil {
return fmt.Errorf("cannot get block validators: %v", err)
}
primary := validators[ic.Block.ConsensusData.PrimaryIndex].GetScriptHash()
var netFee util.Fixed8
for _, tx := range ic.Block.Transactions {
netFee += tx.NetworkFee
}
g.mint(ic, primary, big.NewInt(int64(netFee)))
return nil
}

View file

@ -60,13 +60,16 @@ func makeValidatorKey(key *keys.PublicKey) []byte {
// NewNEO returns NEO native contract.
func NewNEO() *NEO {
n := &NEO{}
nep5 := newNEP5Native(neoSyscallName)
nep5.name = "NEO"
nep5.symbol = "neo"
nep5.decimals = 0
nep5.factor = 1
nep5.onPersist = chainOnPersist(n.onPersist, n.OnPersist)
nep5.incBalance = n.increaseBalance
n := &NEO{nep5TokenNative: *nep5}
n.nep5TokenNative = *nep5
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
manifest.NewParameter("account", smartcontract.Hash160Type),
@ -97,9 +100,6 @@ func NewNEO() *NEO {
md = newMethodAndPrice(n.getNextBlockValidators, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
n.onPersist = chainOnPersist(n.onPersist, n.OnPersist)
n.incBalance = n.increaseBalance
return n
}
@ -251,7 +251,11 @@ func (n *NEO) vote(ic *interop.Context, args []vm.StackItem) vm.StackItem {
// VoteInternal votes from account h for validarors specified in pubs.
func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.PublicKeys) error {
ok, err := runtime.CheckHashedWitness(ic, neoScriptHash{hash: n.Hash}, h)
ok, err := runtime.CheckHashedWitness(ic, nep5ScriptHash{
callingScriptHash: util.Uint160{},
entryScriptHash: n.Hash,
currentScriptHash: n.Hash,
}, h)
if err != nil {
return err
} else if !ok {
@ -479,24 +483,3 @@ func toPublicKey(s vm.StackItem) *keys.PublicKey {
}
return pub
}
// scriptHash is an auxiliary structure which implements ScriptHashGetter
// interface over NEO native contract and is used for runtime.CheckHashedWitness
type neoScriptHash struct {
hash util.Uint160
}
// GetCallingScriptHash implements ScriptHashGetter interface
func (s neoScriptHash) GetCallingScriptHash() util.Uint160 {
return util.Uint160{}
}
// GetEntryScriptHash implements ScriptHashGetter interface
func (s neoScriptHash) GetEntryScriptHash() util.Uint160 {
return s.hash
}
// GetCurrentScriptHash implements ScriptHashGetter interface
func (s neoScriptHash) GetCurrentScriptHash() util.Uint160 {
return s.hash
}

View file

@ -5,6 +5,7 @@ import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
@ -147,6 +148,17 @@ func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, a
return errors.New("negative amount")
}
ok, err := runtime.CheckHashedWitness(ic, nep5ScriptHash{
callingScriptHash: c.Hash,
entryScriptHash: c.Hash,
currentScriptHash: c.Hash,
}, from)
if err != nil {
return err
} else if !ok {
return errors.New("invalid signature")
}
keyFrom := makeAccountKey(from)
siFrom := ic.DAO.GetStorageItem(c.Hash, keyFrom)
if siFrom == nil {
@ -205,9 +217,7 @@ func (c *nep5TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.
}
func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) {
if sign := amount.Sign(); sign == -1 {
panic("negative amount")
} else if sign == 0 {
if amount.Sign() == 0 {
return
}
@ -270,3 +280,26 @@ func toUint160(s vm.StackItem) util.Uint160 {
}
return u
}
// scriptHash is an auxiliary structure which implements ScriptHashGetter
// interface over NEP5 native contract and is used for runtime.CheckHashedWitness
type nep5ScriptHash struct {
callingScriptHash util.Uint160
entryScriptHash util.Uint160
currentScriptHash util.Uint160
}
// GetCallingScriptHash implements ScriptHashGetter interface
func (s nep5ScriptHash) GetCallingScriptHash() util.Uint160 {
return s.callingScriptHash
}
// GetEntryScriptHash implements ScriptHashGetter interface
func (s nep5ScriptHash) GetEntryScriptHash() util.Uint160 {
return s.entryScriptHash
}
// GetCurrentScriptHash implements ScriptHashGetter interface
func (s nep5ScriptHash) GetCurrentScriptHash() util.Uint160 {
return s.currentScriptHash
}

202
pkg/core/opcode_price.go Normal file
View file

@ -0,0 +1,202 @@
package core
import (
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
)
// opcodePrice returns the deployment prices of specified opcodes
func opcodePrice(opcodes ...opcode.Opcode) util.Fixed8 {
var result util.Fixed8
for _, op := range opcodes {
result += util.Fixed8(prices[op])
}
return result
}
var prices = map[opcode.Opcode]int{
opcode.PUSHINT8: 30,
opcode.PUSHINT32: 30,
opcode.PUSHINT64: 30,
opcode.PUSHINT16: 30,
opcode.PUSHINT128: 120,
opcode.PUSHINT256: 120,
opcode.PUSHA: 120,
opcode.PUSHNULL: 30,
opcode.PUSHDATA1: 180,
opcode.PUSHDATA2: 13000,
opcode.PUSHDATA4: 110000,
opcode.PUSHM1: 30,
opcode.PUSH0: 30,
opcode.PUSH1: 30,
opcode.PUSH2: 30,
opcode.PUSH3: 30,
opcode.PUSH4: 30,
opcode.PUSH5: 30,
opcode.PUSH6: 30,
opcode.PUSH7: 30,
opcode.PUSH8: 30,
opcode.PUSH9: 30,
opcode.PUSH10: 30,
opcode.PUSH11: 30,
opcode.PUSH12: 30,
opcode.PUSH13: 30,
opcode.PUSH14: 30,
opcode.PUSH15: 30,
opcode.PUSH16: 30,
opcode.NOP: 30,
opcode.JMP: 70,
opcode.JMPL: 70,
opcode.JMPIF: 70,
opcode.JMPIFL: 70,
opcode.JMPIFNOT: 70,
opcode.JMPIFNOTL: 70,
opcode.JMPEQ: 70,
opcode.JMPEQL: 70,
opcode.JMPNE: 70,
opcode.JMPNEL: 70,
opcode.JMPGT: 70,
opcode.JMPGTL: 70,
opcode.JMPGE: 70,
opcode.JMPGEL: 70,
opcode.JMPLT: 70,
opcode.JMPLTL: 70,
opcode.JMPLE: 70,
opcode.JMPLEL: 70,
opcode.CALL: 22000,
opcode.CALLL: 22000,
opcode.CALLA: 22000,
opcode.ABORT: 30,
opcode.ASSERT: 30,
opcode.THROW: 22000,
//opcode.TRY: 100,
//opcode.TRY_L: 100,
//opcode.ENDTRY: 100,
//opcode.ENDTRY_L: 100,
//opcode.ENDFINALLY: 100,
opcode.RET: 0,
opcode.SYSCALL: 0,
opcode.DEPTH: 60,
opcode.DROP: 60,
opcode.NIP: 60,
opcode.XDROP: 400,
opcode.CLEAR: 400,
opcode.DUP: 60,
opcode.OVER: 60,
opcode.PICK: 60,
opcode.TUCK: 60,
opcode.SWAP: 60,
opcode.ROT: 60,
opcode.ROLL: 400,
opcode.REVERSE3: 60,
opcode.REVERSE4: 60,
opcode.REVERSEN: 400,
opcode.INITSSLOT: 400,
opcode.INITSLOT: 800,
opcode.LDSFLD0: 60,
opcode.LDSFLD1: 60,
opcode.LDSFLD2: 60,
opcode.LDSFLD3: 60,
opcode.LDSFLD4: 60,
opcode.LDSFLD5: 60,
opcode.LDSFLD6: 60,
opcode.LDSFLD: 60,
opcode.STSFLD0: 60,
opcode.STSFLD1: 60,
opcode.STSFLD2: 60,
opcode.STSFLD3: 60,
opcode.STSFLD4: 60,
opcode.STSFLD5: 60,
opcode.STSFLD6: 60,
opcode.STSFLD: 60,
opcode.LDLOC0: 60,
opcode.LDLOC1: 60,
opcode.LDLOC2: 60,
opcode.LDLOC3: 60,
opcode.LDLOC4: 60,
opcode.LDLOC5: 60,
opcode.LDLOC6: 60,
opcode.LDLOC: 60,
opcode.STLOC0: 60,
opcode.STLOC1: 60,
opcode.STLOC2: 60,
opcode.STLOC3: 60,
opcode.STLOC4: 60,
opcode.STLOC5: 60,
opcode.STLOC6: 60,
opcode.STLOC: 60,
opcode.LDARG0: 60,
opcode.LDARG1: 60,
opcode.LDARG2: 60,
opcode.LDARG3: 60,
opcode.LDARG4: 60,
opcode.LDARG5: 60,
opcode.LDARG6: 60,
opcode.LDARG: 60,
opcode.STARG0: 60,
opcode.STARG1: 60,
opcode.STARG2: 60,
opcode.STARG3: 60,
opcode.STARG4: 60,
opcode.STARG5: 60,
opcode.STARG6: 60,
opcode.STARG: 60,
opcode.NEWBUFFER: 80000,
opcode.MEMCPY: 80000,
opcode.CAT: 80000,
opcode.SUBSTR: 80000,
opcode.LEFT: 80000,
opcode.RIGHT: 80000,
opcode.INVERT: 100,
opcode.AND: 200,
opcode.OR: 200,
opcode.XOR: 200,
opcode.EQUAL: 200,
opcode.NOTEQUAL: 200,
opcode.SIGN: 100,
opcode.ABS: 100,
opcode.NEGATE: 100,
opcode.INC: 100,
opcode.DEC: 100,
opcode.ADD: 200,
opcode.SUB: 200,
opcode.MUL: 300,
opcode.DIV: 300,
opcode.MOD: 300,
opcode.SHL: 300,
opcode.SHR: 300,
opcode.NOT: 100,
opcode.BOOLAND: 200,
opcode.BOOLOR: 200,
opcode.NZ: 100,
opcode.NUMEQUAL: 200,
opcode.NUMNOTEQUAL: 200,
opcode.LT: 200,
opcode.LTE: 200,
opcode.GT: 200,
opcode.GTE: 200,
opcode.MIN: 200,
opcode.MAX: 200,
opcode.WITHIN: 200,
opcode.PACK: 7000,
opcode.UNPACK: 7000,
opcode.NEWARRAY0: 400,
opcode.NEWARRAY: 15000,
opcode.NEWARRAYT: 15000,
opcode.NEWSTRUCT0: 400,
opcode.NEWSTRUCT: 15000,
opcode.NEWMAP: 200,
opcode.SIZE: 150,
opcode.HASKEY: 270000,
opcode.KEYS: 500,
opcode.VALUES: 7000,
opcode.PICKITEM: 270000,
opcode.APPEND: 15000,
opcode.SETITEM: 270000,
opcode.REVERSEITEMS: 500,
opcode.REMOVE: 500,
opcode.CLEARITEMS: 400,
opcode.ISNULL: 60,
opcode.ISTYPE: 60,
opcode.CONVERT: 80000,
}

View file

@ -38,6 +38,12 @@ type Transaction struct {
// Address signed the transaction.
Sender util.Uint160
// Fee to be burned.
SystemFee util.Fixed8
// Fee to be distributed to consensus nodes.
NetworkFee util.Fixed8
// Maximum blockchain height exceeding which
// transaction should fail verification.
ValidUntilBlock uint32
@ -119,7 +125,20 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) {
t.Version = uint8(br.ReadB())
t.Nonce = br.ReadU32LE()
t.Sender.DecodeBinary(br)
t.SystemFee.DecodeBinary(br)
if t.SystemFee < 0 {
br.Err = errors.New("negative system fee")
return
}
t.NetworkFee.DecodeBinary(br)
if t.NetworkFee < 0 {
br.Err = errors.New("negative network fee")
return
}
if t.NetworkFee+t.SystemFee < t.SystemFee {
br.Err = errors.New("too big fees: int 64 overflow")
return
}
t.ValidUntilBlock = br.ReadU32LE()
t.decodeData(br)
@ -192,6 +211,8 @@ func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
bw.WriteB(byte(t.Version))
bw.WriteU32LE(t.Nonce)
t.Sender.EncodeBinary(bw)
t.SystemFee.EncodeBinary(bw)
t.NetworkFee.EncodeBinary(bw)
bw.WriteU32LE(t.ValidUntilBlock)
// Underlying TXer.
@ -268,6 +289,12 @@ func NewTransactionFromBytes(b []byte) (*Transaction, error) {
return tx, nil
}
// FeePerByte returns NetworkFee of the transaction divided by
// its size
func (t *Transaction) FeePerByte() util.Fixed8 {
return util.Fixed8(int64(t.NetworkFee) / int64(io.GetVarSize(t)))
}
// transactionJSON is a wrapper for Transaction and
// used for correct marhalling of transaction.Data
type transactionJSON struct {
@ -277,6 +304,8 @@ type transactionJSON struct {
Version uint8 `json:"version"`
Nonce uint32 `json:"nonce"`
Sender string `json:"sender"`
SystemFee util.Fixed8 `json:"sys_fee"`
NetworkFee util.Fixed8 `json:"net_fee"`
ValidUntilBlock uint32 `json:"valid_until_block"`
Attributes []Attribute `json:"attributes"`
Cosigners []Cosigner `json:"cosigners"`
@ -305,6 +334,8 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
Inputs: t.Inputs,
Outputs: t.Outputs,
Scripts: t.Scripts,
SystemFee: t.SystemFee,
NetworkFee: t.NetworkFee,
}
switch t.Type {
case ClaimType:
@ -341,6 +372,8 @@ func (t *Transaction) UnmarshalJSON(data []byte) error {
t.Inputs = tx.Inputs
t.Outputs = tx.Outputs
t.Scripts = tx.Scripts
t.SystemFee = tx.SystemFee
t.NetworkFee = tx.NetworkFee
sender, err := address.StringToUint160(tx.Sender)
if err != nil {
return errors.New("cannot unmarshal tx: bad sender")

View file

@ -11,6 +11,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
)
@ -24,6 +25,10 @@ var (
// utility (GAS) token. It's a part of the genesis block. It's mostly
// useful for its hash that represents GAS asset ID.
utilityTokenTX transaction.Transaction
// ecdsaVerifyInteropPrice returns the price of Neo.Crypto.ECDsaVerify
// syscall to calculate NetworkFee for transaction
ecdsaVerifyInteropPrice = util.Fixed8(100000)
)
// createGenesisBlock creates a genesis block based on the given configuration.
@ -191,3 +196,33 @@ func headerSliceReverse(dest []*block.Header) {
dest[i], dest[j] = dest[j], dest[i]
}
}
// CalculateNetworkFee returns network fee for transaction
func CalculateNetworkFee(script []byte) (util.Fixed8, int) {
var (
netFee util.Fixed8
size int
)
if vm.IsSignatureContract(script) {
size += 67 + io.GetVarSize(script)
netFee = netFee.Add(opcodePrice(opcode.PUSHDATA1, opcode.PUSHNULL).Add(ecdsaVerifyInteropPrice))
} else if n, pubs, ok := vm.ParseMultiSigContract(script); ok {
m := len(pubs)
sizeInv := 66 * m
size += io.GetVarSize(sizeInv) + sizeInv + io.GetVarSize(script)
netFee = netFee.Add(calculateMultisigFee(m)).Add(calculateMultisigFee(n))
netFee = netFee.Add(opcodePrice(opcode.PUSHNULL)).Add(util.Fixed8(int64(ecdsaVerifyInteropPrice) * int64(n)))
} else {
// We can support more contract types in the future.
}
return netFee, size
}
func calculateMultisigFee(n int) util.Fixed8 {
result := util.Fixed8(int64(opcodePrice(opcode.PUSHDATA1)) * int64(n))
bw := io.NewBufBinWriter()
emit.Int(bw.BinWriter, int64(n))
// it's a hack because prices of small PUSH* opcodes are equal
result = result.Add(opcodePrice(opcode.Opcode(bw.Bytes()[0])))
return result
}

View file

@ -20,7 +20,7 @@ func TestGenesisBlockMainNet(t *testing.T) {
// have been changed. Consequently, hash of the genesis block has been changed.
// Update expected genesis block hash for better times.
// Old hash is "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf"
expect := "56bb42c251ea2b216c5ee8306e94fe040613bc626a3813aa32fe07e7607b3a1a"
expect := "1d4156d233220b893797a684fbb827bb2163b5042edd10653bbc1b2769adbb8d"
assert.Equal(t, expect, block.Hash().StringLE())
}
@ -47,7 +47,7 @@ func TestUtilityTokenTX(t *testing.T) {
//TODO: After we added Nonce field to transaction.Transaction, UtilityTockenTx hash
// has been changed. Update it for better times.
// Old hash is "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
expect := "8ef63ccd6f4ea20a93e7f4e84b2d43f778077612b241d617e42e1750cca4f2c5"
expect := "f882fb865bab84b99623f21eedd902286af7da8d8a4609d7acefce04c851dc1c"
assert.Equal(t, expect, UtilityTokenID().StringLE())
}
@ -55,6 +55,6 @@ func TestGoverningTokenTX(t *testing.T) {
//TODO: After we added Nonce field to transaction.Transaction, GoveringTockenTx hash
// has been changed. Update it for better times.
// Old hash is "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
expect := "7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc"
expect := "1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78"
assert.Equal(t, expect, GoverningTokenID().StringLE())
}

View file

@ -38,15 +38,7 @@ func (chain testChain) References(t *transaction.Transaction) ([]transaction.InO
panic("TODO")
}
func (chain testChain) FeePerByte(t *transaction.Transaction) util.Fixed8 {
panic("TODO")
}
func (chain testChain) SystemFee(t *transaction.Transaction) util.Fixed8 {
panic("TODO")
}
func (chain testChain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
func (chain testChain) FeePerByte() util.Fixed8 {
panic("TODO")
}
@ -145,6 +137,10 @@ func (chain testChain) IsLowPriority(util.Fixed8) bool {
panic("TODO")
}
func (chain testChain) GetUtilityTokenBalance(uint160 util.Uint160) util.Fixed8 {
panic("TODO")
}
func (chain testChain) PoolTx(*transaction.Transaction) error {
panic("TODO")
}

View file

@ -1,6 +1,7 @@
package client
import (
"encoding/hex"
"errors"
"fmt"
@ -107,8 +108,29 @@ func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token *walle
emit.AppCallWithOperationAndArgs(w.BinWriter, token.Hash, "transfer", from, to, amount)
emit.Opcode(w.BinWriter, opcode.ASSERT)
tx := transaction.NewInvocationTX(w.Bytes(), gas)
script := w.Bytes()
tx := transaction.NewInvocationTX(script, gas)
tx.Sender = from
tx.Cosigners = []transaction.Cosigner{
{
Account: from,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
},
}
result, err := c.InvokeScript(hex.EncodeToString(script))
if err != nil {
return util.Uint256{}, fmt.Errorf("can't add system fee to transaction: %v", err)
}
gasConsumed, err := util.Fixed8FromString(result.GasConsumed)
if err != nil {
return util.Uint256{}, fmt.Errorf("can't add system fee to transaction: %v", err)
}
if gasConsumed > 0 {
tx.SystemFee = gasConsumed
}
tx.ValidUntilBlock, err = c.CalculateValidUntilBlock()
if err != nil {
@ -119,6 +141,11 @@ func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token *walle
return util.Uint256{}, fmt.Errorf("can't add GAS to transaction: %v", err)
}
err = c.AddNetworkFee(tx, acc)
if err != nil {
return util.Uint256{}, fmt.Errorf("can't add network fee to transaction: %v", err)
}
if err := acc.SignTx(tx); err != nil {
return util.Uint256{}, fmt.Errorf("can't sign tx: %v", err)
}

View file

@ -511,6 +511,8 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys
var err error
tx := transaction.NewInvocationTX(script, sysfee)
tx.SystemFee = sysfee
validUntilBlock, err := c.CalculateValidUntilBlock()
if err != nil {
return txHash, errors.Wrap(err, "failed to add validUntilBlock to transaction")
@ -531,6 +533,11 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys
}
}
err = c.AddNetworkFee(tx, acc)
if err != nil {
return txHash, errors.Wrapf(err, "failed to add network fee")
}
if err = acc.SignTx(tx); err != nil {
return txHash, errors.Wrap(err, "failed to sign tx")
}
@ -588,3 +595,33 @@ func (c *Client) CalculateValidUntilBlock() (uint32, error) {
}
return blockCount + validatorsCount, nil
}
// AddNetworkFee adds network fee for each witness script to transaction.
func (c *Client) AddNetworkFee(tx *transaction.Transaction, acc *wallet.Account) error {
size := io.GetVarSize(tx)
if acc.Contract != nil {
netFee, sizeDelta := core.CalculateNetworkFee(acc.Contract.Script)
tx.NetworkFee += netFee
size += sizeDelta
}
for _, cosigner := range tx.Cosigners {
contract, err := c.GetContractState(cosigner.Account)
if err != nil {
return err
}
if contract == nil {
continue
}
netFee, sizeDelta := core.CalculateNetworkFee(contract.Script)
tx.NetworkFee += netFee
size += sizeDelta
}
tx.NetworkFee += util.Fixed8(int64(size) * int64(c.GetFeePerByte()))
return nil
}
// GetFeePerByte returns transaction network fee per byte
func (c *Client) GetFeePerByte() util.Fixed8 {
// TODO: make it a part of policy contract
return util.Fixed8(1000)
}

View file

@ -40,7 +40,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
invoke: func(c *Client) (interface{}, error) {
return c.GetAccountState("")
},
serverResponse: `{"jsonrpc":"2.0","id": 1,"result":{"version":0,"script_hash":"0x1179716da2e9523d153a35fb3ad10c561b1e5b1a","frozen":false,"votes":[],"balances":[{"asset":"0x7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc","value":"94"}]}}`,
serverResponse: `{"jsonrpc":"2.0","id": 1,"result":{"version":0,"script_hash":"0x1179716da2e9523d153a35fb3ad10c561b1e5b1a","frozen":false,"votes":[],"balances":[{"asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","value":"94"}]}}`,
result: func(c *Client) interface{} {
scriptHash, err := util.Uint160DecodeStringLE("1179716da2e9523d153a35fb3ad10c561b1e5b1a")
if err != nil {
@ -98,7 +98,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
invoke: func(c *Client) (interface{}, error) {
return c.GetAssetState(util.Uint256{})
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":"0x7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc","type":0,"name":"NEO","amount":"100000000","available":"100000000","precision":0,"owner":"00","admin":"Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":4000000,"is_frozen":false}}`,
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","type":0,"name":"NEO","amount":"100000000","available":"100000000","precision":0,"owner":"00","admin":"Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":4000000,"is_frozen":false}}`,
result: func(c *Client) interface{} {
return &result.AssetState{
ID: core.GoverningTokenID(),
@ -138,17 +138,17 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
invoke: func(c *Client) (interface{}, error) {
return c.GetBlockByIndex(202)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"0000000039f06b13aa6ee24d37db9b7868533b7988a6fb1e59c3fa6677dab7fd1f51326405f4fa24f061ffa04ab3127994530ee04c5e578a7b8109133dd142b6a31192cd9debaa5e00000000ca000000e903736ceceeceae1806eee0e3ec61e7cce476ce01fd08010c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b77601094130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb0200570400000000000080000300000075a94799633ed955dd85a8af314a5b435ab51903b0040000000001f6c31d4dd9edf7aea24168885ae82dc574d25e35a9b9b3063f3ea44226a3081f000001dcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d00184a27db86230075a94799633ed955dd85a8af314a5b435ab5190301420c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}`,
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"00000000ef9039404248180eb395d8ff59f985286b6852a68275a487e473114c4240b5938dccbfab32ee60f8b0d16144f258cc30d4f6795522fbd60109e2ed8d1423e9b810cdba5e00000000ca000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c312399694130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb02005704000000000000800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`,
result: func(c *Client) interface{} { return &block.Block{} },
check: func(t *testing.T, c *Client, result interface{}) {
res, ok := result.(*block.Block)
require.True(t, ok)
assert.Equal(t, uint32(0), res.Version)
assert.Equal(t, "75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45", res.Hash().StringLE())
assert.Equal(t, "6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039", res.PrevHash.StringLE())
assert.Equal(t, "cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405", res.MerkleRoot.StringLE())
assert.Equal(t, "cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86", res.Hash().StringLE())
assert.Equal(t, "93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef", res.PrevHash.StringLE())
assert.Equal(t, "b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d", res.MerkleRoot.StringLE())
assert.Equal(t, 1, len(res.Transactions))
assert.Equal(t, "115975f7d1e9c4577ec839c6ea4ae32727616284e2d7cdc5a49e4e421b32e505", res.Transactions[0].Hash().StringLE())
assert.Equal(t, "96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581", res.Transactions[0].Hash().StringLE())
},
},
{
@ -156,49 +156,49 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
invoke: func(c *Client) (i interface{}, err error) {
return c.GetBlockByIndexVerbose(202)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0x75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45","size":765,"version":0,"nextblockhash":"0xec27fed4ff8ad6a87bdd29aecee43e9ecb4e336321dac875d0b14d2aab1c5798","previousblockhash":"0x6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039","merkleroot":"0xcd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405","time":1588259741,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"Ad1wDxzcRiRSryvJobNV211Tv7UUiziPXy","confirmations":6,"script":{"invocation":"0c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b776010","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb"},"tx":[{"sys_fee":"0","net_fee":"0","txid":"0x115975f7d1e9c4577ec839c6ea4ae32727616284e2d7cdc5a49e4e421b32e505","size":238,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6","vout":0}],"vout":[{"address":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","asset":"0x7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}]}]}}`,
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"script":{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"},"tx":[{"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}}`,
result: func(c *Client) interface{} {
hash, err := util.Uint256DecodeStringLE("75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45")
hash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86")
if err != nil {
panic(err)
}
nextBlockHash, err := util.Uint256DecodeStringLE("ec27fed4ff8ad6a87bdd29aecee43e9ecb4e336321dac875d0b14d2aab1c5798")
nextBlockHash, err := util.Uint256DecodeStringLE("13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22")
if err != nil {
panic(err)
}
prevBlockHash, err := util.Uint256DecodeStringLE("6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039")
prevBlockHash, err := util.Uint256DecodeStringLE("93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef")
if err != nil {
panic(err)
}
merkleRoot, err := util.Uint256DecodeStringLE("cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405")
merkleRoot, err := util.Uint256DecodeStringLE("b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d")
if err != nil {
panic(err)
}
invScript, err := hex.DecodeString("0c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b776010")
invScript, err := hex.DecodeString("0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996")
if err != nil {
panic(err)
}
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb")
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb")
if err != nil {
panic(err)
}
sender, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil {
panic(err)
}
txInvScript, err := hex.DecodeString("0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff")
txInvScript, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
if err != nil {
panic(err)
}
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4")
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
if err != nil {
panic(err)
}
vin, err := util.Uint256DecodeStringLE("1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6")
vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
if err != nil {
panic(err)
}
outAddress, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil {
panic(err)
}
@ -231,14 +231,14 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
_ = tx.Hash()
return &result.Block{
Hash: hash,
Size: 765,
Size: 781,
Version: 0,
NextBlockHash: &nextBlockHash,
PreviousBlockHash: prevBlockHash,
MerkleRoot: merkleRoot,
Time: 1588259741,
Time: 1589300496,
Index: 202,
NextConsensus: "Ad1wDxzcRiRSryvJobNV211Tv7UUiziPXy",
NextConsensus: "AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL",
Confirmations: 6,
ConsensusData: result.ConsensusData{
PrimaryIndex: 0,
@ -248,90 +248,84 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
InvocationScript: invScript,
VerificationScript: verifScript,
},
Tx: []result.Tx{{
Transaction: tx,
Fees: result.Fees{
SysFee: 0,
NetFee: 0,
},
}},
Tx: []*transaction.Transaction{tx},
}
},
},
{
name: "byHash_positive",
invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint256DecodeStringLE("45bc7f077a4381c9836164b242af97ffa59e5d233ccba4929796ce873474ab75")
hash, err := util.Uint256DecodeStringLE("86fe1061140b2ea791b0739fb9732abc6e5e47de4927228a1ac41de3d93eb7cb")
if err != nil {
panic(err)
}
return c.GetBlockByHash(hash)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"0000000039f06b13aa6ee24d37db9b7868533b7988a6fb1e59c3fa6677dab7fd1f51326405f4fa24f061ffa04ab3127994530ee04c5e578a7b8109133dd142b6a31192cd9debaa5e00000000ca000000e903736ceceeceae1806eee0e3ec61e7cce476ce01fd08010c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b77601094130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb0200570400000000000080000300000075a94799633ed955dd85a8af314a5b435ab51903b0040000000001f6c31d4dd9edf7aea24168885ae82dc574d25e35a9b9b3063f3ea44226a3081f000001dcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d00184a27db86230075a94799633ed955dd85a8af314a5b435ab5190301420c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}`,
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"00000000ef9039404248180eb395d8ff59f985286b6852a68275a487e473114c4240b5938dccbfab32ee60f8b0d16144f258cc30d4f6795522fbd60109e2ed8d1423e9b810cdba5e00000000ca000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c312399694130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb02005704000000000000800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`,
result: func(c *Client) interface{} { return &block.Block{} },
check: func(t *testing.T, c *Client, result interface{}) {
res, ok := result.(*block.Block)
require.True(t, ok)
assert.Equal(t, uint32(0), res.Version)
assert.Equal(t, "75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45", res.Hash().StringLE())
assert.Equal(t, "6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039", res.PrevHash.StringLE())
assert.Equal(t, "cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405", res.MerkleRoot.StringLE())
assert.Equal(t, "cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86", res.Hash().StringLE())
assert.Equal(t, "93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef", res.PrevHash.StringLE())
assert.Equal(t, "b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d", res.MerkleRoot.StringLE())
assert.Equal(t, 1, len(res.Transactions))
assert.Equal(t, "115975f7d1e9c4577ec839c6ea4ae32727616284e2d7cdc5a49e4e421b32e505", res.Transactions[0].Hash().StringLE())
assert.Equal(t, "96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581", res.Transactions[0].Hash().StringLE())
},
},
{
name: "byHash_verbose_positive",
invoke: func(c *Client) (i interface{}, err error) {
hash, err := util.Uint256DecodeStringLE("45bc7f077a4381c9836164b242af97ffa59e5d233ccba4929796ce873474ab75")
hash, err := util.Uint256DecodeStringLE("86fe1061140b2ea791b0739fb9732abc6e5e47de4927228a1ac41de3d93eb7cb")
if err != nil {
panic(err)
}
return c.GetBlockByHashVerbose(hash)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0x75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45","size":765,"version":0,"nextblockhash":"0xec27fed4ff8ad6a87bdd29aecee43e9ecb4e336321dac875d0b14d2aab1c5798","previousblockhash":"0x6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039","merkleroot":"0xcd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405","time":1588259741,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"Ad1wDxzcRiRSryvJobNV211Tv7UUiziPXy","confirmations":6,"script":{"invocation":"0c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b776010","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb"},"tx":[{"sys_fee":"0","net_fee":"0","txid":"0x115975f7d1e9c4577ec839c6ea4ae32727616284e2d7cdc5a49e4e421b32e505","size":238,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6","vout":0}],"vout":[{"address":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","asset":"0x7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}]}]}}`,
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"script":{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"},"tx":[{"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}}`,
result: func(c *Client) interface{} {
hash, err := util.Uint256DecodeStringLE("75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45")
hash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86")
if err != nil {
panic(err)
}
nextBlockHash, err := util.Uint256DecodeStringLE("ec27fed4ff8ad6a87bdd29aecee43e9ecb4e336321dac875d0b14d2aab1c5798")
nextBlockHash, err := util.Uint256DecodeStringLE("13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22")
if err != nil {
panic(err)
}
prevBlockHash, err := util.Uint256DecodeStringLE("6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039")
prevBlockHash, err := util.Uint256DecodeStringLE("93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef")
if err != nil {
panic(err)
}
merkleRoot, err := util.Uint256DecodeStringLE("cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405")
merkleRoot, err := util.Uint256DecodeStringLE("b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d")
if err != nil {
panic(err)
}
invScript, err := hex.DecodeString("0c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b776010")
invScript, err := hex.DecodeString("0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996")
if err != nil {
panic(err)
}
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb")
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb")
if err != nil {
panic(err)
}
sender, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil {
panic(err)
}
txInvScript, err := hex.DecodeString("0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff")
txInvScript, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
if err != nil {
panic(err)
}
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4")
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
if err != nil {
panic(err)
}
vin, err := util.Uint256DecodeStringLE("1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6")
vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
if err != nil {
panic(err)
}
outAddress, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil {
panic(err)
}
@ -364,14 +358,14 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
_ = tx.Hash()
return &result.Block{
Hash: hash,
Size: 765,
Size: 781,
Version: 0,
NextBlockHash: &nextBlockHash,
PreviousBlockHash: prevBlockHash,
MerkleRoot: merkleRoot,
Time: 1588259741,
Time: 1589300496,
Index: 202,
NextConsensus: "Ad1wDxzcRiRSryvJobNV211Tv7UUiziPXy",
NextConsensus: "AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL",
Confirmations: 6,
ConsensusData: result.ConsensusData{
PrimaryIndex: 0,
@ -381,13 +375,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
InvocationScript: invScript,
VerificationScript: verifScript,
},
Tx: []result.Tx{{
Transaction: tx,
Fees: result.Fees{
SysFee: 0,
NetFee: 0,
},
}},
Tx: []*transaction.Transaction{tx},
}
},
},
@ -701,19 +689,19 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{
name: "positive",
invoke: func(c *Client) (i interface{}, err error) {
hash, err := util.Uint256DecodeStringLE("05e5321b424e9ea4c5cdd7e28462612727e34aeac639c87e57c4e9d1f7755911")
hash, err := util.Uint256DecodeStringLE("8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96")
if err != nil {
panic(err)
}
return c.GetRawTransaction(hash)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"80000300000075a94799633ed955dd85a8af314a5b435ab51903b0040000000001f6c31d4dd9edf7aea24168885ae82dc574d25e35a9b9b3063f3ea44226a3081f000001dcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d00184a27db86230075a94799633ed955dd85a8af314a5b435ab5190301420c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}`,
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`,
result: func(c *Client) interface{} { return &transaction.Transaction{} },
check: func(t *testing.T, c *Client, result interface{}) {
res, ok := result.(*transaction.Transaction)
require.True(t, ok)
assert.Equal(t, uint8(0), res.Version)
assert.Equal(t, "05e5321b424e9ea4c5cdd7e28462612727e34aeac639c87e57c4e9d1f7755911", res.Hash().StringBE())
assert.Equal(t, "8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96", res.Hash().StringBE())
assert.Equal(t, transaction.ContractType, res.Type)
assert.Equal(t, false, res.Trimmed)
},
@ -721,35 +709,35 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{
name: "verbose_positive",
invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint256DecodeStringLE("05e5321b424e9ea4c5cdd7e28462612727e34aeac639c87e57c4e9d1f7755911")
hash, err := util.Uint256DecodeStringLE("8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96")
if err != nil {
panic(err)
}
return c.GetRawTransactionVerbose(hash)
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"sys_fee":"0","net_fee":"0","blockhash":"0x75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45","confirmations":8,"blocktime":1588259741,"txid":"0x115975f7d1e9c4577ec839c6ea4ae32727616284e2d7cdc5a49e4e421b32e505","size":238,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6","vout":0}],"vout":[{"address":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","asset":"0x7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}]}}`,
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"blockhash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","confirmations":8,"blocktime":1589300496,"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}}`,
result: func(c *Client) interface{} {
blockHash, err := util.Uint256DecodeStringLE("75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45")
blockHash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86")
if err != nil {
panic(err)
}
sender, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil {
panic(err)
}
invocation, err := hex.DecodeString("0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff")
invocation, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
if err != nil {
panic(err)
}
verification, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4")
verification, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
if err != nil {
panic(err)
}
vin, err := util.Uint256DecodeStringLE("1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6")
vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
if err != nil {
panic(err)
}
outAddress, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil {
panic(err)
}
@ -784,11 +772,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
return &result.TransactionOutputRaw{
Transaction: tx,
TransactionMetadata: result.TransactionMetadata{
SysFee: 0,
NetFee: 0,
Blockhash: blockHash,
Confirmations: 8,
Timestamp: uint64(1588259741),
Timestamp: uint64(1589300496),
},
}
},

View file

@ -1,8 +1,6 @@
package result
import (
"encoding/json"
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/block"
@ -14,19 +12,6 @@ import (
)
type (
// Tx wrapper used for the representation of
// transaction on the RPC Server.
Tx struct {
*transaction.Transaction
Fees
}
// Fees is an auxilliary struct for proper Tx marshaling.
Fees struct {
SysFee util.Fixed8 `json:"sys_fee"`
NetFee util.Fixed8 `json:"net_fee"`
}
// ConsensusData is a wrapper for block.ConsensusData
ConsensusData struct {
PrimaryIndex uint32 `json:"primary"`
@ -51,7 +36,7 @@ type (
Script transaction.Witness `json:"script"`
Tx []Tx `json:"tx"`
Tx []*transaction.Transaction `json:"tx"`
}
)
@ -74,7 +59,7 @@ func NewBlock(b *block.Block, chain blockchainer.Blockchainer) Block {
Script: b.Script,
Tx: make([]Tx, 0, len(b.Transactions)),
Tx: b.Transactions,
}
hash := chain.GetHeaderHash(int(b.Index) + 1)
@ -82,60 +67,5 @@ func NewBlock(b *block.Block, chain blockchainer.Blockchainer) Block {
res.NextBlockHash = &hash
}
for i := range b.Transactions {
res.Tx = append(res.Tx, Tx{
Transaction: b.Transactions[i],
Fees: Fees{
SysFee: chain.SystemFee(b.Transactions[i]),
NetFee: chain.NetworkFee(b.Transactions[i]),
},
})
}
return res
}
// MarshalJSON implements json.Marshaler interface.
func (t Tx) MarshalJSON() ([]byte, error) {
output, err := json.Marshal(&Fees{
SysFee: t.SysFee,
NetFee: t.NetFee,
})
if err != nil {
return nil, err
}
txBytes, err := json.Marshal(t.Transaction)
if err != nil {
return nil, err
}
// We have to keep both transaction.Transaction and tx at the same level in json in order to match C# API,
// so there's no way to marshall Tx correctly with standard json.Marshaller tool.
if output[len(output)-1] != '}' || txBytes[0] != '{' {
return nil, errors.New("can't merge internal jsons")
}
output[len(output)-1] = ','
output = append(output, txBytes[1:]...)
return output, nil
}
// UnmarshalJSON implements json.Marshaler interface.
func (t *Tx) UnmarshalJSON(data []byte) error {
// As transaction.Transaction and tx are at the same level in json, do unmarshalling
// separately for both structs.
output := new(Fees)
err := json.Unmarshal(data, output)
if err != nil {
return err
}
t.SysFee = output.SysFee
t.NetFee = output.NetFee
transaction := new(transaction.Transaction)
err = json.Unmarshal(data, transaction)
if err != nil {
return err
}
t.Transaction = transaction
return nil
}

View file

@ -19,8 +19,6 @@ type TransactionOutputRaw struct {
// TransactionMetadata is an auxilliary struct for proper TransactionOutputRaw marshaling.
type TransactionMetadata struct {
SysFee util.Fixed8 `json:"sys_fee"`
NetFee util.Fixed8 `json:"net_fee"`
Blockhash util.Uint256 `json:"blockhash,omitempty"`
Confirmations int `json:"confirmations,omitempty"`
Timestamp uint64 `json:"blocktime,omitempty"`
@ -37,8 +35,6 @@ func NewTransactionOutputRaw(tx *transaction.Transaction, header *block.Header,
return TransactionOutputRaw{
Transaction: tx,
TransactionMetadata: TransactionMetadata{
SysFee: chain.SystemFee(tx),
NetFee: chain.NetworkFee(tx),
Blockhash: header.Hash(),
Confirmations: confirmations,
Timestamp: header.Timestamp,
@ -49,8 +45,6 @@ func NewTransactionOutputRaw(tx *transaction.Transaction, header *block.Header,
// MarshalJSON implements json.Marshaler interface.
func (t TransactionOutputRaw) MarshalJSON() ([]byte, error) {
output, err := json.Marshal(TransactionMetadata{
SysFee: t.SysFee,
NetFee: t.NetFee,
Blockhash: t.Blockhash,
Confirmations: t.Confirmations,
Timestamp: t.Timestamp,
@ -82,8 +76,6 @@ func (t *TransactionOutputRaw) UnmarshalJSON(data []byte) error {
if err != nil {
return err
}
t.SysFee = output.SysFee
t.NetFee = output.NetFee
t.Blockhash = output.Blockhash
t.Confirmations = output.Confirmations
t.Timestamp = output.Timestamp

View file

@ -798,7 +798,7 @@ func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *respons
var blockSysFee util.Fixed8
for _, tx := range block.Transactions {
blockSysFee += s.chain.SystemFee(tx)
blockSysFee += tx.SystemFee
}
return blockSysFee, nil

View file

@ -10,7 +10,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/network"
"github.com/nspcc-dev/neo-go/pkg/util"
@ -65,18 +64,14 @@ func initServerWithInMemoryChain(t *testing.T) (*core.Blockchain, *httptest.Serv
type FeerStub struct{}
func (fs *FeerStub) NetworkFee(*transaction.Transaction) util.Fixed8 {
return 0
}
func (fs *FeerStub) IsLowPriority(util.Fixed8) bool {
return false
}
func (fs *FeerStub) FeePerByte(*transaction.Transaction) util.Fixed8 {
func (fs *FeerStub) FeePerByte() util.Fixed8 {
return 0
}
func (fs *FeerStub) SystemFee(*transaction.Transaction) util.Fixed8 {
return 0
func (fs *FeerStub) GetUtilityTokenBalance(acc util.Uint160) util.Fixed8 {
return util.Fixed8FromInt64(1000000)
}

View file

@ -54,12 +54,12 @@ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": {
{
name: "positive",
params: `["396d55aa14b6cd428d793e9e740d24f93f62d7ddcdc0f4fdadd4dfd89bdabd83"]`,
params: `["0a0abf0188053113d0014e0cb9801d090a5d3e7640d76427fa1a3676e7cdf82e"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog)
require.True(t, ok)
expectedTxHash, err := util.Uint256DecodeStringLE("396d55aa14b6cd428d793e9e740d24f93f62d7ddcdc0f4fdadd4dfd89bdabd83")
expectedTxHash, err := util.Uint256DecodeStringLE("0a0abf0188053113d0014e0cb9801d090a5d3e7640d76427fa1a3676e7cdf82e")
require.NoError(t, err)
assert.Equal(t, expectedTxHash, res.TxHash)
assert.Equal(t, 1, len(res.Executions))
@ -170,11 +170,23 @@ var rpcTestCases = map[string][]rpcTestCase{
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.NEP5Balances)
require.True(t, ok)
rubles, err := util.Uint160DecodeStringLE(testContractHash)
require.NoError(t, err)
expected := result.NEP5Balances{
Balances: []result.NEP5Balance{{
Asset: rubles,
Amount: "8.77",
LastUpdated: 208,
},
{
Asset: e.chain.UtilityTokenHash(),
Amount: "10",
LastUpdated: 1,
}},
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
}
require.Equal(t, testchain.PrivateKeyByID(0).Address(), res.Address)
require.Equal(t, 1, len(res.Balances))
require.Equal(t, "8.77", res.Balances[0].Amount)
require.Equal(t, testContractHash, res.Balances[0].Asset.StringLE())
require.Equal(t, uint32(208), res.Balances[0].LastUpdated)
require.ElementsMatch(t, expected.Balances, res.Balances)
},
},
},
@ -196,20 +208,56 @@ var rpcTestCases = map[string][]rpcTestCase{
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.NEP5Transfers)
require.True(t, ok)
require.Equal(t, testchain.PrivateKeyByID(0).Address(), res.Address)
assetHash, err := util.Uint160DecodeStringLE(testContractHash)
rublesHash, err := util.Uint160DecodeStringLE(testContractHash)
require.NoError(t, err)
require.Equal(t, 1, len(res.Received))
require.Equal(t, "10", res.Received[0].Amount)
require.Equal(t, assetHash, res.Received[0].Asset)
require.Equal(t, address.Uint160ToString(assetHash), res.Received[0].Address)
require.Equal(t, 1, len(res.Sent))
require.Equal(t, "1.23", res.Sent[0].Amount)
require.Equal(t, assetHash, res.Sent[0].Asset)
require.Equal(t, testchain.PrivateKeyByID(1).Address(), res.Sent[0].Address)
blockSendRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(208))
require.NoError(t, err)
require.Equal(t, 1, len(blockSendRubles.Transactions))
txSendRublesHash := blockSendRubles.Transactions[0].Hash()
blockRecieveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(207))
require.NoError(t, err)
require.Equal(t, 2, len(blockRecieveRubles.Transactions))
txRecieveRublesHash := blockRecieveRubles.Transactions[1].Hash()
blockRecieveGAS, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
require.NoError(t, err)
require.Equal(t, 1, len(blockRecieveGAS.Transactions))
txRecieveGASHash := blockRecieveGAS.Transactions[0].Hash()
require.NoError(t, err)
expected := result.NEP5Transfers{
Sent: []result.NEP5Transfer{{
Timestamp: blockSendRubles.Timestamp,
Asset: rublesHash,
Address: testchain.PrivateKeyByID(1).Address(),
Amount: "1.23",
Index: 208,
NotifyIndex: 0,
TxHash: txSendRublesHash,
}},
Received: []result.NEP5Transfer{
{
Timestamp: blockRecieveRubles.Timestamp,
Asset: rublesHash,
Address: address.Uint160ToString(rublesHash),
Amount: "10",
Index: 207,
NotifyIndex: 0,
TxHash: txRecieveRublesHash,
},
{
Timestamp: blockRecieveGAS.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: testchain.MultisigAddress(),
Amount: "10",
Index: 1,
NotifyIndex: 0,
TxHash: txRecieveGASHash,
},
},
Address: testchain.PrivateKeyByID(0).Address(),
}
require.Equal(t, expected.Address, res.Address)
require.ElementsMatch(t, expected.Sent, res.Sent)
require.ElementsMatch(t, expected.Received, res.Received)
},
},
},
@ -254,7 +302,7 @@ var rpcTestCases = map[string][]rpcTestCase{
"getassetstate": {
{
name: "positive",
params: `["8ef63ccd6f4ea20a93e7f4e84b2d43f778077612b241d617e42e1750cca4f2c5"]`,
params: `["f882fb865bab84b99623f21eedd902286af7da8d8a4609d7acefce04c851dc1c"]`,
result: func(e *executor) interface{} { return &result.AssetState{} },
check: func(t *testing.T, e *executor, as interface{}) {
res, ok := as.(*result.AssetState)
@ -327,24 +375,24 @@ var rpcTestCases = map[string][]rpcTestCase{
"getblock": {
{
name: "positive",
params: "[2, 1]",
params: "[3, 1]",
result: func(e *executor) interface{} { return &result.Block{} },
check: func(t *testing.T, e *executor, blockRes interface{}) {
res, ok := blockRes.(*result.Block)
require.True(t, ok)
block, err := e.chain.GetBlock(e.chain.GetHeaderHash(2))
block, err := e.chain.GetBlock(e.chain.GetHeaderHash(3))
require.NoErrorf(t, err, "could not get block")
assert.Equal(t, block.Hash(), res.Hash)
for i := range res.Tx {
tx := res.Tx[i]
require.Equal(t, transaction.ContractType, tx.Transaction.Type)
require.Equal(t, transaction.ContractType, tx.Type)
actualTx := block.Transactions[i]
require.True(t, ok)
require.Equal(t, actualTx.Nonce, tx.Transaction.Nonce)
require.Equal(t, block.Transactions[i].Hash(), tx.Transaction.Hash())
require.Equal(t, actualTx.Nonce, tx.Nonce)
require.Equal(t, block.Transactions[i].Hash(), tx.Hash())
}
},
},
@ -436,7 +484,7 @@ var rpcTestCases = map[string][]rpcTestCase{
var expectedBlockSysFee util.Fixed8
for _, tx := range block.Transactions {
expectedBlockSysFee += e.chain.SystemFee(tx)
expectedBlockSysFee += tx.SystemFee
}
return &expectedBlockSysFee
},
@ -473,7 +521,7 @@ var rpcTestCases = map[string][]rpcTestCase{
params: `["` + testchain.MultisigAddress() + `"]`,
result: func(*executor) interface{} {
// hash of the issueTx
h, _ := util.Uint256DecodeStringBE("3b76c9b726ffa9074a69441bf946c4c70b83474b3cf522ea3ba9dcd71c1a3db8")
h, _ := util.Uint256DecodeStringBE("d3a4f2249fe33b18bde73901c1ecc66200485f1c1dcd941b406a630b479090ae")
amount := util.Fixed8FromInt64(1 * 8) // (endHeight - startHeight) * genAmount[0]
return &result.ClaimableInfo{
Spents: []result.Claimable{
@ -532,7 +580,7 @@ var rpcTestCases = map[string][]rpcTestCase{
"gettransactionheight": {
{
name: "positive",
params: `["a05ea6d90b761ec5430f29d25036fdad04efe731b6a5906f4fd1e19048dee0f2"]`,
params: `["0e873d5d565a03c6cd39efa3b446e1901b4636c448a22bc7e8c259c5a28a2eda"]`,
result: func(e *executor) interface{} {
h := 1
return &h
@ -744,7 +792,7 @@ var rpcTestCases = map[string][]rpcTestCase{
"sendrawtransaction": {
{
name: "positive",
params: `["80000b000000316e851039019d39dfc2c37d6c3fee19fd580987b0040000000001241a237db30af3b33d29518288e0c9b542475733f78f4c6d7416cba89c1f1a67010001dcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d0030d3dec3862300316e851039019d39dfc2c37d6c3fee19fd58098701420c40c3c3fedb73e36a8e78bae80ba07c20b34f5af6bba36dccbe0cdc7d4e1237f85eec36e63cb762e1e6ce031a6c752e1ce19a3994d15191a6b75f1a02ede7f9d117290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"]`,
params: `["80000b000000316e851039019d39dfc2c37d6c3fee19fd5809870000000000000000a267050000000000b00400000000017a03a89832a347c4fb53af1f526d0d930b14ab6eb01629ce20ffbaeaeef58af3010001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a0030d3dec3862300316e851039019d39dfc2c37d6c3fee19fd58098701420c40b6aeec1d2699194b842f399448b395d98bbb287dc89ea9e5ce3bb99a1c8c9bf933f55b69db6709b44e6a5c8b28b97018466479e5d500e414a0874c37abab262d290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"]`,
result: func(e *executor) interface{} {
v := true
return &v
@ -867,16 +915,26 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
checkErrGetResult(t, body, true)
})
wif := testchain.WIF(0)
acc, err := wallet.NewAccountFromWIF(wif)
priv0 := testchain.PrivateKeyByID(0)
acc0, err := wallet.NewAccountFromWIF(priv0.WIF())
require.NoError(t, err)
addNetworkFee := func(tx *transaction.Transaction) {
size := io.GetVarSize(tx)
netFee, sizeDelta := core.CalculateNetworkFee(acc0.Contract.Script)
tx.NetworkFee += netFee
size += sizeDelta
tx.NetworkFee = tx.NetworkFee.Add(util.Fixed8(int64(size) * int64(chain.FeePerByte())))
}
newTx := func() *transaction.Transaction {
height := chain.BlockHeight()
tx := transaction.NewContractTX()
tx.Nonce = height + 1
tx.ValidUntilBlock = height + 10
tx.Sender = acc.PrivateKey().GetScriptHash()
require.NoError(t, acc.SignTx(tx))
tx.Sender = acc0.PrivateKey().GetScriptHash()
addNetworkFee(tx)
require.NoError(t, acc0.SignTx(tx))
return tx
}
@ -905,7 +963,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
var res string
err := json.Unmarshal(result, &res)
require.NoErrorf(t, err, "could not parse response: %s", result)
assert.Equal(t, "400000000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000", res)
assert.Equal(t, "400000000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b000000000000000000000000000000000000000000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000", res)
})
t.Run("getrawtransaction 2 arguments", func(t *testing.T) {
@ -917,7 +975,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
var res string
err := json.Unmarshal(result, &res)
require.NoErrorf(t, err, "could not parse response: %s", result)
assert.Equal(t, "400000000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000", res)
assert.Equal(t, "400000000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b000000000000000000000000000000000000000000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000", res)
})
t.Run("getrawtransaction 2 arguments, verbose", func(t *testing.T) {
@ -1006,7 +1064,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
err := json.Unmarshal(res, &txOut)
require.NoErrorf(t, err, "could not parse response: %s", res)
assert.Equal(t, 0, txOut.N)
assert.Equal(t, "0xdcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d", txOut.Asset)
assert.Equal(t, "0x787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a", txOut.Asset)
assert.Equal(t, util.Fixed8FromInt64(100000000), txOut.Value)
assert.Equal(t, testchain.MultisigAddress(), txOut.Address)
})

Binary file not shown.

View file

@ -70,7 +70,7 @@ func (c *ParameterContext) GetWitness(ctr *wallet.Contract) (*transaction.Witnes
// AddSignature adds a signature for the specified contract and public key.
func (c *ParameterContext) AddSignature(ctr *wallet.Contract, pub *keys.PublicKey, sig []byte) error {
item := c.getItemForContract(ctr)
if pubs, ok := vm.ParseMultiSigContract(ctr.Script); ok {
if _, pubs, ok := vm.ParseMultiSigContract(ctr.Script); ok {
if item.GetSignature(pub) != nil {
return errors.New("signature is already added")
}

View file

@ -101,7 +101,7 @@ func (u Uint160) Reverse() (r Uint160) {
return
}
// Equals returns true if both Uint256 values are the same.
// Equals returns true if both Uint160 values are the same.
func (u Uint160) Equals(other Uint160) bool {
return u == other
}

View file

@ -36,65 +36,65 @@ func getNumOfThingsFromInstr(instr opcode.Opcode, param []byte) (int, bool) {
// IsMultiSigContract checks whether the passed script is a multi-signature
// contract.
func IsMultiSigContract(script []byte) bool {
_, ok := ParseMultiSigContract(script)
_, _, ok := ParseMultiSigContract(script)
return ok
}
// ParseMultiSigContract returns list of public keys from the verification
// script of the contract.
func ParseMultiSigContract(script []byte) ([][]byte, bool) {
// ParseMultiSigContract returns number of signatures and list of public keys
// from the verification script of the contract.
func ParseMultiSigContract(script []byte) (int, [][]byte, bool) {
var nsigs, nkeys int
ctx := NewContext(script)
instr, param, err := ctx.Next()
if err != nil {
return nil, false
return nsigs, nil, false
}
nsigs, ok := getNumOfThingsFromInstr(instr, param)
if !ok {
return nil, false
return nsigs, nil, false
}
var pubs [][]byte
for {
instr, param, err = ctx.Next()
if err != nil {
return nil, false
return nsigs, nil, false
}
if instr != opcode.PUSHDATA1 {
break
}
if len(param) < 33 {
return nil, false
return nsigs, nil, false
}
pubs = append(pubs, param)
nkeys++
if nkeys > MaxArraySize {
return nil, false
return nsigs, nil, false
}
}
if nkeys < nsigs {
return nil, false
return nsigs, nil, false
}
nkeys2, ok := getNumOfThingsFromInstr(instr, param)
if !ok {
return nil, false
return nsigs, nil, false
}
if nkeys2 != nkeys {
return nil, false
return nsigs, nil, false
}
instr, _, err = ctx.Next()
if err != nil || instr != opcode.PUSHNULL {
return nil, false
return nsigs, nil, false
}
instr, param, err = ctx.Next()
if err != nil || instr != opcode.SYSCALL || binary.LittleEndian.Uint32(param) != multisigInteropID {
return nil, false
return nsigs, nil, false
}
instr, _, err = ctx.Next()
if err != nil || instr != opcode.RET || ctx.ip != len(script) {
return nil, false
return nsigs, nil, false
}
return pubs, true
return nsigs, pubs, true
}
// IsSignatureContract checks whether the passed script is a signature check