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

View file

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

View file

@ -25,7 +25,7 @@ func TestNewService(t *testing.T) {
tx := transaction.NewContractTX() tx := transaction.NewContractTX()
tx.ValidUntilBlock = 1 tx.ValidUntilBlock = 1
addSender(t, tx) addSender(t, tx)
signTx(t, tx) signTx(t, srv.Chain.FeePerByte(), tx)
require.NoError(t, srv.Chain.PoolTx(tx)) require.NoError(t, srv.Chain.PoolTx(tx))
var txx []block.Transaction var txx []block.Transaction
@ -45,7 +45,7 @@ func TestService_GetVerified(t *testing.T) {
txs = append(txs, tx) txs = append(txs, tx)
} }
addSender(t, txs...) addSender(t, txs...)
signTx(t, txs...) signTx(t, srv.Chain.FeePerByte(), txs...)
require.NoError(t, srv.Chain.PoolTx(txs[3])) require.NoError(t, srv.Chain.PoolTx(txs[3]))
hashes := []util.Uint256{txs[0].Hash(), txs[1].Hash(), txs[2].Hash()} 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.Nonce = 1234
tx.ValidUntilBlock = 1 tx.ValidUntilBlock = 1
addSender(t, tx) addSender(t, tx)
signTx(t, tx) signTx(t, srv.Chain.FeePerByte(), tx)
h := tx.Hash() h := tx.Hash()
require.Equal(t, nil, srv.getTx(h)) require.Equal(t, nil, srv.getTx(h))
@ -229,10 +229,7 @@ func newTestChain(t *testing.T) *core.Blockchain {
type feer struct{} 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) 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() 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) validators := make([]*keys.PublicKey, 4)
privNetKeys := make([]*keys.PrivateKey, 4) privNetKeys := make([]*keys.PrivateKey, 4)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
@ -253,6 +250,11 @@ func signTx(t *testing.T, txs ...*transaction.Transaction) {
rawScript, err := smartcontract.CreateMultiSigRedeemScript(3, validators) rawScript, err := smartcontract.CreateMultiSigRedeemScript(3, validators)
require.NoError(t, err) require.NoError(t, err)
for _, tx := range txs { 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() data := tx.GetSignedPart()
buf := io.NewBufBinWriter() buf := io.NewBufBinWriter()

View file

@ -466,7 +466,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
cache := dao.NewCached(bc.dao) cache := dao.NewCached(bc.dao)
fee := bc.getSystemFeeAmount(block.PrevHash) fee := bc.getSystemFeeAmount(block.PrevHash)
for _, tx := range block.Transactions { for _, tx := range block.Transactions {
fee += uint32(bc.SystemFee(tx).IntegralValue()) fee += uint32(tx.SystemFee.IntegralValue())
} }
if err := cache.StoreAsBlock(block, fee); err != nil { if err := cache.StoreAsBlock(block, fee); err != nil {
return err return err
@ -651,16 +651,26 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
continue continue
} }
op, ok := arr[0].Value().([]byte) op, ok := arr[0].Value().([]byte)
if !ok || string(op) != "transfer" { if !ok || (string(op) != "transfer" && string(op) != "Transfer") {
continue continue
} }
from, ok := arr[1].Value().([]byte) var from []byte
if !ok { fromValue := arr[1].Value()
continue // 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
if !ok { toValue := arr[2].Value()
continue // 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) amount, ok := arr[3].Value().(*big.Int)
if !ok { if !ok {
@ -713,7 +723,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
bc.topBlock.Store(block) bc.topBlock.Store(block)
atomic.StoreUint32(&bc.blockHeight, block.Index) atomic.StoreUint32(&bc.blockHeight, block.Index)
updateBlockHeightMetric(block.Index) updateBlockHeightMetric(block.Index)
bc.memPool.RemoveStale(bc.isTxStillRelevant) bc.memPool.RemoveStale(bc.isTxStillRelevant, bc)
return nil return nil
} }
@ -807,6 +817,11 @@ func (bc *Blockchain) GetNEP5Balances(acc util.Uint160) *state.NEP5Balances {
return bs 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. // LastBatch returns last persisted storage batch.
func (bc *Blockchain) LastBatch() *storage.MemBatch { func (bc *Blockchain) LastBatch() *storage.MemBatch {
return bc.lastBatch return bc.lastBatch
@ -1107,48 +1122,10 @@ func (bc *Blockchain) references(ins []transaction.Input) ([]transaction.InOut,
return references, nil return references, nil
} }
// FeePerByte returns network fee divided by the size of the transaction. // FeePerByte returns transaction network fee per byte.
func (bc *Blockchain) FeePerByte(t *transaction.Transaction) util.Fixed8 { // TODO: should be implemented as part of PolicyContract
return bc.NetworkFee(t).Div(int64(io.GetVarSize(t))) func (bc *Blockchain) FeePerByte() util.Fixed8 {
} return util.Fixed8(1000)
// 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)
} }
// IsLowPriority checks given fee for being less than configured // 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 { if t.ValidUntilBlock <= height || t.ValidUntilBlock > height+transaction.MaxValidUntilBlockIncrement {
return errors.Errorf("transaction has expired. ValidUntilBlock = %d, current height = %d", t.ValidUntilBlock, height) 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) 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) { if transaction.HaveDuplicateInputs(t.Inputs) {
return errors.New("invalid transaction's inputs") return errors.New("invalid transaction's inputs")
} }
if block == nil { 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") 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) txSize := io.GetVarSize(t)
maxFree := bc.config.MaxFreeTransactionSize maxFree := bc.config.MaxFreeTransactionSize
if maxFree != 0 && txSize > maxFree { if maxFree != 0 && txSize > maxFree {
netFee := bc.NetworkFee(t) if bc.IsLowPriority(t.NetworkFee) ||
if bc.IsLowPriority(netFee) || t.NetworkFee < util.Fixed8FromFloat(bc.config.FeePerExtraByte)*util.Fixed8(txSize-maxFree) {
netFee < util.Fixed8FromFloat(bc.config.FeePerExtraByte)*util.Fixed8(txSize-maxFree) {
return ErrPolicy return ErrPolicy
} }
} }
@ -1441,7 +1428,7 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction, results []*trans
if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != UtilityTokenID() { if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != UtilityTokenID() {
return errors.New("tx destroys non-utility token") return errors.New("tx destroys non-utility token")
} }
sysfee := bc.SystemFee(t) sysfee := t.SystemFee
if sysfee.GreaterThan(util.Fixed8(0)) { if sysfee.GreaterThan(util.Fixed8(0)) {
if len(resultsDestroy) == 0 { if len(resultsDestroy) == 0 {
return fmt.Errorf("system requires to pay %s fee, but tx pays nothing", sysfee.String()) 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) 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 { func hashAndIndexToBytes(h util.Uint256, index uint32) []byte {
buf := io.NewBufBinWriter() buf := io.NewBufBinWriter()
buf.WriteBytes(h.BytesLE()) buf.WriteBytes(h.BytesLE())

View file

@ -167,10 +167,23 @@ func TestCreateBasicChain(t *testing.T) {
bc := newTestChain(t) bc := newTestChain(t)
defer bc.Close() defer bc.Close()
// Move almost all NEO to one simple account. gasHash := bc.contracts.GAS.Hash
txMoveNeo := transaction.NewContractTX() 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.ValidUntilBlock = validUntilBlock
txMoveNeo.Nonce = getNextNonce() 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 // use output of issue tx from genesis block as an input
genesisBlock, err := bc.GetBlock(bc.GetHeaderHash(0)) genesisBlock, err := bc.GetBlock(bc.GetHeaderHash(0))
@ -181,15 +194,10 @@ func TestCreateBasicChain(t *testing.T) {
PrevHash: h, PrevHash: h,
PrevIndex: 0, PrevIndex: 0,
}) })
txMoveNeo.Sender = neoOwner
priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash()
txMoveNeo.AddOutput(&transaction.Output{ txMoveNeo.AddOutput(&transaction.Output{
AssetID: GoverningTokenID(), AssetID: GoverningTokenID(),
Amount: neoAmount, Amount: neoAmount,
ScriptHash: priv0.GetScriptHash(), ScriptHash: priv0ScriptHash,
Position: 0, Position: 0,
}) })
txMoveNeo.AddOutput(&transaction.Output{ txMoveNeo.AddOutput(&transaction.Output{
@ -198,7 +206,6 @@ func TestCreateBasicChain(t *testing.T) {
ScriptHash: neoOwner, ScriptHash: neoOwner,
Position: 1, Position: 1,
}) })
txMoveNeo.Data = new(transaction.ContractTX)
require.NoError(t, signTx(bc, txMoveNeo)) require.NoError(t, signTx(bc, txMoveNeo))
b := bc.newBlock(txMoveNeo) b := bc.newBlock(txMoveNeo)
require.NoError(t, bc.AddBlock(b)) require.NoError(t, bc.AddBlock(b))
@ -233,6 +240,7 @@ func TestCreateBasicChain(t *testing.T) {
Position: 0, Position: 0,
}) })
txNeoRound.Data = new(transaction.ContractTX) txNeoRound.Data = new(transaction.ContractTX)
require.NoError(t, addNetworkFee(bc, txNeoRound, acc0))
require.NoError(t, acc0.SignTx(txNeoRound)) require.NoError(t, acc0.SignTx(txNeoRound))
b = bc.newBlock(txNeoRound) b = bc.newBlock(txNeoRound)
require.NoError(t, bc.AddBlock(b)) require.NoError(t, bc.AddBlock(b))
@ -257,6 +265,7 @@ func TestCreateBasicChain(t *testing.T) {
ScriptHash: priv0.GetScriptHash(), ScriptHash: priv0.GetScriptHash(),
Position: 0, Position: 0,
}) })
require.NoError(t, addNetworkFee(bc, txClaim, acc0))
require.NoError(t, acc0.SignTx(txClaim)) require.NoError(t, acc0.SignTx(txClaim))
b = bc.newBlock(txClaim) b = bc.newBlock(txClaim)
require.NoError(t, bc.AddBlock(b)) require.NoError(t, bc.AddBlock(b))
@ -300,6 +309,7 @@ func TestCreateBasicChain(t *testing.T) {
Position: 0, Position: 0,
}) })
gasOwned -= invFee gasOwned -= invFee
require.NoError(t, addNetworkFee(bc, txDeploy, acc0))
require.NoError(t, acc0.SignTx(txDeploy)) require.NoError(t, acc0.SignTx(txDeploy))
b = bc.newBlock(txDeploy) b = bc.newBlock(txDeploy)
require.NoError(t, bc.AddBlock(b)) require.NoError(t, bc.AddBlock(b))
@ -313,6 +323,7 @@ func TestCreateBasicChain(t *testing.T) {
txInv.Nonce = getNextNonce() txInv.Nonce = getNextNonce()
txInv.ValidUntilBlock = validUntilBlock txInv.ValidUntilBlock = validUntilBlock
txInv.Sender = priv0ScriptHash txInv.Sender = priv0ScriptHash
require.NoError(t, addNetworkFee(bc, txInv, acc0))
require.NoError(t, acc0.SignTx(txInv)) require.NoError(t, acc0.SignTx(txInv))
b = bc.newBlock(txInv) b = bc.newBlock(txInv)
require.NoError(t, bc.AddBlock(b)) require.NoError(t, bc.AddBlock(b))
@ -339,6 +350,7 @@ func TestCreateBasicChain(t *testing.T) {
ScriptHash: priv0.GetScriptHash(), ScriptHash: priv0.GetScriptHash(),
}) })
require.NoError(t, addNetworkFee(bc, txNeo0to1, acc0))
require.NoError(t, acc0.SignTx(txNeo0to1)) require.NoError(t, acc0.SignTx(txNeo0to1))
b = bc.newBlock(txNeo0to1) b = bc.newBlock(txNeo0to1)
require.NoError(t, bc.AddBlock(b)) require.NoError(t, bc.AddBlock(b))
@ -350,24 +362,45 @@ func TestCreateBasicChain(t *testing.T) {
initTx.Nonce = getNextNonce() initTx.Nonce = getNextNonce()
initTx.ValidUntilBlock = validUntilBlock initTx.ValidUntilBlock = validUntilBlock
initTx.Sender = priv0ScriptHash initTx.Sender = priv0ScriptHash
require.NoError(t, addNetworkFee(bc, initTx, acc0))
require.NoError(t, acc0.SignTx(initTx)) require.NoError(t, acc0.SignTx(initTx))
transferTx := newNEP5Transfer(sh, sh, priv0.GetScriptHash(), 1000) transferTx := newNEP5Transfer(sh, sh, priv0.GetScriptHash(), 1000)
transferTx.Nonce = getNextNonce() transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock transferTx.ValidUntilBlock = validUntilBlock
transferTx.Sender = priv0ScriptHash 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)) require.NoError(t, acc0.SignTx(transferTx))
b = bc.newBlock(initTx, transferTx) b = bc.newBlock(initTx, transferTx)
require.NoError(t, bc.AddBlock(b)) require.NoError(t, bc.AddBlock(b))
t.Logf("recieveRublesTx: %v", transferTx.Hash().StringBE())
transferTx = newNEP5Transfer(sh, priv0.GetScriptHash(), priv1.GetScriptHash(), 123) transferTx = newNEP5Transfer(sh, priv0.GetScriptHash(), priv1.GetScriptHash(), 123)
transferTx.Nonce = getNextNonce() transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock transferTx.ValidUntilBlock = validUntilBlock
transferTx.Sender = priv0ScriptHash 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)) require.NoError(t, acc0.SignTx(transferTx))
b = bc.newBlock(transferTx) b = bc.newBlock(transferTx)
require.NoError(t, bc.AddBlock(b)) require.NoError(t, bc.AddBlock(b))
t.Logf("sendRublesTx: %v", transferTx.Hash().StringBE())
if saveChain { if saveChain {
outStream, err := os.Create(prefix + "testblocks.acc") outStream, err := os.Create(prefix + "testblocks.acc")
@ -407,6 +440,7 @@ func TestCreateBasicChain(t *testing.T) {
Position: 0, Position: 0,
}) })
txNeoRound.Data = new(transaction.ContractTX) txNeoRound.Data = new(transaction.ContractTX)
require.NoError(t, addNetworkFee(bc, txNeoRound, acc0))
require.NoError(t, acc0.SignTx(txNeoRound)) require.NoError(t, acc0.SignTx(txNeoRound))
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
txNeoRound.EncodeBinary(bw.BinWriter) txNeoRound.EncodeBinary(bw.BinWriter)
@ -439,6 +473,11 @@ func signTx(bc *Blockchain, txs ...*transaction.Transaction) error {
return errors.Wrap(err, "fail to sign tx") return errors.Wrap(err, "fail to sign tx")
} }
for _, tx := range txs { 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() data := tx.GetSignedPart()
tx.Scripts = []transaction.Witness{{ tx.Scripts = []transaction.Witness{{
InvocationScript: testchain.Sign(data), InvocationScript: testchain.Sign(data),
@ -447,3 +486,20 @@ func signTx(bc *Blockchain, txs ...*transaction.Transaction) error {
} }
return nil 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 package mempool
import ( import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
// Feer is an interface that abstract the implementation of the fee calculation. // Feer is an interface that abstract the implementation of the fee calculation.
type Feer interface { type Feer interface {
NetworkFee(t *transaction.Transaction) util.Fixed8
IsLowPriority(util.Fixed8) bool IsLowPriority(util.Fixed8) bool
FeePerByte(t *transaction.Transaction) util.Fixed8 FeePerByte() util.Fixed8
SystemFee(t *transaction.Transaction) util.Fixed8 GetUtilityTokenBalance(util.Uint160) util.Fixed8
} }

View file

@ -25,11 +25,9 @@ var (
// item represents a transaction in the the Memory pool. // item represents a transaction in the the Memory pool.
type item struct { type item struct {
txn *transaction.Transaction txn *transaction.Transaction
timeStamp time.Time timeStamp time.Time
perByteFee util.Fixed8 isLowPrio bool
netFee util.Fixed8
isLowPrio bool
} }
// items is a slice of item. // items is a slice of item.
@ -41,6 +39,13 @@ type TxWithFee struct {
Fee util.Fixed8 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. // Pool stores the unconfirms transactions.
type Pool struct { type Pool struct {
lock sync.RWMutex lock sync.RWMutex
@ -48,6 +53,7 @@ type Pool struct {
verifiedTxes items verifiedTxes items
inputs []*transaction.Input inputs []*transaction.Input
claims []*transaction.Input claims []*transaction.Input
fees map[util.Uint160]utilityBalanceAndFees
capacity int capacity int
} }
@ -88,11 +94,11 @@ func (p *item) CompareTo(otherP *item) int {
} }
// Fees sorted ascending. // 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 return ret
} }
if ret := p.netFee.CompareTo(otherP.netFee); ret != 0 { if ret := p.txn.NetworkFee.CompareTo(otherP.txn.NetworkFee); ret != 0 {
return ret return ret
} }
@ -158,17 +164,47 @@ func dropInputFromSortedSlice(slice *[]*transaction.Input, input *transaction.In
*slice = (*slice)[:len(*slice)-1] *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. // Add tries to add given transaction to the Pool.
func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error { func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
var pItem = &item{ var pItem = &item{
txn: t, txn: t,
timeStamp: time.Now().UTC(), 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() mp.lock.Lock()
if !mp.checkTxConflicts(t) { if !mp.checkTxConflicts(t, fee) {
mp.lock.Unlock() mp.lock.Unlock()
return ErrConflict return ErrConflict
} }
@ -206,6 +242,7 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
copy(mp.verifiedTxes[n+1:], mp.verifiedTxes[n:]) copy(mp.verifiedTxes[n+1:], mp.verifiedTxes[n:])
mp.verifiedTxes[n] = pItem mp.verifiedTxes[n] = pItem
} }
mp.addSendersFee(pItem.txn)
// For lots of inputs it might be easier to push them all and sort // For lots of inputs it might be easier to push them all and sort
// afterwards, but that requires benchmarking. // afterwards, but that requires benchmarking.
@ -241,6 +278,9 @@ func (mp *Pool) Remove(hash util.Uint256) {
} else if num == len(mp.verifiedTxes)-1 { } else if num == len(mp.verifiedTxes)-1 {
mp.verifiedTxes = mp.verifiedTxes[:num] 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 { for i := range it.txn.Inputs {
dropInputFromSortedSlice(&mp.inputs, &it.txn.Inputs[i]) 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 // RemoveStale filters verified transactions through the given function keeping
// only the transactions for which it returns a true result. It's used to quickly // 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. // 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() mp.lock.Lock()
// We can reuse already allocated slice // We can reuse already allocated slice
// because items are iterated one-by-one in increasing order. // because items are iterated one-by-one in increasing order.
newVerifiedTxes := mp.verifiedTxes[:0] newVerifiedTxes := mp.verifiedTxes[:0]
newInputs := mp.inputs[:0] newInputs := mp.inputs[:0]
newClaims := mp.claims[: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 { for _, itm := range mp.verifiedTxes {
if isOK(itm.txn) { if isOK(itm.txn) && mp.tryAddSendersFee(itm.txn, feer) {
newVerifiedTxes = append(newVerifiedTxes, itm) newVerifiedTxes = append(newVerifiedTxes, itm)
for i := range itm.txn.Inputs { for i := range itm.txn.Inputs {
newInputs = append(newInputs, &itm.txn.Inputs[i]) newInputs = append(newInputs, &itm.txn.Inputs[i])
@ -299,6 +340,7 @@ func NewMemPool(capacity int) Pool {
verifiedMap: make(map[util.Uint256]*item), verifiedMap: make(map[util.Uint256]*item),
verifiedTxes: make([]*item, 0, capacity), verifiedTxes: make([]*item, 0, capacity),
capacity: 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() mp.lock.RLock()
defer mp.lock.RUnlock() defer mp.lock.RUnlock()
if pItem, ok := mp.verifiedMap[hash]; ok { if pItem, ok := mp.verifiedMap[hash]; ok {
return pItem.txn, pItem.netFee, ok return pItem.txn, pItem.txn.NetworkFee, ok
} }
return nil, 0, false return nil, 0, false
@ -323,7 +365,7 @@ func (mp *Pool) GetVerifiedTransactions() []TxWithFee {
for i := range mp.verifiedTxes { for i := range mp.verifiedTxes {
t[i].Tx = mp.verifiedTxes[i].txn t[i].Tx = mp.verifiedTxes[i].txn
t[i].Fee = mp.verifiedTxes[i].netFee t[i].Fee = mp.verifiedTxes[i].txn.NetworkFee
} }
return t return t
@ -342,10 +384,13 @@ func areInputsInPool(inputs []transaction.Input, pool []*transaction.Input) bool
} }
// checkTxConflicts is an internal unprotected version of Verify. // 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) { if areInputsInPool(tx.Inputs, mp.inputs) {
return false return false
} }
if !mp.checkBalanceAndUpdate(tx, fee) {
return false
}
switch tx.Type { switch tx.Type {
case transaction.ClaimType: case transaction.ClaimType:
claim := tx.Data.(*transaction.ClaimTX) 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. // 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 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. // 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() mp.lock.RLock()
defer mp.lock.RUnlock() defer mp.lock.RUnlock()
return mp.checkTxConflicts(tx) return mp.checkTxConflicts(tx, feer)
} }

View file

@ -13,25 +13,19 @@ import (
type FeerStub struct { type FeerStub struct {
lowPriority bool lowPriority bool
sysFee util.Fixed8 feePerByte util.Fixed8
netFee util.Fixed8
perByteFee util.Fixed8
}
func (fs *FeerStub) NetworkFee(*transaction.Transaction) util.Fixed8 {
return fs.netFee
} }
func (fs *FeerStub) IsLowPriority(util.Fixed8) bool { func (fs *FeerStub) IsLowPriority(util.Fixed8) bool {
return fs.lowPriority return fs.lowPriority
} }
func (fs *FeerStub) FeePerByte(*transaction.Transaction) util.Fixed8 { func (fs *FeerStub) FeePerByte() util.Fixed8 {
return fs.perByteFee return fs.feePerByte
} }
func (fs *FeerStub) SystemFee(*transaction.Transaction) util.Fixed8 { func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) util.Fixed8 {
return fs.sysFee return util.Fixed8FromInt64(10000)
} }
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) { func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
@ -121,7 +115,7 @@ func TestMemPoolAddRemoveWithInputsAndClaims(t *testing.T) {
return false return false
} }
return true return true
}) }, &FeerStub{})
assert.Equal(t, len(txm1.Inputs), len(mp.inputs)) assert.Equal(t, len(txm1.Inputs), len(mp.inputs))
assert.True(t, sort.SliceIsSorted(mp.inputs, mpLessInputs)) assert.True(t, sort.SliceIsSorted(mp.inputs, mpLessInputs))
assert.Equal(t, len(claim2.Claims), len(mp.claims)) assert.Equal(t, len(claim2.Claims), len(mp.claims))
@ -134,24 +128,24 @@ func TestMemPoolVerifyInputs(t *testing.T) {
tx.Nonce = 1 tx.Nonce = 1
inhash1 := random.Uint256() inhash1 := random.Uint256()
tx.Inputs = append(tx.Inputs, transaction.Input{PrevHash: inhash1, PrevIndex: 0}) 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{})) require.NoError(t, mp.Add(tx, &FeerStub{}))
tx2 := transaction.NewContractTX() tx2 := transaction.NewContractTX()
tx2.Nonce = 2 tx2.Nonce = 2
inhash2 := random.Uint256() inhash2 := random.Uint256()
tx2.Inputs = append(tx2.Inputs, transaction.Input{PrevHash: inhash2, PrevIndex: 0}) 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{})) require.NoError(t, mp.Add(tx2, &FeerStub{}))
tx3 := transaction.NewContractTX() tx3 := transaction.NewContractTX()
tx3.Nonce = 3 tx3.Nonce = 3
// Different index number, but the same PrevHash as in tx1. // Different index number, but the same PrevHash as in tx1.
tx3.Inputs = append(tx3.Inputs, transaction.Input{PrevHash: inhash1, PrevIndex: 1}) 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. // The same input as in tx2.
tx3.Inputs = append(tx3.Inputs, transaction.Input{PrevHash: inhash2, PrevIndex: 0}) 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{})) 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: hash1, PrevIndex: uint16(i)})
claim1.Claims = append(claim1.Claims, transaction.Input{PrevHash: hash2, 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{})) require.NoError(t, mp.Add(tx1, &FeerStub{}))
tx2, claim2 := newClaimTX() tx2, claim2 := newClaimTX()
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
claim2.Claims = append(claim2.Claims, transaction.Input{PrevHash: hash2, PrevIndex: uint16(i + 10)}) 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{})) require.NoError(t, mp.Add(tx2, &FeerStub{}))
tx3, claim3 := newClaimTX() tx3, claim3 := newClaimTX()
claim3.Claims = append(claim3.Claims, transaction.Input{PrevHash: hash1, PrevIndex: 0}) 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{})) require.Error(t, mp.Add(tx3, &FeerStub{}))
} }
func TestMemPoolVerifyIssue(t *testing.T) { func TestMemPoolVerifyIssue(t *testing.T) {
mp := NewMemPool(50) mp := NewMemPool(50)
tx1 := newIssueTX() tx1 := newIssueTX()
require.Equal(t, true, mp.Verify(tx1)) require.Equal(t, true, mp.Verify(tx1, &FeerStub{}))
require.NoError(t, mp.Add(tx1, &FeerStub{})) require.NoError(t, mp.Add(tx1, &FeerStub{}))
tx2 := newIssueTX() tx2 := newIssueTX()
require.Equal(t, false, mp.Verify(tx2)) require.Equal(t, false, mp.Verify(tx2, &FeerStub{}))
require.Error(t, mp.Add(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))) require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
// Fees are also prioritized. // Fees are also prioritized.
fs.netFee = util.Fixed8FromFloat(0.0001)
for i := 0; i < mempoolSize-1; i++ { for i := 0; i < mempoolSize-1; i++ {
tx := transaction.NewContractTX() 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 tx.Nonce = txcnt
txcnt++ txcnt++
// size is 84, networkFee is 0.0001 => feePerByte is 0.00000119
require.NoError(t, mp.Add(tx, fs)) require.NoError(t, mp.Add(tx, fs))
require.Equal(t, mempoolSize, mp.Count()) require.Equal(t, mempoolSize, mp.Count())
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes))) require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
} }
// Less prioritized txes are not allowed anymore. // Less prioritized txes are not allowed anymore.
fs.netFee = util.Fixed8FromFloat(0.00001)
tx := transaction.NewContractTX() 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 tx.Nonce = txcnt
txcnt++ txcnt++
require.Error(t, mp.Add(tx, fs)) require.Error(t, mp.Add(tx, fs))
@ -257,10 +260,12 @@ func TestOverCapacity(t *testing.T) {
require.True(t, mp.ContainsKey(claim.Hash())) require.True(t, mp.ContainsKey(claim.Hash()))
// Low net fee, but higher per-byte fee is still a better combination. // Low net fee, but higher per-byte fee is still a better combination.
fs.perByteFee = util.Fixed8FromFloat(0.001)
tx = transaction.NewContractTX() tx = transaction.NewContractTX()
tx.Nonce = txcnt tx.Nonce = txcnt
tx.NetworkFee = util.Fixed8FromFloat(0.00007)
txcnt++ 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.NoError(t, mp.Add(tx, fs))
require.Equal(t, mempoolSize, mp.Count()) require.Equal(t, mempoolSize, mp.Count())
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes))) require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
@ -334,7 +339,7 @@ func TestRemoveStale(t *testing.T) {
} }
} }
return false return false
}) }, &FeerStub{})
require.Equal(t, mempoolSize/2, mp.Count()) require.Equal(t, mempoolSize/2, mp.Count())
verTxes := mp.GetVerifiedTransactions() verTxes := mp.GetVerifiedTransactions()
for _, txf := range verTxes { for _, txf := range verTxes {
@ -342,3 +347,76 @@ func TestRemoveStale(t *testing.T) {
require.Contains(t, txes2, txf.Tx) 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 ( import (
"errors" "errors"
"fmt"
"math/big" "math/big"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
@ -28,21 +29,22 @@ const initialGAS = 30000000
// NewGAS returns GAS native contract. // NewGAS returns GAS native contract.
func NewGAS() *GAS { func NewGAS() *GAS {
g := &GAS{}
nep5 := newNEP5Native(gasSyscallName) nep5 := newNEP5Native(gasSyscallName)
nep5.name = "GAS" nep5.name = "GAS"
nep5.symbol = "gas" nep5.symbol = "gas"
nep5.decimals = 8 nep5.decimals = 8
nep5.factor = GASFactor 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, desc := newDescriptor("getSysFeeAmount", smartcontract.IntegerType,
manifest.NewParameter("index", smartcontract.IntegerType)) manifest.NewParameter("index", smartcontract.IntegerType))
md := newMethodAndPrice(g.getSysFeeAmount, 1, smartcontract.NoneFlag) md := newMethodAndPrice(g.getSysFeeAmount, 1, smartcontract.NoneFlag)
g.AddMethod(md, desc, true) g.AddMethod(md, desc, true)
g.onPersist = chainOnPersist(g.onPersist, g.OnPersist)
g.incBalance = g.increaseBalance
return g return g
} }
@ -79,15 +81,20 @@ func (g *GAS) Initialize(ic *interop.Context) error {
// OnPersist implements Contract interface. // OnPersist implements Contract interface.
func (g *GAS) OnPersist(ic *interop.Context) error { func (g *GAS) OnPersist(ic *interop.Context) error {
//for _ ,tx := range ic.block.Transactions { for _, tx := range ic.Block.Transactions {
// g.burn(ic, tx.Sender, tx.SystemFee + tx.NetworkFee) absAmount := big.NewInt(int64(tx.SystemFee + tx.NetworkFee))
//} g.burn(ic, tx.Sender, absAmount)
//validators := g.NEO.getNextBlockValidators(ic) }
//var netFee util.Fixed8 validators, err := g.NEO.GetValidatorsInternal(ic.Chain, ic.DAO)
//for _, tx := range ic.block.Transactions { if err != nil {
// netFee += tx.NetworkFee return fmt.Errorf("cannot get block validators: %v", err)
//} }
//g.mint(ic, <primary>, netFee) 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 return nil
} }

View file

@ -60,13 +60,16 @@ func makeValidatorKey(key *keys.PublicKey) []byte {
// NewNEO returns NEO native contract. // NewNEO returns NEO native contract.
func NewNEO() *NEO { func NewNEO() *NEO {
n := &NEO{}
nep5 := newNEP5Native(neoSyscallName) nep5 := newNEP5Native(neoSyscallName)
nep5.name = "NEO" nep5.name = "NEO"
nep5.symbol = "neo" nep5.symbol = "neo"
nep5.decimals = 0 nep5.decimals = 0
nep5.factor = 1 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, desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
manifest.NewParameter("account", smartcontract.Hash160Type), manifest.NewParameter("account", smartcontract.Hash160Type),
@ -97,9 +100,6 @@ func NewNEO() *NEO {
md = newMethodAndPrice(n.getNextBlockValidators, 1, smartcontract.NoneFlag) md = newMethodAndPrice(n.getNextBlockValidators, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true) n.AddMethod(md, desc, true)
n.onPersist = chainOnPersist(n.onPersist, n.OnPersist)
n.incBalance = n.increaseBalance
return n 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. // VoteInternal votes from account h for validarors specified in pubs.
func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.PublicKeys) error { 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 { if err != nil {
return err return err
} else if !ok { } else if !ok {
@ -479,24 +483,3 @@ func toPublicKey(s vm.StackItem) *keys.PublicKey {
} }
return pub 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" "math/big"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "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/core/state"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "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") 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) keyFrom := makeAccountKey(from)
siFrom := ic.DAO.GetStorageItem(c.Hash, keyFrom) siFrom := ic.DAO.GetStorageItem(c.Hash, keyFrom)
if siFrom == nil { 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) { func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) {
if sign := amount.Sign(); sign == -1 { if amount.Sign() == 0 {
panic("negative amount")
} else if sign == 0 {
return return
} }
@ -270,3 +280,26 @@ func toUint160(s vm.StackItem) util.Uint160 {
} }
return u 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. // Address signed the transaction.
Sender util.Uint160 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 // Maximum blockchain height exceeding which
// transaction should fail verification. // transaction should fail verification.
ValidUntilBlock uint32 ValidUntilBlock uint32
@ -119,7 +125,20 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) {
t.Version = uint8(br.ReadB()) t.Version = uint8(br.ReadB())
t.Nonce = br.ReadU32LE() t.Nonce = br.ReadU32LE()
t.Sender.DecodeBinary(br) 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.ValidUntilBlock = br.ReadU32LE()
t.decodeData(br) t.decodeData(br)
@ -192,6 +211,8 @@ func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
bw.WriteB(byte(t.Version)) bw.WriteB(byte(t.Version))
bw.WriteU32LE(t.Nonce) bw.WriteU32LE(t.Nonce)
t.Sender.EncodeBinary(bw) t.Sender.EncodeBinary(bw)
t.SystemFee.EncodeBinary(bw)
t.NetworkFee.EncodeBinary(bw)
bw.WriteU32LE(t.ValidUntilBlock) bw.WriteU32LE(t.ValidUntilBlock)
// Underlying TXer. // Underlying TXer.
@ -268,6 +289,12 @@ func NewTransactionFromBytes(b []byte) (*Transaction, error) {
return tx, nil 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 // transactionJSON is a wrapper for Transaction and
// used for correct marhalling of transaction.Data // used for correct marhalling of transaction.Data
type transactionJSON struct { type transactionJSON struct {
@ -277,6 +304,8 @@ type transactionJSON struct {
Version uint8 `json:"version"` Version uint8 `json:"version"`
Nonce uint32 `json:"nonce"` Nonce uint32 `json:"nonce"`
Sender string `json:"sender"` Sender string `json:"sender"`
SystemFee util.Fixed8 `json:"sys_fee"`
NetworkFee util.Fixed8 `json:"net_fee"`
ValidUntilBlock uint32 `json:"valid_until_block"` ValidUntilBlock uint32 `json:"valid_until_block"`
Attributes []Attribute `json:"attributes"` Attributes []Attribute `json:"attributes"`
Cosigners []Cosigner `json:"cosigners"` Cosigners []Cosigner `json:"cosigners"`
@ -305,6 +334,8 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
Inputs: t.Inputs, Inputs: t.Inputs,
Outputs: t.Outputs, Outputs: t.Outputs,
Scripts: t.Scripts, Scripts: t.Scripts,
SystemFee: t.SystemFee,
NetworkFee: t.NetworkFee,
} }
switch t.Type { switch t.Type {
case ClaimType: case ClaimType:
@ -341,6 +372,8 @@ func (t *Transaction) UnmarshalJSON(data []byte) error {
t.Inputs = tx.Inputs t.Inputs = tx.Inputs
t.Outputs = tx.Outputs t.Outputs = tx.Outputs
t.Scripts = tx.Scripts t.Scripts = tx.Scripts
t.SystemFee = tx.SystemFee
t.NetworkFee = tx.NetworkFee
sender, err := address.StringToUint160(tx.Sender) sender, err := address.StringToUint160(tx.Sender)
if err != nil { if err != nil {
return errors.New("cannot unmarshal tx: bad sender") 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/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "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/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "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 // utility (GAS) token. It's a part of the genesis block. It's mostly
// useful for its hash that represents GAS asset ID. // useful for its hash that represents GAS asset ID.
utilityTokenTX transaction.Transaction 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. // 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] 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. // have been changed. Consequently, hash of the genesis block has been changed.
// Update expected genesis block hash for better times. // Update expected genesis block hash for better times.
// Old hash is "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf" // Old hash is "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf"
expect := "56bb42c251ea2b216c5ee8306e94fe040613bc626a3813aa32fe07e7607b3a1a" expect := "1d4156d233220b893797a684fbb827bb2163b5042edd10653bbc1b2769adbb8d"
assert.Equal(t, expect, block.Hash().StringLE()) 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 //TODO: After we added Nonce field to transaction.Transaction, UtilityTockenTx hash
// has been changed. Update it for better times. // has been changed. Update it for better times.
// Old hash is "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" // Old hash is "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
expect := "8ef63ccd6f4ea20a93e7f4e84b2d43f778077612b241d617e42e1750cca4f2c5" expect := "f882fb865bab84b99623f21eedd902286af7da8d8a4609d7acefce04c851dc1c"
assert.Equal(t, expect, UtilityTokenID().StringLE()) 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 //TODO: After we added Nonce field to transaction.Transaction, GoveringTockenTx hash
// has been changed. Update it for better times. // has been changed. Update it for better times.
// Old hash is "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b" // Old hash is "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
expect := "7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc" expect := "1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78"
assert.Equal(t, expect, GoverningTokenID().StringLE()) assert.Equal(t, expect, GoverningTokenID().StringLE())
} }

View file

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

View file

@ -1,6 +1,7 @@
package client package client
import ( import (
"encoding/hex"
"errors" "errors"
"fmt" "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.AppCallWithOperationAndArgs(w.BinWriter, token.Hash, "transfer", from, to, amount)
emit.Opcode(w.BinWriter, opcode.ASSERT) emit.Opcode(w.BinWriter, opcode.ASSERT)
tx := transaction.NewInvocationTX(w.Bytes(), gas) script := w.Bytes()
tx := transaction.NewInvocationTX(script, gas)
tx.Sender = from 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() tx.ValidUntilBlock, err = c.CalculateValidUntilBlock()
if err != nil { 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) 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 { if err := acc.SignTx(tx); err != nil {
return util.Uint256{}, fmt.Errorf("can't sign tx: %v", err) 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 var err error
tx := transaction.NewInvocationTX(script, sysfee) tx := transaction.NewInvocationTX(script, sysfee)
tx.SystemFee = sysfee
validUntilBlock, err := c.CalculateValidUntilBlock() validUntilBlock, err := c.CalculateValidUntilBlock()
if err != nil { if err != nil {
return txHash, errors.Wrap(err, "failed to add validUntilBlock to transaction") 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 { if err = acc.SignTx(tx); err != nil {
return txHash, errors.Wrap(err, "failed to sign tx") return txHash, errors.Wrap(err, "failed to sign tx")
} }
@ -588,3 +595,33 @@ func (c *Client) CalculateValidUntilBlock() (uint32, error) {
} }
return blockCount + validatorsCount, nil 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) { invoke: func(c *Client) (interface{}, error) {
return c.GetAccountState("") 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{} { result: func(c *Client) interface{} {
scriptHash, err := util.Uint160DecodeStringLE("1179716da2e9523d153a35fb3ad10c561b1e5b1a") scriptHash, err := util.Uint160DecodeStringLE("1179716da2e9523d153a35fb3ad10c561b1e5b1a")
if err != nil { if err != nil {
@ -98,7 +98,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetAssetState(util.Uint256{}) 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{} { result: func(c *Client) interface{} {
return &result.AssetState{ return &result.AssetState{
ID: core.GoverningTokenID(), ID: core.GoverningTokenID(),
@ -138,17 +138,17 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetBlockByIndex(202) 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{} }, result: func(c *Client) interface{} { return &block.Block{} },
check: func(t *testing.T, c *Client, result interface{}) { check: func(t *testing.T, c *Client, result interface{}) {
res, ok := result.(*block.Block) res, ok := result.(*block.Block)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, uint32(0), res.Version) assert.Equal(t, uint32(0), res.Version)
assert.Equal(t, "75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45", res.Hash().StringLE()) assert.Equal(t, "cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86", res.Hash().StringLE())
assert.Equal(t, "6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039", res.PrevHash.StringLE()) assert.Equal(t, "93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef", res.PrevHash.StringLE())
assert.Equal(t, "cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405", res.MerkleRoot.StringLE()) assert.Equal(t, "b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d", res.MerkleRoot.StringLE())
assert.Equal(t, 1, len(res.Transactions)) 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) { invoke: func(c *Client) (i interface{}, err error) {
return c.GetBlockByIndexVerbose(202) 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{} { result: func(c *Client) interface{} {
hash, err := util.Uint256DecodeStringLE("75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45") hash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86")
if err != nil { if err != nil {
panic(err) panic(err)
} }
nextBlockHash, err := util.Uint256DecodeStringLE("ec27fed4ff8ad6a87bdd29aecee43e9ecb4e336321dac875d0b14d2aab1c5798") nextBlockHash, err := util.Uint256DecodeStringLE("13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22")
if err != nil { if err != nil {
panic(err) panic(err)
} }
prevBlockHash, err := util.Uint256DecodeStringLE("6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039") prevBlockHash, err := util.Uint256DecodeStringLE("93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef")
if err != nil { if err != nil {
panic(err) panic(err)
} }
merkleRoot, err := util.Uint256DecodeStringLE("cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405") merkleRoot, err := util.Uint256DecodeStringLE("b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d")
if err != nil { if err != nil {
panic(err) panic(err)
} }
invScript, err := hex.DecodeString("0c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b776010") invScript, err := hex.DecodeString("0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996")
if err != nil { if err != nil {
panic(err) panic(err)
} }
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb") verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb")
if err != nil { if err != nil {
panic(err) panic(err)
} }
sender, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR") sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil { if err != nil {
panic(err) panic(err)
} }
txInvScript, err := hex.DecodeString("0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff") txInvScript, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
if err != nil { if err != nil {
panic(err) panic(err)
} }
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4") txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
if err != nil { if err != nil {
panic(err) panic(err)
} }
vin, err := util.Uint256DecodeStringLE("1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6") vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
if err != nil { if err != nil {
panic(err) panic(err)
} }
outAddress, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR") outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -231,14 +231,14 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
_ = tx.Hash() _ = tx.Hash()
return &result.Block{ return &result.Block{
Hash: hash, Hash: hash,
Size: 765, Size: 781,
Version: 0, Version: 0,
NextBlockHash: &nextBlockHash, NextBlockHash: &nextBlockHash,
PreviousBlockHash: prevBlockHash, PreviousBlockHash: prevBlockHash,
MerkleRoot: merkleRoot, MerkleRoot: merkleRoot,
Time: 1588259741, Time: 1589300496,
Index: 202, Index: 202,
NextConsensus: "Ad1wDxzcRiRSryvJobNV211Tv7UUiziPXy", NextConsensus: "AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL",
Confirmations: 6, Confirmations: 6,
ConsensusData: result.ConsensusData{ ConsensusData: result.ConsensusData{
PrimaryIndex: 0, PrimaryIndex: 0,
@ -248,90 +248,84 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
InvocationScript: invScript, InvocationScript: invScript,
VerificationScript: verifScript, VerificationScript: verifScript,
}, },
Tx: []result.Tx{{ Tx: []*transaction.Transaction{tx},
Transaction: tx,
Fees: result.Fees{
SysFee: 0,
NetFee: 0,
},
}},
} }
}, },
}, },
{ {
name: "byHash_positive", name: "byHash_positive",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint256DecodeStringLE("45bc7f077a4381c9836164b242af97ffa59e5d233ccba4929796ce873474ab75") hash, err := util.Uint256DecodeStringLE("86fe1061140b2ea791b0739fb9732abc6e5e47de4927228a1ac41de3d93eb7cb")
if err != nil { if err != nil {
panic(err) panic(err)
} }
return c.GetBlockByHash(hash) 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{} }, result: func(c *Client) interface{} { return &block.Block{} },
check: func(t *testing.T, c *Client, result interface{}) { check: func(t *testing.T, c *Client, result interface{}) {
res, ok := result.(*block.Block) res, ok := result.(*block.Block)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, uint32(0), res.Version) assert.Equal(t, uint32(0), res.Version)
assert.Equal(t, "75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45", res.Hash().StringLE()) assert.Equal(t, "cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86", res.Hash().StringLE())
assert.Equal(t, "6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039", res.PrevHash.StringLE()) assert.Equal(t, "93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef", res.PrevHash.StringLE())
assert.Equal(t, "cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405", res.MerkleRoot.StringLE()) assert.Equal(t, "b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d", res.MerkleRoot.StringLE())
assert.Equal(t, 1, len(res.Transactions)) 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", name: "byHash_verbose_positive",
invoke: func(c *Client) (i interface{}, err error) { invoke: func(c *Client) (i interface{}, err error) {
hash, err := util.Uint256DecodeStringLE("45bc7f077a4381c9836164b242af97ffa59e5d233ccba4929796ce873474ab75") hash, err := util.Uint256DecodeStringLE("86fe1061140b2ea791b0739fb9732abc6e5e47de4927228a1ac41de3d93eb7cb")
if err != nil { if err != nil {
panic(err) panic(err)
} }
return c.GetBlockByHashVerbose(hash) 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{} { result: func(c *Client) interface{} {
hash, err := util.Uint256DecodeStringLE("75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45") hash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86")
if err != nil { if err != nil {
panic(err) panic(err)
} }
nextBlockHash, err := util.Uint256DecodeStringLE("ec27fed4ff8ad6a87bdd29aecee43e9ecb4e336321dac875d0b14d2aab1c5798") nextBlockHash, err := util.Uint256DecodeStringLE("13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22")
if err != nil { if err != nil {
panic(err) panic(err)
} }
prevBlockHash, err := util.Uint256DecodeStringLE("6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039") prevBlockHash, err := util.Uint256DecodeStringLE("93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef")
if err != nil { if err != nil {
panic(err) panic(err)
} }
merkleRoot, err := util.Uint256DecodeStringLE("cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405") merkleRoot, err := util.Uint256DecodeStringLE("b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d")
if err != nil { if err != nil {
panic(err) panic(err)
} }
invScript, err := hex.DecodeString("0c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b776010") invScript, err := hex.DecodeString("0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996")
if err != nil { if err != nil {
panic(err) panic(err)
} }
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb") verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb")
if err != nil { if err != nil {
panic(err) panic(err)
} }
sender, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR") sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil { if err != nil {
panic(err) panic(err)
} }
txInvScript, err := hex.DecodeString("0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff") txInvScript, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
if err != nil { if err != nil {
panic(err) panic(err)
} }
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4") txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
if err != nil { if err != nil {
panic(err) panic(err)
} }
vin, err := util.Uint256DecodeStringLE("1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6") vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
if err != nil { if err != nil {
panic(err) panic(err)
} }
outAddress, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR") outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -364,14 +358,14 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
_ = tx.Hash() _ = tx.Hash()
return &result.Block{ return &result.Block{
Hash: hash, Hash: hash,
Size: 765, Size: 781,
Version: 0, Version: 0,
NextBlockHash: &nextBlockHash, NextBlockHash: &nextBlockHash,
PreviousBlockHash: prevBlockHash, PreviousBlockHash: prevBlockHash,
MerkleRoot: merkleRoot, MerkleRoot: merkleRoot,
Time: 1588259741, Time: 1589300496,
Index: 202, Index: 202,
NextConsensus: "Ad1wDxzcRiRSryvJobNV211Tv7UUiziPXy", NextConsensus: "AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL",
Confirmations: 6, Confirmations: 6,
ConsensusData: result.ConsensusData{ ConsensusData: result.ConsensusData{
PrimaryIndex: 0, PrimaryIndex: 0,
@ -381,13 +375,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
InvocationScript: invScript, InvocationScript: invScript,
VerificationScript: verifScript, VerificationScript: verifScript,
}, },
Tx: []result.Tx{{ Tx: []*transaction.Transaction{tx},
Transaction: tx,
Fees: result.Fees{
SysFee: 0,
NetFee: 0,
},
}},
} }
}, },
}, },
@ -701,19 +689,19 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{ {
name: "positive", name: "positive",
invoke: func(c *Client) (i interface{}, err error) { invoke: func(c *Client) (i interface{}, err error) {
hash, err := util.Uint256DecodeStringLE("05e5321b424e9ea4c5cdd7e28462612727e34aeac639c87e57c4e9d1f7755911") hash, err := util.Uint256DecodeStringLE("8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96")
if err != nil { if err != nil {
panic(err) panic(err)
} }
return c.GetRawTransaction(hash) 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{} }, result: func(c *Client) interface{} { return &transaction.Transaction{} },
check: func(t *testing.T, c *Client, result interface{}) { check: func(t *testing.T, c *Client, result interface{}) {
res, ok := result.(*transaction.Transaction) res, ok := result.(*transaction.Transaction)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, uint8(0), res.Version) 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, transaction.ContractType, res.Type)
assert.Equal(t, false, res.Trimmed) assert.Equal(t, false, res.Trimmed)
}, },
@ -721,35 +709,35 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{ {
name: "verbose_positive", name: "verbose_positive",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint256DecodeStringLE("05e5321b424e9ea4c5cdd7e28462612727e34aeac639c87e57c4e9d1f7755911") hash, err := util.Uint256DecodeStringLE("8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96")
if err != nil { if err != nil {
panic(err) panic(err)
} }
return c.GetRawTransactionVerbose(hash) 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{} { result: func(c *Client) interface{} {
blockHash, err := util.Uint256DecodeStringLE("75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45") blockHash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86")
if err != nil { if err != nil {
panic(err) panic(err)
} }
sender, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR") sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil { if err != nil {
panic(err) panic(err)
} }
invocation, err := hex.DecodeString("0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff") invocation, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
if err != nil { if err != nil {
panic(err) panic(err)
} }
verification, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4") verification, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
if err != nil { if err != nil {
panic(err) panic(err)
} }
vin, err := util.Uint256DecodeStringLE("1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6") vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
if err != nil { if err != nil {
panic(err) panic(err)
} }
outAddress, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR") outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -784,11 +772,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
return &result.TransactionOutputRaw{ return &result.TransactionOutputRaw{
Transaction: tx, Transaction: tx,
TransactionMetadata: result.TransactionMetadata{ TransactionMetadata: result.TransactionMetadata{
SysFee: 0,
NetFee: 0,
Blockhash: blockHash, Blockhash: blockHash,
Confirmations: 8, Confirmations: 8,
Timestamp: uint64(1588259741), Timestamp: uint64(1589300496),
}, },
} }
}, },

View file

@ -1,8 +1,6 @@
package result package result
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
@ -14,19 +12,6 @@ import (
) )
type ( 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 is a wrapper for block.ConsensusData
ConsensusData struct { ConsensusData struct {
PrimaryIndex uint32 `json:"primary"` PrimaryIndex uint32 `json:"primary"`
@ -51,7 +36,7 @@ type (
Script transaction.Witness `json:"script"` 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, Script: b.Script,
Tx: make([]Tx, 0, len(b.Transactions)), Tx: b.Transactions,
} }
hash := chain.GetHeaderHash(int(b.Index) + 1) hash := chain.GetHeaderHash(int(b.Index) + 1)
@ -82,60 +67,5 @@ func NewBlock(b *block.Block, chain blockchainer.Blockchainer) Block {
res.NextBlockHash = &hash 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 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. // TransactionMetadata is an auxilliary struct for proper TransactionOutputRaw marshaling.
type TransactionMetadata struct { type TransactionMetadata struct {
SysFee util.Fixed8 `json:"sys_fee"`
NetFee util.Fixed8 `json:"net_fee"`
Blockhash util.Uint256 `json:"blockhash,omitempty"` Blockhash util.Uint256 `json:"blockhash,omitempty"`
Confirmations int `json:"confirmations,omitempty"` Confirmations int `json:"confirmations,omitempty"`
Timestamp uint64 `json:"blocktime,omitempty"` Timestamp uint64 `json:"blocktime,omitempty"`
@ -37,8 +35,6 @@ func NewTransactionOutputRaw(tx *transaction.Transaction, header *block.Header,
return TransactionOutputRaw{ return TransactionOutputRaw{
Transaction: tx, Transaction: tx,
TransactionMetadata: TransactionMetadata{ TransactionMetadata: TransactionMetadata{
SysFee: chain.SystemFee(tx),
NetFee: chain.NetworkFee(tx),
Blockhash: header.Hash(), Blockhash: header.Hash(),
Confirmations: confirmations, Confirmations: confirmations,
Timestamp: header.Timestamp, Timestamp: header.Timestamp,
@ -49,8 +45,6 @@ func NewTransactionOutputRaw(tx *transaction.Transaction, header *block.Header,
// MarshalJSON implements json.Marshaler interface. // MarshalJSON implements json.Marshaler interface.
func (t TransactionOutputRaw) MarshalJSON() ([]byte, error) { func (t TransactionOutputRaw) MarshalJSON() ([]byte, error) {
output, err := json.Marshal(TransactionMetadata{ output, err := json.Marshal(TransactionMetadata{
SysFee: t.SysFee,
NetFee: t.NetFee,
Blockhash: t.Blockhash, Blockhash: t.Blockhash,
Confirmations: t.Confirmations, Confirmations: t.Confirmations,
Timestamp: t.Timestamp, Timestamp: t.Timestamp,
@ -82,8 +76,6 @@ func (t *TransactionOutputRaw) UnmarshalJSON(data []byte) error {
if err != nil { if err != nil {
return err return err
} }
t.SysFee = output.SysFee
t.NetFee = output.NetFee
t.Blockhash = output.Blockhash t.Blockhash = output.Blockhash
t.Confirmations = output.Confirmations t.Confirmations = output.Confirmations
t.Timestamp = output.Timestamp t.Timestamp = output.Timestamp

View file

@ -798,7 +798,7 @@ func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *respons
var blockSysFee util.Fixed8 var blockSysFee util.Fixed8
for _, tx := range block.Transactions { for _, tx := range block.Transactions {
blockSysFee += s.chain.SystemFee(tx) blockSysFee += tx.SystemFee
} }
return blockSysFee, nil 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"
"github.com/nspcc-dev/neo-go/pkg/core/block" "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/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/io"
"github.com/nspcc-dev/neo-go/pkg/network" "github.com/nspcc-dev/neo-go/pkg/network"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -65,18 +64,14 @@ func initServerWithInMemoryChain(t *testing.T) (*core.Blockchain, *httptest.Serv
type FeerStub struct{} type FeerStub struct{}
func (fs *FeerStub) NetworkFee(*transaction.Transaction) util.Fixed8 {
return 0
}
func (fs *FeerStub) IsLowPriority(util.Fixed8) bool { func (fs *FeerStub) IsLowPriority(util.Fixed8) bool {
return false return false
} }
func (fs *FeerStub) FeePerByte(*transaction.Transaction) util.Fixed8 { func (fs *FeerStub) FeePerByte() util.Fixed8 {
return 0 return 0
} }
func (fs *FeerStub) SystemFee(*transaction.Transaction) util.Fixed8 { func (fs *FeerStub) GetUtilityTokenBalance(acc util.Uint160) util.Fixed8 {
return 0 return util.Fixed8FromInt64(1000000)
} }

View file

@ -54,12 +54,12 @@ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": { "getapplicationlog": {
{ {
name: "positive", name: "positive",
params: `["396d55aa14b6cd428d793e9e740d24f93f62d7ddcdc0f4fdadd4dfd89bdabd83"]`, params: `["0a0abf0188053113d0014e0cb9801d090a5d3e7640d76427fa1a3676e7cdf82e"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} }, result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) { check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog) res, ok := acc.(*result.ApplicationLog)
require.True(t, ok) require.True(t, ok)
expectedTxHash, err := util.Uint256DecodeStringLE("396d55aa14b6cd428d793e9e740d24f93f62d7ddcdc0f4fdadd4dfd89bdabd83") expectedTxHash, err := util.Uint256DecodeStringLE("0a0abf0188053113d0014e0cb9801d090a5d3e7640d76427fa1a3676e7cdf82e")
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, expectedTxHash, res.TxHash) assert.Equal(t, expectedTxHash, res.TxHash)
assert.Equal(t, 1, len(res.Executions)) 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{}) { check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.NEP5Balances) res, ok := acc.(*result.NEP5Balances)
require.True(t, ok) 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, testchain.PrivateKeyByID(0).Address(), res.Address)
require.Equal(t, 1, len(res.Balances)) require.ElementsMatch(t, expected.Balances, 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)
}, },
}, },
}, },
@ -196,20 +208,56 @@ var rpcTestCases = map[string][]rpcTestCase{
check: func(t *testing.T, e *executor, acc interface{}) { check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.NEP5Transfers) res, ok := acc.(*result.NEP5Transfers)
require.True(t, ok) require.True(t, ok)
require.Equal(t, testchain.PrivateKeyByID(0).Address(), res.Address) rublesHash, err := util.Uint160DecodeStringLE(testContractHash)
assetHash, err := util.Uint160DecodeStringLE(testContractHash)
require.NoError(t, err) require.NoError(t, err)
blockSendRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(208))
require.Equal(t, 1, len(res.Received)) require.NoError(t, err)
require.Equal(t, "10", res.Received[0].Amount) require.Equal(t, 1, len(blockSendRubles.Transactions))
require.Equal(t, assetHash, res.Received[0].Asset) txSendRublesHash := blockSendRubles.Transactions[0].Hash()
require.Equal(t, address.Uint160ToString(assetHash), res.Received[0].Address) blockRecieveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(207))
require.NoError(t, err)
require.Equal(t, 1, len(res.Sent)) require.Equal(t, 2, len(blockRecieveRubles.Transactions))
require.Equal(t, "1.23", res.Sent[0].Amount) txRecieveRublesHash := blockRecieveRubles.Transactions[1].Hash()
require.Equal(t, assetHash, res.Sent[0].Asset) blockRecieveGAS, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
require.Equal(t, testchain.PrivateKeyByID(1).Address(), res.Sent[0].Address) 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": { "getassetstate": {
{ {
name: "positive", name: "positive",
params: `["8ef63ccd6f4ea20a93e7f4e84b2d43f778077612b241d617e42e1750cca4f2c5"]`, params: `["f882fb865bab84b99623f21eedd902286af7da8d8a4609d7acefce04c851dc1c"]`,
result: func(e *executor) interface{} { return &result.AssetState{} }, result: func(e *executor) interface{} { return &result.AssetState{} },
check: func(t *testing.T, e *executor, as interface{}) { check: func(t *testing.T, e *executor, as interface{}) {
res, ok := as.(*result.AssetState) res, ok := as.(*result.AssetState)
@ -327,24 +375,24 @@ var rpcTestCases = map[string][]rpcTestCase{
"getblock": { "getblock": {
{ {
name: "positive", name: "positive",
params: "[2, 1]", params: "[3, 1]",
result: func(e *executor) interface{} { return &result.Block{} }, result: func(e *executor) interface{} { return &result.Block{} },
check: func(t *testing.T, e *executor, blockRes interface{}) { check: func(t *testing.T, e *executor, blockRes interface{}) {
res, ok := blockRes.(*result.Block) res, ok := blockRes.(*result.Block)
require.True(t, ok) 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") require.NoErrorf(t, err, "could not get block")
assert.Equal(t, block.Hash(), res.Hash) assert.Equal(t, block.Hash(), res.Hash)
for i := range res.Tx { for i := range res.Tx {
tx := res.Tx[i] tx := res.Tx[i]
require.Equal(t, transaction.ContractType, tx.Transaction.Type) require.Equal(t, transaction.ContractType, tx.Type)
actualTx := block.Transactions[i] actualTx := block.Transactions[i]
require.True(t, ok) require.True(t, ok)
require.Equal(t, actualTx.Nonce, tx.Transaction.Nonce) require.Equal(t, actualTx.Nonce, tx.Nonce)
require.Equal(t, block.Transactions[i].Hash(), tx.Transaction.Hash()) require.Equal(t, block.Transactions[i].Hash(), tx.Hash())
} }
}, },
}, },
@ -436,7 +484,7 @@ var rpcTestCases = map[string][]rpcTestCase{
var expectedBlockSysFee util.Fixed8 var expectedBlockSysFee util.Fixed8
for _, tx := range block.Transactions { for _, tx := range block.Transactions {
expectedBlockSysFee += e.chain.SystemFee(tx) expectedBlockSysFee += tx.SystemFee
} }
return &expectedBlockSysFee return &expectedBlockSysFee
}, },
@ -473,7 +521,7 @@ var rpcTestCases = map[string][]rpcTestCase{
params: `["` + testchain.MultisigAddress() + `"]`, params: `["` + testchain.MultisigAddress() + `"]`,
result: func(*executor) interface{} { result: func(*executor) interface{} {
// hash of the issueTx // hash of the issueTx
h, _ := util.Uint256DecodeStringBE("3b76c9b726ffa9074a69441bf946c4c70b83474b3cf522ea3ba9dcd71c1a3db8") h, _ := util.Uint256DecodeStringBE("d3a4f2249fe33b18bde73901c1ecc66200485f1c1dcd941b406a630b479090ae")
amount := util.Fixed8FromInt64(1 * 8) // (endHeight - startHeight) * genAmount[0] amount := util.Fixed8FromInt64(1 * 8) // (endHeight - startHeight) * genAmount[0]
return &result.ClaimableInfo{ return &result.ClaimableInfo{
Spents: []result.Claimable{ Spents: []result.Claimable{
@ -532,7 +580,7 @@ var rpcTestCases = map[string][]rpcTestCase{
"gettransactionheight": { "gettransactionheight": {
{ {
name: "positive", name: "positive",
params: `["a05ea6d90b761ec5430f29d25036fdad04efe731b6a5906f4fd1e19048dee0f2"]`, params: `["0e873d5d565a03c6cd39efa3b446e1901b4636c448a22bc7e8c259c5a28a2eda"]`,
result: func(e *executor) interface{} { result: func(e *executor) interface{} {
h := 1 h := 1
return &h return &h
@ -744,7 +792,7 @@ var rpcTestCases = map[string][]rpcTestCase{
"sendrawtransaction": { "sendrawtransaction": {
{ {
name: "positive", name: "positive",
params: `["80000b000000316e851039019d39dfc2c37d6c3fee19fd580987b0040000000001241a237db30af3b33d29518288e0c9b542475733f78f4c6d7416cba89c1f1a67010001dcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d0030d3dec3862300316e851039019d39dfc2c37d6c3fee19fd58098701420c40c3c3fedb73e36a8e78bae80ba07c20b34f5af6bba36dccbe0cdc7d4e1237f85eec36e63cb762e1e6ce031a6c752e1ce19a3994d15191a6b75f1a02ede7f9d117290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"]`, params: `["80000b000000316e851039019d39dfc2c37d6c3fee19fd5809870000000000000000a267050000000000b00400000000017a03a89832a347c4fb53af1f526d0d930b14ab6eb01629ce20ffbaeaeef58af3010001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a0030d3dec3862300316e851039019d39dfc2c37d6c3fee19fd58098701420c40b6aeec1d2699194b842f399448b395d98bbb287dc89ea9e5ce3bb99a1c8c9bf933f55b69db6709b44e6a5c8b28b97018466479e5d500e414a0874c37abab262d290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"]`,
result: func(e *executor) interface{} { result: func(e *executor) interface{} {
v := true v := true
return &v return &v
@ -867,16 +915,26 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
checkErrGetResult(t, body, true) checkErrGetResult(t, body, true)
}) })
wif := testchain.WIF(0) priv0 := testchain.PrivateKeyByID(0)
acc, err := wallet.NewAccountFromWIF(wif) acc0, err := wallet.NewAccountFromWIF(priv0.WIF())
require.NoError(t, err) 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 { newTx := func() *transaction.Transaction {
height := chain.BlockHeight() height := chain.BlockHeight()
tx := transaction.NewContractTX() tx := transaction.NewContractTX()
tx.Nonce = height + 1 tx.Nonce = height + 1
tx.ValidUntilBlock = height + 10 tx.ValidUntilBlock = height + 10
tx.Sender = acc.PrivateKey().GetScriptHash() tx.Sender = acc0.PrivateKey().GetScriptHash()
require.NoError(t, acc.SignTx(tx)) addNetworkFee(tx)
require.NoError(t, acc0.SignTx(tx))
return tx return tx
} }
@ -905,7 +963,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
var res string var res string
err := json.Unmarshal(result, &res) err := json.Unmarshal(result, &res)
require.NoErrorf(t, err, "could not parse response: %s", result) 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) { 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 var res string
err := json.Unmarshal(result, &res) err := json.Unmarshal(result, &res)
require.NoErrorf(t, err, "could not parse response: %s", result) 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) { 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) err := json.Unmarshal(res, &txOut)
require.NoErrorf(t, err, "could not parse response: %s", res) require.NoErrorf(t, err, "could not parse response: %s", res)
assert.Equal(t, 0, txOut.N) 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, util.Fixed8FromInt64(100000000), txOut.Value)
assert.Equal(t, testchain.MultisigAddress(), txOut.Address) 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. // AddSignature adds a signature for the specified contract and public key.
func (c *ParameterContext) AddSignature(ctr *wallet.Contract, pub *keys.PublicKey, sig []byte) error { func (c *ParameterContext) AddSignature(ctr *wallet.Contract, pub *keys.PublicKey, sig []byte) error {
item := c.getItemForContract(ctr) 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 { if item.GetSignature(pub) != nil {
return errors.New("signature is already added") return errors.New("signature is already added")
} }

View file

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