forked from TrueCloudLab/neoneo-go
Merge pull request #943 from nspcc-dev/neo3/transaction/fees
core: add SystemFee and NetworkFee to transaction
This commit is contained in:
commit
c751472852
28 changed files with 894 additions and 397 deletions
|
@ -277,6 +277,10 @@ func claimGas(ctx *cli.Context) error {
|
|||
ScriptHash: scriptHash,
|
||||
})
|
||||
|
||||
err = c.AddNetworkFee(tx, acc)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
_ = acc.SignTx(tx)
|
||||
if err := c.SendRawTransaction(tx); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
|
@ -538,6 +542,11 @@ func transferAsset(ctx *cli.Context) error {
|
|||
Position: 1,
|
||||
})
|
||||
|
||||
err = c.AddNetworkFee(tx, acc)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
if outFile := ctx.String("out"); outFile != "" {
|
||||
priv := acc.PrivateKey()
|
||||
pub := priv.PublicKey()
|
||||
|
|
|
@ -500,9 +500,7 @@ func newBlockFromContext(ctx *dbft.Context) block.Block {
|
|||
Nonce: ctx.Nonce,
|
||||
}
|
||||
|
||||
if len(ctx.TransactionHashes) != 0 {
|
||||
mt := merkle.NewMerkleTree(append([]util.Uint256{consensusData.Hash()}, ctx.TransactionHashes...)...)
|
||||
block.Block.MerkleRoot = mt.Root().Hash
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ func TestNewService(t *testing.T) {
|
|||
tx := transaction.NewContractTX()
|
||||
tx.ValidUntilBlock = 1
|
||||
addSender(t, tx)
|
||||
signTx(t, tx)
|
||||
signTx(t, srv.Chain.FeePerByte(), tx)
|
||||
require.NoError(t, srv.Chain.PoolTx(tx))
|
||||
|
||||
var txx []block.Transaction
|
||||
|
@ -45,7 +45,7 @@ func TestService_GetVerified(t *testing.T) {
|
|||
txs = append(txs, tx)
|
||||
}
|
||||
addSender(t, txs...)
|
||||
signTx(t, txs...)
|
||||
signTx(t, srv.Chain.FeePerByte(), txs...)
|
||||
require.NoError(t, srv.Chain.PoolTx(txs[3]))
|
||||
|
||||
hashes := []util.Uint256{txs[0].Hash(), txs[1].Hash(), txs[2].Hash()}
|
||||
|
@ -124,7 +124,7 @@ func TestService_getTx(t *testing.T) {
|
|||
tx.Nonce = 1234
|
||||
tx.ValidUntilBlock = 1
|
||||
addSender(t, tx)
|
||||
signTx(t, tx)
|
||||
signTx(t, srv.Chain.FeePerByte(), tx)
|
||||
h := tx.Hash()
|
||||
|
||||
require.Equal(t, nil, srv.getTx(h))
|
||||
|
@ -229,10 +229,7 @@ func newTestChain(t *testing.T) *core.Blockchain {
|
|||
|
||||
type feer struct{}
|
||||
|
||||
func (fs *feer) NetworkFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
|
||||
func (fs *feer) IsLowPriority(util.Fixed8) bool { return false }
|
||||
func (fs *feer) FeePerByte(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
|
||||
func (fs *feer) SystemFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
|
||||
|
||||
var neoOwner = testchain.MultisigScriptHash()
|
||||
|
||||
|
@ -242,7 +239,7 @@ func addSender(t *testing.T, txs ...*transaction.Transaction) {
|
|||
}
|
||||
}
|
||||
|
||||
func signTx(t *testing.T, txs ...*transaction.Transaction) {
|
||||
func signTx(t *testing.T, feePerByte util.Fixed8, txs ...*transaction.Transaction) {
|
||||
validators := make([]*keys.PublicKey, 4)
|
||||
privNetKeys := make([]*keys.PrivateKey, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
|
@ -253,6 +250,11 @@ func signTx(t *testing.T, txs ...*transaction.Transaction) {
|
|||
rawScript, err := smartcontract.CreateMultiSigRedeemScript(3, validators)
|
||||
require.NoError(t, err)
|
||||
for _, tx := range txs {
|
||||
size := io.GetVarSize(tx)
|
||||
netFee, sizeDelta := core.CalculateNetworkFee(rawScript)
|
||||
tx.NetworkFee = tx.NetworkFee.Add(netFee)
|
||||
size += sizeDelta
|
||||
tx.NetworkFee = tx.NetworkFee.Add(util.Fixed8(int64(size) * int64(feePerByte)))
|
||||
data := tx.GetSignedPart()
|
||||
|
||||
buf := io.NewBufBinWriter()
|
||||
|
|
|
@ -466,7 +466,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
|||
cache := dao.NewCached(bc.dao)
|
||||
fee := bc.getSystemFeeAmount(block.PrevHash)
|
||||
for _, tx := range block.Transactions {
|
||||
fee += uint32(bc.SystemFee(tx).IntegralValue())
|
||||
fee += uint32(tx.SystemFee.IntegralValue())
|
||||
}
|
||||
if err := cache.StoreAsBlock(block, fee); err != nil {
|
||||
return err
|
||||
|
@ -651,17 +651,27 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
|||
continue
|
||||
}
|
||||
op, ok := arr[0].Value().([]byte)
|
||||
if !ok || string(op) != "transfer" {
|
||||
if !ok || (string(op) != "transfer" && string(op) != "Transfer") {
|
||||
continue
|
||||
}
|
||||
from, ok := arr[1].Value().([]byte)
|
||||
var from []byte
|
||||
fromValue := arr[1].Value()
|
||||
// we don't have `from` set when we are minting tokens
|
||||
if fromValue != nil {
|
||||
from, ok = fromValue.([]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
to, ok := arr[2].Value().([]byte)
|
||||
}
|
||||
var to []byte
|
||||
toValue := arr[2].Value()
|
||||
// we don't have `to` set when we are burning tokens
|
||||
if toValue != nil {
|
||||
to, ok = toValue.([]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
amount, ok := arr[3].Value().(*big.Int)
|
||||
if !ok {
|
||||
bs, ok := arr[3].Value().([]byte)
|
||||
|
@ -713,7 +723,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
|||
bc.topBlock.Store(block)
|
||||
atomic.StoreUint32(&bc.blockHeight, block.Index)
|
||||
updateBlockHeightMetric(block.Index)
|
||||
bc.memPool.RemoveStale(bc.isTxStillRelevant)
|
||||
bc.memPool.RemoveStale(bc.isTxStillRelevant, bc)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -807,6 +817,11 @@ func (bc *Blockchain) GetNEP5Balances(acc util.Uint160) *state.NEP5Balances {
|
|||
return bs
|
||||
}
|
||||
|
||||
// GetUtilityTokenBalance returns utility token (GAS) balance for the acc.
|
||||
func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) util.Fixed8 {
|
||||
return util.Fixed8FromInt64(bc.GetNEP5Balances(acc).Trackers[bc.contracts.GAS.Hash].Balance)
|
||||
}
|
||||
|
||||
// LastBatch returns last persisted storage batch.
|
||||
func (bc *Blockchain) LastBatch() *storage.MemBatch {
|
||||
return bc.lastBatch
|
||||
|
@ -1107,48 +1122,10 @@ func (bc *Blockchain) references(ins []transaction.Input) ([]transaction.InOut,
|
|||
return references, nil
|
||||
}
|
||||
|
||||
// FeePerByte returns network fee divided by the size of the transaction.
|
||||
func (bc *Blockchain) FeePerByte(t *transaction.Transaction) util.Fixed8 {
|
||||
return bc.NetworkFee(t).Div(int64(io.GetVarSize(t)))
|
||||
}
|
||||
|
||||
// NetworkFee returns network fee.
|
||||
func (bc *Blockchain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
|
||||
// https://github.com/neo-project/neo/blob/master-2.x/neo/Network/P2P/Payloads/ClaimTransaction.cs#L16
|
||||
if t.Type == transaction.ClaimType {
|
||||
return 0
|
||||
}
|
||||
|
||||
inputAmount := util.Fixed8FromInt64(0)
|
||||
refs, err := bc.References(t)
|
||||
if err != nil {
|
||||
return inputAmount
|
||||
}
|
||||
for i := range refs {
|
||||
if refs[i].Out.AssetID == UtilityTokenID() {
|
||||
inputAmount = inputAmount.Add(refs[i].Out.Amount)
|
||||
}
|
||||
}
|
||||
|
||||
outputAmount := util.Fixed8FromInt64(0)
|
||||
for _, txOutput := range t.Outputs {
|
||||
if txOutput.AssetID == UtilityTokenID() {
|
||||
outputAmount = outputAmount.Add(txOutput.Amount)
|
||||
}
|
||||
}
|
||||
|
||||
return inputAmount.Sub(outputAmount).Sub(bc.SystemFee(t))
|
||||
}
|
||||
|
||||
// SystemFee returns system fee.
|
||||
func (bc *Blockchain) SystemFee(t *transaction.Transaction) util.Fixed8 {
|
||||
if t.Type == transaction.InvocationType {
|
||||
inv := t.Data.(*transaction.InvocationTX)
|
||||
if inv.Version >= 1 {
|
||||
return inv.Gas
|
||||
}
|
||||
}
|
||||
return bc.GetConfig().SystemFee.TryGetValue(t.Type)
|
||||
// FeePerByte returns transaction network fee per byte.
|
||||
// TODO: should be implemented as part of PolicyContract
|
||||
func (bc *Blockchain) FeePerByte() util.Fixed8 {
|
||||
return util.Fixed8(1000)
|
||||
}
|
||||
|
||||
// IsLowPriority checks given fee for being less than configured
|
||||
|
@ -1199,14 +1176,25 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
|
|||
if t.ValidUntilBlock <= height || t.ValidUntilBlock > height+transaction.MaxValidUntilBlockIncrement {
|
||||
return errors.Errorf("transaction has expired. ValidUntilBlock = %d, current height = %d", t.ValidUntilBlock, height)
|
||||
}
|
||||
if io.GetVarSize(t) > transaction.MaxTransactionSize {
|
||||
balance := bc.GetUtilityTokenBalance(t.Sender)
|
||||
need := t.SystemFee.Add(t.NetworkFee)
|
||||
if balance.LessThan(need) {
|
||||
return errors.Errorf("insufficient funds: balance is %v, need: %v", balance, need)
|
||||
}
|
||||
size := io.GetVarSize(t)
|
||||
if size > transaction.MaxTransactionSize {
|
||||
return errors.Errorf("invalid transaction size = %d. It shoud be less then MaxTransactionSize = %d", io.GetVarSize(t), transaction.MaxTransactionSize)
|
||||
}
|
||||
needNetworkFee := util.Fixed8(int64(size) * int64(bc.FeePerByte()))
|
||||
netFee := t.NetworkFee.Sub(needNetworkFee)
|
||||
if netFee < 0 {
|
||||
return errors.Errorf("insufficient funds: net fee is %v, need %v", t.NetworkFee, needNetworkFee)
|
||||
}
|
||||
if transaction.HaveDuplicateInputs(t.Inputs) {
|
||||
return errors.New("invalid transaction's inputs")
|
||||
}
|
||||
if block == nil {
|
||||
if ok := bc.memPool.Verify(t); !ok {
|
||||
if ok := bc.memPool.Verify(t, bc); !ok {
|
||||
return errors.New("invalid transaction due to conflicts with the memory pool")
|
||||
}
|
||||
}
|
||||
|
@ -1382,9 +1370,8 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error {
|
|||
txSize := io.GetVarSize(t)
|
||||
maxFree := bc.config.MaxFreeTransactionSize
|
||||
if maxFree != 0 && txSize > maxFree {
|
||||
netFee := bc.NetworkFee(t)
|
||||
if bc.IsLowPriority(netFee) ||
|
||||
netFee < util.Fixed8FromFloat(bc.config.FeePerExtraByte)*util.Fixed8(txSize-maxFree) {
|
||||
if bc.IsLowPriority(t.NetworkFee) ||
|
||||
t.NetworkFee < util.Fixed8FromFloat(bc.config.FeePerExtraByte)*util.Fixed8(txSize-maxFree) {
|
||||
return ErrPolicy
|
||||
}
|
||||
}
|
||||
|
@ -1441,7 +1428,7 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction, results []*trans
|
|||
if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != UtilityTokenID() {
|
||||
return errors.New("tx destroys non-utility token")
|
||||
}
|
||||
sysfee := bc.SystemFee(t)
|
||||
sysfee := t.SystemFee
|
||||
if sysfee.GreaterThan(util.Fixed8(0)) {
|
||||
if len(resultsDestroy) == 0 {
|
||||
return fmt.Errorf("system requires to pay %s fee, but tx pays nothing", sysfee.String())
|
||||
|
@ -1711,6 +1698,16 @@ func (bc *Blockchain) verifyHeaderWitnesses(currHeader, prevHeader *block.Header
|
|||
return bc.verifyHashAgainstScript(hash, &currHeader.Script, interopCtx, true)
|
||||
}
|
||||
|
||||
// GoverningTokenHash returns the governing token (NEO) native contract hash.
|
||||
func (bc *Blockchain) GoverningTokenHash() util.Uint160 {
|
||||
return bc.contracts.NEO.Hash
|
||||
}
|
||||
|
||||
// UtilityTokenHash returns the utility token (GAS) native contract hash.
|
||||
func (bc *Blockchain) UtilityTokenHash() util.Uint160 {
|
||||
return bc.contracts.GAS.Hash
|
||||
}
|
||||
|
||||
func hashAndIndexToBytes(h util.Uint256, index uint32) []byte {
|
||||
buf := io.NewBufBinWriter()
|
||||
buf.WriteBytes(h.BytesLE())
|
||||
|
|
|
@ -167,10 +167,23 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
bc := newTestChain(t)
|
||||
defer bc.Close()
|
||||
|
||||
// Move almost all NEO to one simple account.
|
||||
txMoveNeo := transaction.NewContractTX()
|
||||
gasHash := bc.contracts.GAS.Hash
|
||||
t.Logf("native GAS hash: %v", gasHash)
|
||||
|
||||
priv0 := testchain.PrivateKeyByID(0)
|
||||
priv0ScriptHash := priv0.GetScriptHash()
|
||||
|
||||
// Move almost all NEO and some nep5 GAS to one simple account.
|
||||
txMoveNeo := newNEP5Transfer(gasHash, neoOwner, priv0ScriptHash, 1000000000)
|
||||
txMoveNeo.ValidUntilBlock = validUntilBlock
|
||||
txMoveNeo.Nonce = getNextNonce()
|
||||
txMoveNeo.Sender = neoOwner
|
||||
txMoveNeo.Cosigners = []transaction.Cosigner{{
|
||||
Account: neoOwner,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
AllowedContracts: nil,
|
||||
AllowedGroups: nil,
|
||||
}}
|
||||
|
||||
// use output of issue tx from genesis block as an input
|
||||
genesisBlock, err := bc.GetBlock(bc.GetHeaderHash(0))
|
||||
|
@ -181,15 +194,10 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
PrevHash: h,
|
||||
PrevIndex: 0,
|
||||
})
|
||||
|
||||
txMoveNeo.Sender = neoOwner
|
||||
|
||||
priv0 := testchain.PrivateKeyByID(0)
|
||||
priv0ScriptHash := priv0.GetScriptHash()
|
||||
txMoveNeo.AddOutput(&transaction.Output{
|
||||
AssetID: GoverningTokenID(),
|
||||
Amount: neoAmount,
|
||||
ScriptHash: priv0.GetScriptHash(),
|
||||
ScriptHash: priv0ScriptHash,
|
||||
Position: 0,
|
||||
})
|
||||
txMoveNeo.AddOutput(&transaction.Output{
|
||||
|
@ -198,7 +206,6 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
ScriptHash: neoOwner,
|
||||
Position: 1,
|
||||
})
|
||||
txMoveNeo.Data = new(transaction.ContractTX)
|
||||
require.NoError(t, signTx(bc, txMoveNeo))
|
||||
b := bc.newBlock(txMoveNeo)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
|
@ -233,6 +240,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
Position: 0,
|
||||
})
|
||||
txNeoRound.Data = new(transaction.ContractTX)
|
||||
require.NoError(t, addNetworkFee(bc, txNeoRound, acc0))
|
||||
require.NoError(t, acc0.SignTx(txNeoRound))
|
||||
b = bc.newBlock(txNeoRound)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
|
@ -257,6 +265,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
ScriptHash: priv0.GetScriptHash(),
|
||||
Position: 0,
|
||||
})
|
||||
require.NoError(t, addNetworkFee(bc, txClaim, acc0))
|
||||
require.NoError(t, acc0.SignTx(txClaim))
|
||||
b = bc.newBlock(txClaim)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
|
@ -300,6 +309,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
Position: 0,
|
||||
})
|
||||
gasOwned -= invFee
|
||||
require.NoError(t, addNetworkFee(bc, txDeploy, acc0))
|
||||
require.NoError(t, acc0.SignTx(txDeploy))
|
||||
b = bc.newBlock(txDeploy)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
|
@ -313,6 +323,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
txInv.Nonce = getNextNonce()
|
||||
txInv.ValidUntilBlock = validUntilBlock
|
||||
txInv.Sender = priv0ScriptHash
|
||||
require.NoError(t, addNetworkFee(bc, txInv, acc0))
|
||||
require.NoError(t, acc0.SignTx(txInv))
|
||||
b = bc.newBlock(txInv)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
|
@ -339,6 +350,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
ScriptHash: priv0.GetScriptHash(),
|
||||
})
|
||||
|
||||
require.NoError(t, addNetworkFee(bc, txNeo0to1, acc0))
|
||||
require.NoError(t, acc0.SignTx(txNeo0to1))
|
||||
b = bc.newBlock(txNeo0to1)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
|
@ -350,24 +362,45 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
initTx.Nonce = getNextNonce()
|
||||
initTx.ValidUntilBlock = validUntilBlock
|
||||
initTx.Sender = priv0ScriptHash
|
||||
require.NoError(t, addNetworkFee(bc, initTx, acc0))
|
||||
require.NoError(t, acc0.SignTx(initTx))
|
||||
transferTx := newNEP5Transfer(sh, sh, priv0.GetScriptHash(), 1000)
|
||||
transferTx.Nonce = getNextNonce()
|
||||
transferTx.ValidUntilBlock = validUntilBlock
|
||||
transferTx.Sender = priv0ScriptHash
|
||||
transferTx.Cosigners = []transaction.Cosigner{
|
||||
{
|
||||
Account: priv0ScriptHash,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
AllowedContracts: nil,
|
||||
AllowedGroups: nil,
|
||||
},
|
||||
}
|
||||
require.NoError(t, addNetworkFee(bc, transferTx, acc0))
|
||||
require.NoError(t, acc0.SignTx(transferTx))
|
||||
|
||||
b = bc.newBlock(initTx, transferTx)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
t.Logf("recieveRublesTx: %v", transferTx.Hash().StringBE())
|
||||
|
||||
transferTx = newNEP5Transfer(sh, priv0.GetScriptHash(), priv1.GetScriptHash(), 123)
|
||||
transferTx.Nonce = getNextNonce()
|
||||
transferTx.ValidUntilBlock = validUntilBlock
|
||||
transferTx.Sender = priv0ScriptHash
|
||||
transferTx.Cosigners = []transaction.Cosigner{
|
||||
{
|
||||
Account: priv0ScriptHash,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
AllowedContracts: nil,
|
||||
AllowedGroups: nil,
|
||||
},
|
||||
}
|
||||
require.NoError(t, addNetworkFee(bc, transferTx, acc0))
|
||||
require.NoError(t, acc0.SignTx(transferTx))
|
||||
|
||||
b = bc.newBlock(transferTx)
|
||||
require.NoError(t, bc.AddBlock(b))
|
||||
t.Logf("sendRublesTx: %v", transferTx.Hash().StringBE())
|
||||
|
||||
if saveChain {
|
||||
outStream, err := os.Create(prefix + "testblocks.acc")
|
||||
|
@ -407,6 +440,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
Position: 0,
|
||||
})
|
||||
txNeoRound.Data = new(transaction.ContractTX)
|
||||
require.NoError(t, addNetworkFee(bc, txNeoRound, acc0))
|
||||
require.NoError(t, acc0.SignTx(txNeoRound))
|
||||
bw := io.NewBufBinWriter()
|
||||
txNeoRound.EncodeBinary(bw.BinWriter)
|
||||
|
@ -439,6 +473,11 @@ func signTx(bc *Blockchain, txs ...*transaction.Transaction) error {
|
|||
return errors.Wrap(err, "fail to sign tx")
|
||||
}
|
||||
for _, tx := range txs {
|
||||
size := io.GetVarSize(tx)
|
||||
netFee, sizeDelta := CalculateNetworkFee(rawScript)
|
||||
tx.NetworkFee = tx.NetworkFee.Add(netFee)
|
||||
size += sizeDelta
|
||||
tx.NetworkFee = tx.NetworkFee.Add(util.Fixed8(int64(size) * int64(bc.FeePerByte())))
|
||||
data := tx.GetSignedPart()
|
||||
tx.Scripts = []transaction.Witness{{
|
||||
InvocationScript: testchain.Sign(data),
|
||||
|
@ -447,3 +486,20 @@ func signTx(bc *Blockchain, txs ...*transaction.Transaction) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addNetworkFee(bc *Blockchain, tx *transaction.Transaction, sender *wallet.Account) error {
|
||||
size := io.GetVarSize(tx)
|
||||
netFee, sizeDelta := CalculateNetworkFee(sender.Contract.Script)
|
||||
tx.NetworkFee += netFee
|
||||
size += sizeDelta
|
||||
for _, cosigner := range tx.Cosigners {
|
||||
contract := bc.GetContractState(cosigner.Account)
|
||||
if contract != nil {
|
||||
netFee, sizeDelta = CalculateNetworkFee(contract.Script)
|
||||
tx.NetworkFee += netFee
|
||||
size += sizeDelta
|
||||
}
|
||||
}
|
||||
tx.NetworkFee += util.Fixed8(int64(size) * int64(bc.FeePerByte()))
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package mempool
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// Feer is an interface that abstract the implementation of the fee calculation.
|
||||
type Feer interface {
|
||||
NetworkFee(t *transaction.Transaction) util.Fixed8
|
||||
IsLowPriority(util.Fixed8) bool
|
||||
FeePerByte(t *transaction.Transaction) util.Fixed8
|
||||
SystemFee(t *transaction.Transaction) util.Fixed8
|
||||
FeePerByte() util.Fixed8
|
||||
GetUtilityTokenBalance(util.Uint160) util.Fixed8
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ var (
|
|||
type item struct {
|
||||
txn *transaction.Transaction
|
||||
timeStamp time.Time
|
||||
perByteFee util.Fixed8
|
||||
netFee util.Fixed8
|
||||
isLowPrio bool
|
||||
}
|
||||
|
||||
|
@ -41,6 +39,13 @@ type TxWithFee struct {
|
|||
Fee util.Fixed8
|
||||
}
|
||||
|
||||
// utilityBalanceAndFees stores sender's balance and overall fees of
|
||||
// sender's transactions which are currently in mempool
|
||||
type utilityBalanceAndFees struct {
|
||||
balance util.Fixed8
|
||||
feeSum util.Fixed8
|
||||
}
|
||||
|
||||
// Pool stores the unconfirms transactions.
|
||||
type Pool struct {
|
||||
lock sync.RWMutex
|
||||
|
@ -48,6 +53,7 @@ type Pool struct {
|
|||
verifiedTxes items
|
||||
inputs []*transaction.Input
|
||||
claims []*transaction.Input
|
||||
fees map[util.Uint160]utilityBalanceAndFees
|
||||
|
||||
capacity int
|
||||
}
|
||||
|
@ -88,11 +94,11 @@ func (p *item) CompareTo(otherP *item) int {
|
|||
}
|
||||
|
||||
// Fees sorted ascending.
|
||||
if ret := p.perByteFee.CompareTo(otherP.perByteFee); ret != 0 {
|
||||
if ret := p.txn.FeePerByte().CompareTo(otherP.txn.FeePerByte()); ret != 0 {
|
||||
return ret
|
||||
}
|
||||
|
||||
if ret := p.netFee.CompareTo(otherP.netFee); ret != 0 {
|
||||
if ret := p.txn.NetworkFee.CompareTo(otherP.txn.NetworkFee); ret != 0 {
|
||||
return ret
|
||||
}
|
||||
|
||||
|
@ -158,17 +164,47 @@ func dropInputFromSortedSlice(slice *[]*transaction.Input, input *transaction.In
|
|||
*slice = (*slice)[:len(*slice)-1]
|
||||
}
|
||||
|
||||
// tryAddSendersFee tries to add system fee and network fee to the total sender`s fee in mempool
|
||||
// and returns false if sender has not enough GAS to pay
|
||||
func (mp *Pool) tryAddSendersFee(tx *transaction.Transaction, feer Feer) bool {
|
||||
if !mp.checkBalanceAndUpdate(tx, feer) {
|
||||
return false
|
||||
}
|
||||
mp.addSendersFee(tx)
|
||||
return true
|
||||
}
|
||||
|
||||
// checkBalanceAndUpdate returns true in case when sender has enough GAS to pay for
|
||||
// the transaction and sets sender's balance value in mempool in case if it was not set
|
||||
func (mp *Pool) checkBalanceAndUpdate(tx *transaction.Transaction, feer Feer) bool {
|
||||
senderFee, ok := mp.fees[tx.Sender]
|
||||
if !ok {
|
||||
senderFee.balance = feer.GetUtilityTokenBalance(tx.Sender)
|
||||
mp.fees[tx.Sender] = senderFee
|
||||
}
|
||||
needFee := senderFee.feeSum + tx.SystemFee + tx.NetworkFee
|
||||
if senderFee.balance < needFee {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// addSendersFee adds system fee and network fee to the total sender`s fee in mempool
|
||||
func (mp *Pool) addSendersFee(tx *transaction.Transaction) {
|
||||
senderFee := mp.fees[tx.Sender]
|
||||
senderFee.feeSum += tx.SystemFee + tx.NetworkFee
|
||||
mp.fees[tx.Sender] = senderFee
|
||||
}
|
||||
|
||||
// Add tries to add given transaction to the Pool.
|
||||
func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
|
||||
var pItem = &item{
|
||||
txn: t,
|
||||
timeStamp: time.Now().UTC(),
|
||||
perByteFee: fee.FeePerByte(t),
|
||||
netFee: fee.NetworkFee(t),
|
||||
}
|
||||
pItem.isLowPrio = fee.IsLowPriority(pItem.netFee)
|
||||
pItem.isLowPrio = fee.IsLowPriority(pItem.txn.NetworkFee)
|
||||
mp.lock.Lock()
|
||||
if !mp.checkTxConflicts(t) {
|
||||
if !mp.checkTxConflicts(t, fee) {
|
||||
mp.lock.Unlock()
|
||||
return ErrConflict
|
||||
}
|
||||
|
@ -206,6 +242,7 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
|
|||
copy(mp.verifiedTxes[n+1:], mp.verifiedTxes[n:])
|
||||
mp.verifiedTxes[n] = pItem
|
||||
}
|
||||
mp.addSendersFee(pItem.txn)
|
||||
|
||||
// For lots of inputs it might be easier to push them all and sort
|
||||
// afterwards, but that requires benchmarking.
|
||||
|
@ -241,6 +278,9 @@ func (mp *Pool) Remove(hash util.Uint256) {
|
|||
} else if num == len(mp.verifiedTxes)-1 {
|
||||
mp.verifiedTxes = mp.verifiedTxes[:num]
|
||||
}
|
||||
senderFee := mp.fees[it.txn.Sender]
|
||||
senderFee.feeSum -= it.txn.SystemFee + it.txn.NetworkFee
|
||||
mp.fees[it.txn.Sender] = senderFee
|
||||
for i := range it.txn.Inputs {
|
||||
dropInputFromSortedSlice(&mp.inputs, &it.txn.Inputs[i])
|
||||
}
|
||||
|
@ -258,15 +298,16 @@ func (mp *Pool) Remove(hash util.Uint256) {
|
|||
// RemoveStale filters verified transactions through the given function keeping
|
||||
// only the transactions for which it returns a true result. It's used to quickly
|
||||
// drop part of the mempool that is now invalid after the block acceptance.
|
||||
func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool) {
|
||||
func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer) {
|
||||
mp.lock.Lock()
|
||||
// We can reuse already allocated slice
|
||||
// because items are iterated one-by-one in increasing order.
|
||||
newVerifiedTxes := mp.verifiedTxes[:0]
|
||||
newInputs := mp.inputs[:0]
|
||||
newClaims := mp.claims[:0]
|
||||
mp.fees = make(map[util.Uint160]utilityBalanceAndFees) // it'd be nice to reuse existing map, but we can't easily clear it
|
||||
for _, itm := range mp.verifiedTxes {
|
||||
if isOK(itm.txn) {
|
||||
if isOK(itm.txn) && mp.tryAddSendersFee(itm.txn, feer) {
|
||||
newVerifiedTxes = append(newVerifiedTxes, itm)
|
||||
for i := range itm.txn.Inputs {
|
||||
newInputs = append(newInputs, &itm.txn.Inputs[i])
|
||||
|
@ -299,6 +340,7 @@ func NewMemPool(capacity int) Pool {
|
|||
verifiedMap: make(map[util.Uint256]*item),
|
||||
verifiedTxes: make([]*item, 0, capacity),
|
||||
capacity: capacity,
|
||||
fees: make(map[util.Uint160]utilityBalanceAndFees),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -307,7 +349,7 @@ func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, util.F
|
|||
mp.lock.RLock()
|
||||
defer mp.lock.RUnlock()
|
||||
if pItem, ok := mp.verifiedMap[hash]; ok {
|
||||
return pItem.txn, pItem.netFee, ok
|
||||
return pItem.txn, pItem.txn.NetworkFee, ok
|
||||
}
|
||||
|
||||
return nil, 0, false
|
||||
|
@ -323,7 +365,7 @@ func (mp *Pool) GetVerifiedTransactions() []TxWithFee {
|
|||
|
||||
for i := range mp.verifiedTxes {
|
||||
t[i].Tx = mp.verifiedTxes[i].txn
|
||||
t[i].Fee = mp.verifiedTxes[i].netFee
|
||||
t[i].Fee = mp.verifiedTxes[i].txn.NetworkFee
|
||||
}
|
||||
|
||||
return t
|
||||
|
@ -342,10 +384,13 @@ func areInputsInPool(inputs []transaction.Input, pool []*transaction.Input) bool
|
|||
}
|
||||
|
||||
// checkTxConflicts is an internal unprotected version of Verify.
|
||||
func (mp *Pool) checkTxConflicts(tx *transaction.Transaction) bool {
|
||||
func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) bool {
|
||||
if areInputsInPool(tx.Inputs, mp.inputs) {
|
||||
return false
|
||||
}
|
||||
if !mp.checkBalanceAndUpdate(tx, fee) {
|
||||
return false
|
||||
}
|
||||
switch tx.Type {
|
||||
case transaction.ClaimType:
|
||||
claim := tx.Data.(*transaction.ClaimTX)
|
||||
|
@ -368,8 +413,8 @@ func (mp *Pool) checkTxConflicts(tx *transaction.Transaction) bool {
|
|||
// Verify verifies if the inputs of a transaction tx are already used in any other transaction in the memory pool.
|
||||
// If yes, the transaction tx is not a valid transaction and the function return false.
|
||||
// If no, the transaction tx is a valid transaction and the function return true.
|
||||
func (mp *Pool) Verify(tx *transaction.Transaction) bool {
|
||||
func (mp *Pool) Verify(tx *transaction.Transaction, feer Feer) bool {
|
||||
mp.lock.RLock()
|
||||
defer mp.lock.RUnlock()
|
||||
return mp.checkTxConflicts(tx)
|
||||
return mp.checkTxConflicts(tx, feer)
|
||||
}
|
||||
|
|
|
@ -13,25 +13,19 @@ import (
|
|||
|
||||
type FeerStub struct {
|
||||
lowPriority bool
|
||||
sysFee util.Fixed8
|
||||
netFee util.Fixed8
|
||||
perByteFee util.Fixed8
|
||||
}
|
||||
|
||||
func (fs *FeerStub) NetworkFee(*transaction.Transaction) util.Fixed8 {
|
||||
return fs.netFee
|
||||
feePerByte util.Fixed8
|
||||
}
|
||||
|
||||
func (fs *FeerStub) IsLowPriority(util.Fixed8) bool {
|
||||
return fs.lowPriority
|
||||
}
|
||||
|
||||
func (fs *FeerStub) FeePerByte(*transaction.Transaction) util.Fixed8 {
|
||||
return fs.perByteFee
|
||||
func (fs *FeerStub) FeePerByte() util.Fixed8 {
|
||||
return fs.feePerByte
|
||||
}
|
||||
|
||||
func (fs *FeerStub) SystemFee(*transaction.Transaction) util.Fixed8 {
|
||||
return fs.sysFee
|
||||
func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) util.Fixed8 {
|
||||
return util.Fixed8FromInt64(10000)
|
||||
}
|
||||
|
||||
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
|
||||
|
@ -121,7 +115,7 @@ func TestMemPoolAddRemoveWithInputsAndClaims(t *testing.T) {
|
|||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}, &FeerStub{})
|
||||
assert.Equal(t, len(txm1.Inputs), len(mp.inputs))
|
||||
assert.True(t, sort.SliceIsSorted(mp.inputs, mpLessInputs))
|
||||
assert.Equal(t, len(claim2.Claims), len(mp.claims))
|
||||
|
@ -134,24 +128,24 @@ func TestMemPoolVerifyInputs(t *testing.T) {
|
|||
tx.Nonce = 1
|
||||
inhash1 := random.Uint256()
|
||||
tx.Inputs = append(tx.Inputs, transaction.Input{PrevHash: inhash1, PrevIndex: 0})
|
||||
require.Equal(t, true, mp.Verify(tx))
|
||||
require.Equal(t, true, mp.Verify(tx, &FeerStub{}))
|
||||
require.NoError(t, mp.Add(tx, &FeerStub{}))
|
||||
|
||||
tx2 := transaction.NewContractTX()
|
||||
tx2.Nonce = 2
|
||||
inhash2 := random.Uint256()
|
||||
tx2.Inputs = append(tx2.Inputs, transaction.Input{PrevHash: inhash2, PrevIndex: 0})
|
||||
require.Equal(t, true, mp.Verify(tx2))
|
||||
require.Equal(t, true, mp.Verify(tx2, &FeerStub{}))
|
||||
require.NoError(t, mp.Add(tx2, &FeerStub{}))
|
||||
|
||||
tx3 := transaction.NewContractTX()
|
||||
tx3.Nonce = 3
|
||||
// Different index number, but the same PrevHash as in tx1.
|
||||
tx3.Inputs = append(tx3.Inputs, transaction.Input{PrevHash: inhash1, PrevIndex: 1})
|
||||
require.Equal(t, true, mp.Verify(tx3))
|
||||
require.Equal(t, true, mp.Verify(tx3, &FeerStub{}))
|
||||
// The same input as in tx2.
|
||||
tx3.Inputs = append(tx3.Inputs, transaction.Input{PrevHash: inhash2, PrevIndex: 0})
|
||||
require.Equal(t, false, mp.Verify(tx3))
|
||||
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))
|
||||
require.Error(t, mp.Add(tx3, &FeerStub{}))
|
||||
}
|
||||
|
||||
|
@ -166,30 +160,30 @@ func TestMemPoolVerifyClaims(t *testing.T) {
|
|||
claim1.Claims = append(claim1.Claims, transaction.Input{PrevHash: hash1, PrevIndex: uint16(i)})
|
||||
claim1.Claims = append(claim1.Claims, transaction.Input{PrevHash: hash2, PrevIndex: uint16(i)})
|
||||
}
|
||||
require.Equal(t, true, mp.Verify(tx1))
|
||||
require.Equal(t, true, mp.Verify(tx1, &FeerStub{}))
|
||||
require.NoError(t, mp.Add(tx1, &FeerStub{}))
|
||||
|
||||
tx2, claim2 := newClaimTX()
|
||||
for i := 0; i < 10; i++ {
|
||||
claim2.Claims = append(claim2.Claims, transaction.Input{PrevHash: hash2, PrevIndex: uint16(i + 10)})
|
||||
}
|
||||
require.Equal(t, true, mp.Verify(tx2))
|
||||
require.Equal(t, true, mp.Verify(tx2, &FeerStub{}))
|
||||
require.NoError(t, mp.Add(tx2, &FeerStub{}))
|
||||
|
||||
tx3, claim3 := newClaimTX()
|
||||
claim3.Claims = append(claim3.Claims, transaction.Input{PrevHash: hash1, PrevIndex: 0})
|
||||
require.Equal(t, false, mp.Verify(tx3))
|
||||
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))
|
||||
require.Error(t, mp.Add(tx3, &FeerStub{}))
|
||||
}
|
||||
|
||||
func TestMemPoolVerifyIssue(t *testing.T) {
|
||||
mp := NewMemPool(50)
|
||||
tx1 := newIssueTX()
|
||||
require.Equal(t, true, mp.Verify(tx1))
|
||||
require.Equal(t, true, mp.Verify(tx1, &FeerStub{}))
|
||||
require.NoError(t, mp.Add(tx1, &FeerStub{}))
|
||||
|
||||
tx2 := newIssueTX()
|
||||
require.Equal(t, false, mp.Verify(tx2))
|
||||
require.Equal(t, false, mp.Verify(tx2, &FeerStub{}))
|
||||
require.Error(t, mp.Add(tx2, &FeerStub{}))
|
||||
}
|
||||
|
||||
|
@ -235,18 +229,27 @@ func TestOverCapacity(t *testing.T) {
|
|||
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
|
||||
|
||||
// Fees are also prioritized.
|
||||
fs.netFee = util.Fixed8FromFloat(0.0001)
|
||||
for i := 0; i < mempoolSize-1; i++ {
|
||||
tx := transaction.NewContractTX()
|
||||
tx.Attributes = append(tx.Attributes, transaction.Attribute{
|
||||
Usage: transaction.Hash1,
|
||||
Data: util.Uint256{1, 2, 3, 4}.BytesBE(),
|
||||
})
|
||||
tx.NetworkFee = util.Fixed8FromFloat(0.0001)
|
||||
tx.Nonce = txcnt
|
||||
txcnt++
|
||||
// size is 84, networkFee is 0.0001 => feePerByte is 0.00000119
|
||||
require.NoError(t, mp.Add(tx, fs))
|
||||
require.Equal(t, mempoolSize, mp.Count())
|
||||
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
|
||||
}
|
||||
// Less prioritized txes are not allowed anymore.
|
||||
fs.netFee = util.Fixed8FromFloat(0.00001)
|
||||
tx := transaction.NewContractTX()
|
||||
tx.Attributes = append(tx.Attributes, transaction.Attribute{
|
||||
Usage: transaction.Hash1,
|
||||
Data: util.Uint256{1, 2, 3, 4}.BytesBE(),
|
||||
})
|
||||
tx.NetworkFee = util.Fixed8FromFloat(0.00001)
|
||||
tx.Nonce = txcnt
|
||||
txcnt++
|
||||
require.Error(t, mp.Add(tx, fs))
|
||||
|
@ -257,10 +260,12 @@ func TestOverCapacity(t *testing.T) {
|
|||
require.True(t, mp.ContainsKey(claim.Hash()))
|
||||
|
||||
// Low net fee, but higher per-byte fee is still a better combination.
|
||||
fs.perByteFee = util.Fixed8FromFloat(0.001)
|
||||
tx = transaction.NewContractTX()
|
||||
tx.Nonce = txcnt
|
||||
tx.NetworkFee = util.Fixed8FromFloat(0.00007)
|
||||
txcnt++
|
||||
// size is 51 (no attributes), networkFee is 0.00007 (<0.0001)
|
||||
// => feePerByte is 0.00000137 (>0.00000119)
|
||||
require.NoError(t, mp.Add(tx, fs))
|
||||
require.Equal(t, mempoolSize, mp.Count())
|
||||
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
|
||||
|
@ -334,7 +339,7 @@ func TestRemoveStale(t *testing.T) {
|
|||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}, &FeerStub{})
|
||||
require.Equal(t, mempoolSize/2, mp.Count())
|
||||
verTxes := mp.GetVerifiedTransactions()
|
||||
for _, txf := range verTxes {
|
||||
|
@ -342,3 +347,76 @@ func TestRemoveStale(t *testing.T) {
|
|||
require.Contains(t, txes2, txf.Tx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemPoolFees(t *testing.T) {
|
||||
mp := NewMemPool(10)
|
||||
sender0 := util.Uint160{1, 2, 3}
|
||||
tx0 := transaction.NewContractTX()
|
||||
tx0.NetworkFee = util.Fixed8FromInt64(11000)
|
||||
tx0.Sender = sender0
|
||||
// insufficient funds to add transaction, but balance should be stored
|
||||
require.Equal(t, false, mp.Verify(tx0, &FeerStub{}))
|
||||
require.Error(t, mp.Add(tx0, &FeerStub{}))
|
||||
require.Equal(t, 1, len(mp.fees))
|
||||
require.Equal(t, utilityBalanceAndFees{
|
||||
balance: util.Fixed8FromInt64(10000),
|
||||
feeSum: 0,
|
||||
}, mp.fees[sender0])
|
||||
|
||||
// no problems with adding another transaction with lower fee
|
||||
tx1 := transaction.NewContractTX()
|
||||
tx1.NetworkFee = util.Fixed8FromInt64(7000)
|
||||
tx1.Sender = sender0
|
||||
require.NoError(t, mp.Add(tx1, &FeerStub{}))
|
||||
require.Equal(t, 1, len(mp.fees))
|
||||
require.Equal(t, utilityBalanceAndFees{
|
||||
balance: util.Fixed8FromInt64(10000),
|
||||
feeSum: util.Fixed8FromInt64(7000),
|
||||
}, mp.fees[sender0])
|
||||
|
||||
// balance shouldn't change after adding one more transaction
|
||||
tx2 := transaction.NewContractTX()
|
||||
tx2.NetworkFee = util.Fixed8FromFloat(3000)
|
||||
tx2.Sender = sender0
|
||||
require.NoError(t, mp.Add(tx2, &FeerStub{}))
|
||||
require.Equal(t, 2, len(mp.verifiedTxes))
|
||||
require.Equal(t, 1, len(mp.fees))
|
||||
require.Equal(t, utilityBalanceAndFees{
|
||||
balance: util.Fixed8FromInt64(10000),
|
||||
feeSum: util.Fixed8FromInt64(10000),
|
||||
}, mp.fees[sender0])
|
||||
|
||||
// can't add more transactions as we don't have enough GAS
|
||||
tx3 := transaction.NewContractTX()
|
||||
tx3.NetworkFee = util.Fixed8FromFloat(0.5)
|
||||
tx3.Sender = sender0
|
||||
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))
|
||||
require.Error(t, mp.Add(tx3, &FeerStub{}))
|
||||
require.Equal(t, 1, len(mp.fees))
|
||||
require.Equal(t, utilityBalanceAndFees{
|
||||
balance: util.Fixed8FromInt64(10000),
|
||||
feeSum: util.Fixed8FromInt64(10000),
|
||||
}, mp.fees[sender0])
|
||||
|
||||
// check whether sender's fee updates correctly
|
||||
mp.RemoveStale(func(t *transaction.Transaction) bool {
|
||||
if t == tx2 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, &FeerStub{})
|
||||
require.Equal(t, 1, len(mp.fees))
|
||||
require.Equal(t, utilityBalanceAndFees{
|
||||
balance: util.Fixed8FromInt64(10000),
|
||||
feeSum: util.Fixed8FromFloat(3000),
|
||||
}, mp.fees[sender0])
|
||||
|
||||
// there should be nothing left
|
||||
mp.RemoveStale(func(t *transaction.Transaction) bool {
|
||||
if t == tx3 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, &FeerStub{})
|
||||
require.Equal(t, 0, len(mp.fees))
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package native
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
|
@ -28,21 +29,22 @@ const initialGAS = 30000000
|
|||
|
||||
// NewGAS returns GAS native contract.
|
||||
func NewGAS() *GAS {
|
||||
g := &GAS{}
|
||||
nep5 := newNEP5Native(gasSyscallName)
|
||||
nep5.name = "GAS"
|
||||
nep5.symbol = "gas"
|
||||
nep5.decimals = 8
|
||||
nep5.factor = GASFactor
|
||||
nep5.onPersist = chainOnPersist(g.onPersist, g.OnPersist)
|
||||
nep5.incBalance = g.increaseBalance
|
||||
|
||||
g := &GAS{nep5TokenNative: *nep5}
|
||||
g.nep5TokenNative = *nep5
|
||||
|
||||
desc := newDescriptor("getSysFeeAmount", smartcontract.IntegerType,
|
||||
manifest.NewParameter("index", smartcontract.IntegerType))
|
||||
md := newMethodAndPrice(g.getSysFeeAmount, 1, smartcontract.NoneFlag)
|
||||
g.AddMethod(md, desc, true)
|
||||
|
||||
g.onPersist = chainOnPersist(g.onPersist, g.OnPersist)
|
||||
g.incBalance = g.increaseBalance
|
||||
return g
|
||||
}
|
||||
|
||||
|
@ -79,15 +81,20 @@ func (g *GAS) Initialize(ic *interop.Context) error {
|
|||
|
||||
// OnPersist implements Contract interface.
|
||||
func (g *GAS) OnPersist(ic *interop.Context) error {
|
||||
//for _ ,tx := range ic.block.Transactions {
|
||||
// g.burn(ic, tx.Sender, tx.SystemFee + tx.NetworkFee)
|
||||
//}
|
||||
//validators := g.NEO.getNextBlockValidators(ic)
|
||||
//var netFee util.Fixed8
|
||||
//for _, tx := range ic.block.Transactions {
|
||||
// netFee += tx.NetworkFee
|
||||
//}
|
||||
//g.mint(ic, <primary>, netFee)
|
||||
for _, tx := range ic.Block.Transactions {
|
||||
absAmount := big.NewInt(int64(tx.SystemFee + tx.NetworkFee))
|
||||
g.burn(ic, tx.Sender, absAmount)
|
||||
}
|
||||
validators, err := g.NEO.GetValidatorsInternal(ic.Chain, ic.DAO)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get block validators: %v", err)
|
||||
}
|
||||
primary := validators[ic.Block.ConsensusData.PrimaryIndex].GetScriptHash()
|
||||
var netFee util.Fixed8
|
||||
for _, tx := range ic.Block.Transactions {
|
||||
netFee += tx.NetworkFee
|
||||
}
|
||||
g.mint(ic, primary, big.NewInt(int64(netFee)))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -60,13 +60,16 @@ func makeValidatorKey(key *keys.PublicKey) []byte {
|
|||
|
||||
// NewNEO returns NEO native contract.
|
||||
func NewNEO() *NEO {
|
||||
n := &NEO{}
|
||||
nep5 := newNEP5Native(neoSyscallName)
|
||||
nep5.name = "NEO"
|
||||
nep5.symbol = "neo"
|
||||
nep5.decimals = 0
|
||||
nep5.factor = 1
|
||||
nep5.onPersist = chainOnPersist(n.onPersist, n.OnPersist)
|
||||
nep5.incBalance = n.increaseBalance
|
||||
|
||||
n := &NEO{nep5TokenNative: *nep5}
|
||||
n.nep5TokenNative = *nep5
|
||||
|
||||
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
||||
manifest.NewParameter("account", smartcontract.Hash160Type),
|
||||
|
@ -97,9 +100,6 @@ func NewNEO() *NEO {
|
|||
md = newMethodAndPrice(n.getNextBlockValidators, 1, smartcontract.NoneFlag)
|
||||
n.AddMethod(md, desc, true)
|
||||
|
||||
n.onPersist = chainOnPersist(n.onPersist, n.OnPersist)
|
||||
n.incBalance = n.increaseBalance
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
|
@ -251,7 +251,11 @@ func (n *NEO) vote(ic *interop.Context, args []vm.StackItem) vm.StackItem {
|
|||
|
||||
// VoteInternal votes from account h for validarors specified in pubs.
|
||||
func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.PublicKeys) error {
|
||||
ok, err := runtime.CheckHashedWitness(ic, neoScriptHash{hash: n.Hash}, h)
|
||||
ok, err := runtime.CheckHashedWitness(ic, nep5ScriptHash{
|
||||
callingScriptHash: util.Uint160{},
|
||||
entryScriptHash: n.Hash,
|
||||
currentScriptHash: n.Hash,
|
||||
}, h)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
|
@ -479,24 +483,3 @@ func toPublicKey(s vm.StackItem) *keys.PublicKey {
|
|||
}
|
||||
return pub
|
||||
}
|
||||
|
||||
// scriptHash is an auxiliary structure which implements ScriptHashGetter
|
||||
// interface over NEO native contract and is used for runtime.CheckHashedWitness
|
||||
type neoScriptHash struct {
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// GetCallingScriptHash implements ScriptHashGetter interface
|
||||
func (s neoScriptHash) GetCallingScriptHash() util.Uint160 {
|
||||
return util.Uint160{}
|
||||
}
|
||||
|
||||
// GetEntryScriptHash implements ScriptHashGetter interface
|
||||
func (s neoScriptHash) GetEntryScriptHash() util.Uint160 {
|
||||
return s.hash
|
||||
}
|
||||
|
||||
// GetCurrentScriptHash implements ScriptHashGetter interface
|
||||
func (s neoScriptHash) GetCurrentScriptHash() util.Uint160 {
|
||||
return s.hash
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
|
@ -147,6 +148,17 @@ func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, a
|
|||
return errors.New("negative amount")
|
||||
}
|
||||
|
||||
ok, err := runtime.CheckHashedWitness(ic, nep5ScriptHash{
|
||||
callingScriptHash: c.Hash,
|
||||
entryScriptHash: c.Hash,
|
||||
currentScriptHash: c.Hash,
|
||||
}, from)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return errors.New("invalid signature")
|
||||
}
|
||||
|
||||
keyFrom := makeAccountKey(from)
|
||||
siFrom := ic.DAO.GetStorageItem(c.Hash, keyFrom)
|
||||
if siFrom == nil {
|
||||
|
@ -205,9 +217,7 @@ func (c *nep5TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.
|
|||
}
|
||||
|
||||
func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) {
|
||||
if sign := amount.Sign(); sign == -1 {
|
||||
panic("negative amount")
|
||||
} else if sign == 0 {
|
||||
if amount.Sign() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -270,3 +280,26 @@ func toUint160(s vm.StackItem) util.Uint160 {
|
|||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// scriptHash is an auxiliary structure which implements ScriptHashGetter
|
||||
// interface over NEP5 native contract and is used for runtime.CheckHashedWitness
|
||||
type nep5ScriptHash struct {
|
||||
callingScriptHash util.Uint160
|
||||
entryScriptHash util.Uint160
|
||||
currentScriptHash util.Uint160
|
||||
}
|
||||
|
||||
// GetCallingScriptHash implements ScriptHashGetter interface
|
||||
func (s nep5ScriptHash) GetCallingScriptHash() util.Uint160 {
|
||||
return s.callingScriptHash
|
||||
}
|
||||
|
||||
// GetEntryScriptHash implements ScriptHashGetter interface
|
||||
func (s nep5ScriptHash) GetEntryScriptHash() util.Uint160 {
|
||||
return s.entryScriptHash
|
||||
}
|
||||
|
||||
// GetCurrentScriptHash implements ScriptHashGetter interface
|
||||
func (s nep5ScriptHash) GetCurrentScriptHash() util.Uint160 {
|
||||
return s.currentScriptHash
|
||||
}
|
||||
|
|
202
pkg/core/opcode_price.go
Normal file
202
pkg/core/opcode_price.go
Normal 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,
|
||||
}
|
|
@ -38,6 +38,12 @@ type Transaction struct {
|
|||
// Address signed the transaction.
|
||||
Sender util.Uint160
|
||||
|
||||
// Fee to be burned.
|
||||
SystemFee util.Fixed8
|
||||
|
||||
// Fee to be distributed to consensus nodes.
|
||||
NetworkFee util.Fixed8
|
||||
|
||||
// Maximum blockchain height exceeding which
|
||||
// transaction should fail verification.
|
||||
ValidUntilBlock uint32
|
||||
|
@ -119,7 +125,20 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) {
|
|||
t.Version = uint8(br.ReadB())
|
||||
t.Nonce = br.ReadU32LE()
|
||||
t.Sender.DecodeBinary(br)
|
||||
|
||||
t.SystemFee.DecodeBinary(br)
|
||||
if t.SystemFee < 0 {
|
||||
br.Err = errors.New("negative system fee")
|
||||
return
|
||||
}
|
||||
t.NetworkFee.DecodeBinary(br)
|
||||
if t.NetworkFee < 0 {
|
||||
br.Err = errors.New("negative network fee")
|
||||
return
|
||||
}
|
||||
if t.NetworkFee+t.SystemFee < t.SystemFee {
|
||||
br.Err = errors.New("too big fees: int 64 overflow")
|
||||
return
|
||||
}
|
||||
t.ValidUntilBlock = br.ReadU32LE()
|
||||
t.decodeData(br)
|
||||
|
||||
|
@ -192,6 +211,8 @@ func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
|
|||
bw.WriteB(byte(t.Version))
|
||||
bw.WriteU32LE(t.Nonce)
|
||||
t.Sender.EncodeBinary(bw)
|
||||
t.SystemFee.EncodeBinary(bw)
|
||||
t.NetworkFee.EncodeBinary(bw)
|
||||
bw.WriteU32LE(t.ValidUntilBlock)
|
||||
|
||||
// Underlying TXer.
|
||||
|
@ -268,6 +289,12 @@ func NewTransactionFromBytes(b []byte) (*Transaction, error) {
|
|||
return tx, nil
|
||||
}
|
||||
|
||||
// FeePerByte returns NetworkFee of the transaction divided by
|
||||
// its size
|
||||
func (t *Transaction) FeePerByte() util.Fixed8 {
|
||||
return util.Fixed8(int64(t.NetworkFee) / int64(io.GetVarSize(t)))
|
||||
}
|
||||
|
||||
// transactionJSON is a wrapper for Transaction and
|
||||
// used for correct marhalling of transaction.Data
|
||||
type transactionJSON struct {
|
||||
|
@ -277,6 +304,8 @@ type transactionJSON struct {
|
|||
Version uint8 `json:"version"`
|
||||
Nonce uint32 `json:"nonce"`
|
||||
Sender string `json:"sender"`
|
||||
SystemFee util.Fixed8 `json:"sys_fee"`
|
||||
NetworkFee util.Fixed8 `json:"net_fee"`
|
||||
ValidUntilBlock uint32 `json:"valid_until_block"`
|
||||
Attributes []Attribute `json:"attributes"`
|
||||
Cosigners []Cosigner `json:"cosigners"`
|
||||
|
@ -305,6 +334,8 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
|
|||
Inputs: t.Inputs,
|
||||
Outputs: t.Outputs,
|
||||
Scripts: t.Scripts,
|
||||
SystemFee: t.SystemFee,
|
||||
NetworkFee: t.NetworkFee,
|
||||
}
|
||||
switch t.Type {
|
||||
case ClaimType:
|
||||
|
@ -341,6 +372,8 @@ func (t *Transaction) UnmarshalJSON(data []byte) error {
|
|||
t.Inputs = tx.Inputs
|
||||
t.Outputs = tx.Outputs
|
||||
t.Scripts = tx.Scripts
|
||||
t.SystemFee = tx.SystemFee
|
||||
t.NetworkFee = tx.NetworkFee
|
||||
sender, err := address.StringToUint160(tx.Sender)
|
||||
if err != nil {
|
||||
return errors.New("cannot unmarshal tx: bad sender")
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
)
|
||||
|
@ -24,6 +25,10 @@ var (
|
|||
// utility (GAS) token. It's a part of the genesis block. It's mostly
|
||||
// useful for its hash that represents GAS asset ID.
|
||||
utilityTokenTX transaction.Transaction
|
||||
|
||||
// ecdsaVerifyInteropPrice returns the price of Neo.Crypto.ECDsaVerify
|
||||
// syscall to calculate NetworkFee for transaction
|
||||
ecdsaVerifyInteropPrice = util.Fixed8(100000)
|
||||
)
|
||||
|
||||
// createGenesisBlock creates a genesis block based on the given configuration.
|
||||
|
@ -191,3 +196,33 @@ func headerSliceReverse(dest []*block.Header) {
|
|||
dest[i], dest[j] = dest[j], dest[i]
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateNetworkFee returns network fee for transaction
|
||||
func CalculateNetworkFee(script []byte) (util.Fixed8, int) {
|
||||
var (
|
||||
netFee util.Fixed8
|
||||
size int
|
||||
)
|
||||
if vm.IsSignatureContract(script) {
|
||||
size += 67 + io.GetVarSize(script)
|
||||
netFee = netFee.Add(opcodePrice(opcode.PUSHDATA1, opcode.PUSHNULL).Add(ecdsaVerifyInteropPrice))
|
||||
} else if n, pubs, ok := vm.ParseMultiSigContract(script); ok {
|
||||
m := len(pubs)
|
||||
sizeInv := 66 * m
|
||||
size += io.GetVarSize(sizeInv) + sizeInv + io.GetVarSize(script)
|
||||
netFee = netFee.Add(calculateMultisigFee(m)).Add(calculateMultisigFee(n))
|
||||
netFee = netFee.Add(opcodePrice(opcode.PUSHNULL)).Add(util.Fixed8(int64(ecdsaVerifyInteropPrice) * int64(n)))
|
||||
} else {
|
||||
// We can support more contract types in the future.
|
||||
}
|
||||
return netFee, size
|
||||
}
|
||||
|
||||
func calculateMultisigFee(n int) util.Fixed8 {
|
||||
result := util.Fixed8(int64(opcodePrice(opcode.PUSHDATA1)) * int64(n))
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.Int(bw.BinWriter, int64(n))
|
||||
// it's a hack because prices of small PUSH* opcodes are equal
|
||||
result = result.Add(opcodePrice(opcode.Opcode(bw.Bytes()[0])))
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ func TestGenesisBlockMainNet(t *testing.T) {
|
|||
// have been changed. Consequently, hash of the genesis block has been changed.
|
||||
// Update expected genesis block hash for better times.
|
||||
// Old hash is "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf"
|
||||
expect := "56bb42c251ea2b216c5ee8306e94fe040613bc626a3813aa32fe07e7607b3a1a"
|
||||
expect := "1d4156d233220b893797a684fbb827bb2163b5042edd10653bbc1b2769adbb8d"
|
||||
assert.Equal(t, expect, block.Hash().StringLE())
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ func TestUtilityTokenTX(t *testing.T) {
|
|||
//TODO: After we added Nonce field to transaction.Transaction, UtilityTockenTx hash
|
||||
// has been changed. Update it for better times.
|
||||
// Old hash is "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
||||
expect := "8ef63ccd6f4ea20a93e7f4e84b2d43f778077612b241d617e42e1750cca4f2c5"
|
||||
expect := "f882fb865bab84b99623f21eedd902286af7da8d8a4609d7acefce04c851dc1c"
|
||||
assert.Equal(t, expect, UtilityTokenID().StringLE())
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,6 @@ func TestGoverningTokenTX(t *testing.T) {
|
|||
//TODO: After we added Nonce field to transaction.Transaction, GoveringTockenTx hash
|
||||
// has been changed. Update it for better times.
|
||||
// Old hash is "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
|
||||
expect := "7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc"
|
||||
expect := "1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78"
|
||||
assert.Equal(t, expect, GoverningTokenID().StringLE())
|
||||
}
|
||||
|
|
|
@ -38,15 +38,7 @@ func (chain testChain) References(t *transaction.Transaction) ([]transaction.InO
|
|||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) FeePerByte(t *transaction.Transaction) util.Fixed8 {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) SystemFee(t *transaction.Transaction) util.Fixed8 {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
|
||||
func (chain testChain) FeePerByte() util.Fixed8 {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
|
@ -145,6 +137,10 @@ func (chain testChain) IsLowPriority(util.Fixed8) bool {
|
|||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) GetUtilityTokenBalance(uint160 util.Uint160) util.Fixed8 {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) PoolTx(*transaction.Transaction) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
|
@ -107,8 +108,29 @@ func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token *walle
|
|||
emit.AppCallWithOperationAndArgs(w.BinWriter, token.Hash, "transfer", from, to, amount)
|
||||
emit.Opcode(w.BinWriter, opcode.ASSERT)
|
||||
|
||||
tx := transaction.NewInvocationTX(w.Bytes(), gas)
|
||||
script := w.Bytes()
|
||||
tx := transaction.NewInvocationTX(script, gas)
|
||||
tx.Sender = from
|
||||
tx.Cosigners = []transaction.Cosigner{
|
||||
{
|
||||
Account: from,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
AllowedContracts: nil,
|
||||
AllowedGroups: nil,
|
||||
},
|
||||
}
|
||||
|
||||
result, err := c.InvokeScript(hex.EncodeToString(script))
|
||||
if err != nil {
|
||||
return util.Uint256{}, fmt.Errorf("can't add system fee to transaction: %v", err)
|
||||
}
|
||||
gasConsumed, err := util.Fixed8FromString(result.GasConsumed)
|
||||
if err != nil {
|
||||
return util.Uint256{}, fmt.Errorf("can't add system fee to transaction: %v", err)
|
||||
}
|
||||
if gasConsumed > 0 {
|
||||
tx.SystemFee = gasConsumed
|
||||
}
|
||||
|
||||
tx.ValidUntilBlock, err = c.CalculateValidUntilBlock()
|
||||
if err != nil {
|
||||
|
@ -119,6 +141,11 @@ func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token *walle
|
|||
return util.Uint256{}, fmt.Errorf("can't add GAS to transaction: %v", err)
|
||||
}
|
||||
|
||||
err = c.AddNetworkFee(tx, acc)
|
||||
if err != nil {
|
||||
return util.Uint256{}, fmt.Errorf("can't add network fee to transaction: %v", err)
|
||||
}
|
||||
|
||||
if err := acc.SignTx(tx); err != nil {
|
||||
return util.Uint256{}, fmt.Errorf("can't sign tx: %v", err)
|
||||
}
|
||||
|
|
|
@ -511,6 +511,8 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys
|
|||
var err error
|
||||
|
||||
tx := transaction.NewInvocationTX(script, sysfee)
|
||||
tx.SystemFee = sysfee
|
||||
|
||||
validUntilBlock, err := c.CalculateValidUntilBlock()
|
||||
if err != nil {
|
||||
return txHash, errors.Wrap(err, "failed to add validUntilBlock to transaction")
|
||||
|
@ -531,6 +533,11 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys
|
|||
}
|
||||
}
|
||||
|
||||
err = c.AddNetworkFee(tx, acc)
|
||||
if err != nil {
|
||||
return txHash, errors.Wrapf(err, "failed to add network fee")
|
||||
}
|
||||
|
||||
if err = acc.SignTx(tx); err != nil {
|
||||
return txHash, errors.Wrap(err, "failed to sign tx")
|
||||
}
|
||||
|
@ -588,3 +595,33 @@ func (c *Client) CalculateValidUntilBlock() (uint32, error) {
|
|||
}
|
||||
return blockCount + validatorsCount, nil
|
||||
}
|
||||
|
||||
// AddNetworkFee adds network fee for each witness script to transaction.
|
||||
func (c *Client) AddNetworkFee(tx *transaction.Transaction, acc *wallet.Account) error {
|
||||
size := io.GetVarSize(tx)
|
||||
if acc.Contract != nil {
|
||||
netFee, sizeDelta := core.CalculateNetworkFee(acc.Contract.Script)
|
||||
tx.NetworkFee += netFee
|
||||
size += sizeDelta
|
||||
}
|
||||
for _, cosigner := range tx.Cosigners {
|
||||
contract, err := c.GetContractState(cosigner.Account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if contract == nil {
|
||||
continue
|
||||
}
|
||||
netFee, sizeDelta := core.CalculateNetworkFee(contract.Script)
|
||||
tx.NetworkFee += netFee
|
||||
size += sizeDelta
|
||||
}
|
||||
tx.NetworkFee += util.Fixed8(int64(size) * int64(c.GetFeePerByte()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFeePerByte returns transaction network fee per byte
|
||||
func (c *Client) GetFeePerByte() util.Fixed8 {
|
||||
// TODO: make it a part of policy contract
|
||||
return util.Fixed8(1000)
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.GetAccountState("")
|
||||
},
|
||||
serverResponse: `{"jsonrpc":"2.0","id": 1,"result":{"version":0,"script_hash":"0x1179716da2e9523d153a35fb3ad10c561b1e5b1a","frozen":false,"votes":[],"balances":[{"asset":"0x7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc","value":"94"}]}}`,
|
||||
serverResponse: `{"jsonrpc":"2.0","id": 1,"result":{"version":0,"script_hash":"0x1179716da2e9523d153a35fb3ad10c561b1e5b1a","frozen":false,"votes":[],"balances":[{"asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","value":"94"}]}}`,
|
||||
result: func(c *Client) interface{} {
|
||||
scriptHash, err := util.Uint160DecodeStringLE("1179716da2e9523d153a35fb3ad10c561b1e5b1a")
|
||||
if err != nil {
|
||||
|
@ -98,7 +98,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.GetAssetState(util.Uint256{})
|
||||
},
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":"0x7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc","type":0,"name":"NEO","amount":"100000000","available":"100000000","precision":0,"owner":"00","admin":"Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":4000000,"is_frozen":false}}`,
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","type":0,"name":"NEO","amount":"100000000","available":"100000000","precision":0,"owner":"00","admin":"Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":4000000,"is_frozen":false}}`,
|
||||
result: func(c *Client) interface{} {
|
||||
return &result.AssetState{
|
||||
ID: core.GoverningTokenID(),
|
||||
|
@ -138,17 +138,17 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
invoke: func(c *Client) (interface{}, error) {
|
||||
return c.GetBlockByIndex(202)
|
||||
},
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"0000000039f06b13aa6ee24d37db9b7868533b7988a6fb1e59c3fa6677dab7fd1f51326405f4fa24f061ffa04ab3127994530ee04c5e578a7b8109133dd142b6a31192cd9debaa5e00000000ca000000e903736ceceeceae1806eee0e3ec61e7cce476ce01fd08010c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b77601094130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb0200570400000000000080000300000075a94799633ed955dd85a8af314a5b435ab51903b0040000000001f6c31d4dd9edf7aea24168885ae82dc574d25e35a9b9b3063f3ea44226a3081f000001dcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d00184a27db86230075a94799633ed955dd85a8af314a5b435ab5190301420c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}`,
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"00000000ef9039404248180eb395d8ff59f985286b6852a68275a487e473114c4240b5938dccbfab32ee60f8b0d16144f258cc30d4f6795522fbd60109e2ed8d1423e9b810cdba5e00000000ca000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c312399694130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb02005704000000000000800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`,
|
||||
result: func(c *Client) interface{} { return &block.Block{} },
|
||||
check: func(t *testing.T, c *Client, result interface{}) {
|
||||
res, ok := result.(*block.Block)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, uint32(0), res.Version)
|
||||
assert.Equal(t, "75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45", res.Hash().StringLE())
|
||||
assert.Equal(t, "6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039", res.PrevHash.StringLE())
|
||||
assert.Equal(t, "cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405", res.MerkleRoot.StringLE())
|
||||
assert.Equal(t, "cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86", res.Hash().StringLE())
|
||||
assert.Equal(t, "93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef", res.PrevHash.StringLE())
|
||||
assert.Equal(t, "b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d", res.MerkleRoot.StringLE())
|
||||
assert.Equal(t, 1, len(res.Transactions))
|
||||
assert.Equal(t, "115975f7d1e9c4577ec839c6ea4ae32727616284e2d7cdc5a49e4e421b32e505", res.Transactions[0].Hash().StringLE())
|
||||
assert.Equal(t, "96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581", res.Transactions[0].Hash().StringLE())
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -156,49 +156,49 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
invoke: func(c *Client) (i interface{}, err error) {
|
||||
return c.GetBlockByIndexVerbose(202)
|
||||
},
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0x75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45","size":765,"version":0,"nextblockhash":"0xec27fed4ff8ad6a87bdd29aecee43e9ecb4e336321dac875d0b14d2aab1c5798","previousblockhash":"0x6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039","merkleroot":"0xcd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405","time":1588259741,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"Ad1wDxzcRiRSryvJobNV211Tv7UUiziPXy","confirmations":6,"script":{"invocation":"0c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b776010","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb"},"tx":[{"sys_fee":"0","net_fee":"0","txid":"0x115975f7d1e9c4577ec839c6ea4ae32727616284e2d7cdc5a49e4e421b32e505","size":238,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6","vout":0}],"vout":[{"address":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","asset":"0x7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}]}]}}`,
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"script":{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"},"tx":[{"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}}`,
|
||||
result: func(c *Client) interface{} {
|
||||
hash, err := util.Uint256DecodeStringLE("75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45")
|
||||
hash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
nextBlockHash, err := util.Uint256DecodeStringLE("ec27fed4ff8ad6a87bdd29aecee43e9ecb4e336321dac875d0b14d2aab1c5798")
|
||||
nextBlockHash, err := util.Uint256DecodeStringLE("13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
prevBlockHash, err := util.Uint256DecodeStringLE("6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039")
|
||||
prevBlockHash, err := util.Uint256DecodeStringLE("93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
merkleRoot, err := util.Uint256DecodeStringLE("cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405")
|
||||
merkleRoot, err := util.Uint256DecodeStringLE("b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
invScript, err := hex.DecodeString("0c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b776010")
|
||||
invScript, err := hex.DecodeString("0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb")
|
||||
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sender, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
|
||||
sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
txInvScript, err := hex.DecodeString("0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff")
|
||||
txInvScript, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4")
|
||||
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vin, err := util.Uint256DecodeStringLE("1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6")
|
||||
vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
outAddress, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
|
||||
outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -231,14 +231,14 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
_ = tx.Hash()
|
||||
return &result.Block{
|
||||
Hash: hash,
|
||||
Size: 765,
|
||||
Size: 781,
|
||||
Version: 0,
|
||||
NextBlockHash: &nextBlockHash,
|
||||
PreviousBlockHash: prevBlockHash,
|
||||
MerkleRoot: merkleRoot,
|
||||
Time: 1588259741,
|
||||
Time: 1589300496,
|
||||
Index: 202,
|
||||
NextConsensus: "Ad1wDxzcRiRSryvJobNV211Tv7UUiziPXy",
|
||||
NextConsensus: "AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL",
|
||||
Confirmations: 6,
|
||||
ConsensusData: result.ConsensusData{
|
||||
PrimaryIndex: 0,
|
||||
|
@ -248,90 +248,84 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
InvocationScript: invScript,
|
||||
VerificationScript: verifScript,
|
||||
},
|
||||
Tx: []result.Tx{{
|
||||
Transaction: tx,
|
||||
Fees: result.Fees{
|
||||
SysFee: 0,
|
||||
NetFee: 0,
|
||||
},
|
||||
}},
|
||||
Tx: []*transaction.Transaction{tx},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byHash_positive",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
hash, err := util.Uint256DecodeStringLE("45bc7f077a4381c9836164b242af97ffa59e5d233ccba4929796ce873474ab75")
|
||||
hash, err := util.Uint256DecodeStringLE("86fe1061140b2ea791b0739fb9732abc6e5e47de4927228a1ac41de3d93eb7cb")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c.GetBlockByHash(hash)
|
||||
},
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"0000000039f06b13aa6ee24d37db9b7868533b7988a6fb1e59c3fa6677dab7fd1f51326405f4fa24f061ffa04ab3127994530ee04c5e578a7b8109133dd142b6a31192cd9debaa5e00000000ca000000e903736ceceeceae1806eee0e3ec61e7cce476ce01fd08010c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b77601094130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb0200570400000000000080000300000075a94799633ed955dd85a8af314a5b435ab51903b0040000000001f6c31d4dd9edf7aea24168885ae82dc574d25e35a9b9b3063f3ea44226a3081f000001dcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d00184a27db86230075a94799633ed955dd85a8af314a5b435ab5190301420c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}`,
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"00000000ef9039404248180eb395d8ff59f985286b6852a68275a487e473114c4240b5938dccbfab32ee60f8b0d16144f258cc30d4f6795522fbd60109e2ed8d1423e9b810cdba5e00000000ca000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c312399694130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb02005704000000000000800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`,
|
||||
result: func(c *Client) interface{} { return &block.Block{} },
|
||||
check: func(t *testing.T, c *Client, result interface{}) {
|
||||
res, ok := result.(*block.Block)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, uint32(0), res.Version)
|
||||
assert.Equal(t, "75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45", res.Hash().StringLE())
|
||||
assert.Equal(t, "6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039", res.PrevHash.StringLE())
|
||||
assert.Equal(t, "cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405", res.MerkleRoot.StringLE())
|
||||
assert.Equal(t, "cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86", res.Hash().StringLE())
|
||||
assert.Equal(t, "93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef", res.PrevHash.StringLE())
|
||||
assert.Equal(t, "b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d", res.MerkleRoot.StringLE())
|
||||
assert.Equal(t, 1, len(res.Transactions))
|
||||
assert.Equal(t, "115975f7d1e9c4577ec839c6ea4ae32727616284e2d7cdc5a49e4e421b32e505", res.Transactions[0].Hash().StringLE())
|
||||
assert.Equal(t, "96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581", res.Transactions[0].Hash().StringLE())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byHash_verbose_positive",
|
||||
invoke: func(c *Client) (i interface{}, err error) {
|
||||
hash, err := util.Uint256DecodeStringLE("45bc7f077a4381c9836164b242af97ffa59e5d233ccba4929796ce873474ab75")
|
||||
hash, err := util.Uint256DecodeStringLE("86fe1061140b2ea791b0739fb9732abc6e5e47de4927228a1ac41de3d93eb7cb")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c.GetBlockByHashVerbose(hash)
|
||||
},
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0x75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45","size":765,"version":0,"nextblockhash":"0xec27fed4ff8ad6a87bdd29aecee43e9ecb4e336321dac875d0b14d2aab1c5798","previousblockhash":"0x6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039","merkleroot":"0xcd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405","time":1588259741,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"Ad1wDxzcRiRSryvJobNV211Tv7UUiziPXy","confirmations":6,"script":{"invocation":"0c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b776010","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb"},"tx":[{"sys_fee":"0","net_fee":"0","txid":"0x115975f7d1e9c4577ec839c6ea4ae32727616284e2d7cdc5a49e4e421b32e505","size":238,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6","vout":0}],"vout":[{"address":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","asset":"0x7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}]}]}}`,
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"script":{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"},"tx":[{"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}}`,
|
||||
result: func(c *Client) interface{} {
|
||||
hash, err := util.Uint256DecodeStringLE("75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45")
|
||||
hash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
nextBlockHash, err := util.Uint256DecodeStringLE("ec27fed4ff8ad6a87bdd29aecee43e9ecb4e336321dac875d0b14d2aab1c5798")
|
||||
nextBlockHash, err := util.Uint256DecodeStringLE("13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
prevBlockHash, err := util.Uint256DecodeStringLE("6432511ffdb7da7766fac3591efba688793b5368789bdb374de26eaa136bf039")
|
||||
prevBlockHash, err := util.Uint256DecodeStringLE("93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
merkleRoot, err := util.Uint256DecodeStringLE("cd9211a3b642d13d1309817b8a575e4ce00e53947912b34aa0ff61f024faf405")
|
||||
merkleRoot, err := util.Uint256DecodeStringLE("b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
invScript, err := hex.DecodeString("0c404617b6788538326383015c44ffddd4a05a4e200b65a26fc84234ae8b1e28ef27b7f139dc498c58071193d530ba83081701290eba8f7108397499f5556c16e3780c402131f2bdcc494c73a379e86c46f9e9fe9899a05b23928926b1eaaf816928e160fe971b82263aa1e7efa5f7e46bf99de735fc4fc5aeb81edfdc6a9b2e9fcfa1000c408cbae1582bb9d82de9ff030b8c729737f2157844c0ca29edcdbfed1dd5e2473e0061f0dc29412477417e2c1f7c55443f11b9bd6e0d0856d1ec00240be1b9b9a70c4034d1055531cf0522ac7e5dcd817cf3cd86997ae38da806dc789b1f16eb0005f00b9bc29f7372bb43a8fed040c6763b162c8a0d8e2d0b2d7476e22e0b2b776010")
|
||||
invScript, err := hex.DecodeString("0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b683073b3bb")
|
||||
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sender, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
|
||||
sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
txInvScript, err := hex.DecodeString("0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff")
|
||||
txInvScript, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4")
|
||||
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vin, err := util.Uint256DecodeStringLE("1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6")
|
||||
vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
outAddress, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
|
||||
outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -364,14 +358,14 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
_ = tx.Hash()
|
||||
return &result.Block{
|
||||
Hash: hash,
|
||||
Size: 765,
|
||||
Size: 781,
|
||||
Version: 0,
|
||||
NextBlockHash: &nextBlockHash,
|
||||
PreviousBlockHash: prevBlockHash,
|
||||
MerkleRoot: merkleRoot,
|
||||
Time: 1588259741,
|
||||
Time: 1589300496,
|
||||
Index: 202,
|
||||
NextConsensus: "Ad1wDxzcRiRSryvJobNV211Tv7UUiziPXy",
|
||||
NextConsensus: "AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL",
|
||||
Confirmations: 6,
|
||||
ConsensusData: result.ConsensusData{
|
||||
PrimaryIndex: 0,
|
||||
|
@ -381,13 +375,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
InvocationScript: invScript,
|
||||
VerificationScript: verifScript,
|
||||
},
|
||||
Tx: []result.Tx{{
|
||||
Transaction: tx,
|
||||
Fees: result.Fees{
|
||||
SysFee: 0,
|
||||
NetFee: 0,
|
||||
},
|
||||
}},
|
||||
Tx: []*transaction.Transaction{tx},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -701,19 +689,19 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
{
|
||||
name: "positive",
|
||||
invoke: func(c *Client) (i interface{}, err error) {
|
||||
hash, err := util.Uint256DecodeStringLE("05e5321b424e9ea4c5cdd7e28462612727e34aeac639c87e57c4e9d1f7755911")
|
||||
hash, err := util.Uint256DecodeStringLE("8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c.GetRawTransaction(hash)
|
||||
},
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"80000300000075a94799633ed955dd85a8af314a5b435ab51903b0040000000001f6c31d4dd9edf7aea24168885ae82dc574d25e35a9b9b3063f3ea44226a3081f000001dcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d00184a27db86230075a94799633ed955dd85a8af314a5b435ab5190301420c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}`,
|
||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`,
|
||||
result: func(c *Client) interface{} { return &transaction.Transaction{} },
|
||||
check: func(t *testing.T, c *Client, result interface{}) {
|
||||
res, ok := result.(*transaction.Transaction)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, uint8(0), res.Version)
|
||||
assert.Equal(t, "05e5321b424e9ea4c5cdd7e28462612727e34aeac639c87e57c4e9d1f7755911", res.Hash().StringBE())
|
||||
assert.Equal(t, "8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96", res.Hash().StringBE())
|
||||
assert.Equal(t, transaction.ContractType, res.Type)
|
||||
assert.Equal(t, false, res.Trimmed)
|
||||
},
|
||||
|
@ -721,35 +709,35 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
{
|
||||
name: "verbose_positive",
|
||||
invoke: func(c *Client) (interface{}, error) {
|
||||
hash, err := util.Uint256DecodeStringLE("05e5321b424e9ea4c5cdd7e28462612727e34aeac639c87e57c4e9d1f7755911")
|
||||
hash, err := util.Uint256DecodeStringLE("8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c.GetRawTransactionVerbose(hash)
|
||||
},
|
||||
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"sys_fee":"0","net_fee":"0","blockhash":"0x75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45","confirmations":8,"blocktime":1588259741,"txid":"0x115975f7d1e9c4577ec839c6ea4ae32727616284e2d7cdc5a49e4e421b32e505","size":238,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6","vout":0}],"vout":[{"address":"ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR","asset":"0x7dc12f8835b085d468ddbab3f7152c0e2f5679b5815b2028685abb4608e7b7dc","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4"}]}}`,
|
||||
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"blockhash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","confirmations":8,"blocktime":1589300496,"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}}`,
|
||||
result: func(c *Client) interface{} {
|
||||
blockHash, err := util.Uint256DecodeStringLE("75ab743487ce969792a4cb3c235d9ea5ff97af42b2646183c981437a077fbc45")
|
||||
blockHash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sender, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
|
||||
sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
invocation, err := hex.DecodeString("0c40bfce1ead7d53339440bb29745eed4ad9840875de4f970950065291e7a14cbd249bfbf777d9a997c5e00bbc08e8ce9fdd2cd13c45c3585b4939599ff84c6149ff")
|
||||
invocation, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
verification, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b680a906ad4")
|
||||
verification, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vin, err := util.Uint256DecodeStringLE("1f08a32642a43e3f06b3b9a9355ed274c52de85a886841a2aef7edd94d1dc3f6")
|
||||
vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
outAddress, err := address.StringToUint160("ASW1VhcukJRrukCXRipY4BE9d9zy4mAYsR")
|
||||
outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -784,11 +772,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
|||
return &result.TransactionOutputRaw{
|
||||
Transaction: tx,
|
||||
TransactionMetadata: result.TransactionMetadata{
|
||||
SysFee: 0,
|
||||
NetFee: 0,
|
||||
Blockhash: blockHash,
|
||||
Confirmations: 8,
|
||||
Timestamp: uint64(1588259741),
|
||||
Timestamp: uint64(1589300496),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
|
@ -14,19 +12,6 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
// Tx wrapper used for the representation of
|
||||
// transaction on the RPC Server.
|
||||
Tx struct {
|
||||
*transaction.Transaction
|
||||
Fees
|
||||
}
|
||||
|
||||
// Fees is an auxilliary struct for proper Tx marshaling.
|
||||
Fees struct {
|
||||
SysFee util.Fixed8 `json:"sys_fee"`
|
||||
NetFee util.Fixed8 `json:"net_fee"`
|
||||
}
|
||||
|
||||
// ConsensusData is a wrapper for block.ConsensusData
|
||||
ConsensusData struct {
|
||||
PrimaryIndex uint32 `json:"primary"`
|
||||
|
@ -51,7 +36,7 @@ type (
|
|||
|
||||
Script transaction.Witness `json:"script"`
|
||||
|
||||
Tx []Tx `json:"tx"`
|
||||
Tx []*transaction.Transaction `json:"tx"`
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -74,7 +59,7 @@ func NewBlock(b *block.Block, chain blockchainer.Blockchainer) Block {
|
|||
|
||||
Script: b.Script,
|
||||
|
||||
Tx: make([]Tx, 0, len(b.Transactions)),
|
||||
Tx: b.Transactions,
|
||||
}
|
||||
|
||||
hash := chain.GetHeaderHash(int(b.Index) + 1)
|
||||
|
@ -82,60 +67,5 @@ func NewBlock(b *block.Block, chain blockchainer.Blockchainer) Block {
|
|||
res.NextBlockHash = &hash
|
||||
}
|
||||
|
||||
for i := range b.Transactions {
|
||||
res.Tx = append(res.Tx, Tx{
|
||||
Transaction: b.Transactions[i],
|
||||
Fees: Fees{
|
||||
SysFee: chain.SystemFee(b.Transactions[i]),
|
||||
NetFee: chain.NetworkFee(b.Transactions[i]),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler interface.
|
||||
func (t Tx) MarshalJSON() ([]byte, error) {
|
||||
output, err := json.Marshal(&Fees{
|
||||
SysFee: t.SysFee,
|
||||
NetFee: t.NetFee,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txBytes, err := json.Marshal(t.Transaction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We have to keep both transaction.Transaction and tx at the same level in json in order to match C# API,
|
||||
// so there's no way to marshall Tx correctly with standard json.Marshaller tool.
|
||||
if output[len(output)-1] != '}' || txBytes[0] != '{' {
|
||||
return nil, errors.New("can't merge internal jsons")
|
||||
}
|
||||
output[len(output)-1] = ','
|
||||
output = append(output, txBytes[1:]...)
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Marshaler interface.
|
||||
func (t *Tx) UnmarshalJSON(data []byte) error {
|
||||
// As transaction.Transaction and tx are at the same level in json, do unmarshalling
|
||||
// separately for both structs.
|
||||
output := new(Fees)
|
||||
err := json.Unmarshal(data, output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.SysFee = output.SysFee
|
||||
t.NetFee = output.NetFee
|
||||
|
||||
transaction := new(transaction.Transaction)
|
||||
err = json.Unmarshal(data, transaction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Transaction = transaction
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@ type TransactionOutputRaw struct {
|
|||
|
||||
// TransactionMetadata is an auxilliary struct for proper TransactionOutputRaw marshaling.
|
||||
type TransactionMetadata struct {
|
||||
SysFee util.Fixed8 `json:"sys_fee"`
|
||||
NetFee util.Fixed8 `json:"net_fee"`
|
||||
Blockhash util.Uint256 `json:"blockhash,omitempty"`
|
||||
Confirmations int `json:"confirmations,omitempty"`
|
||||
Timestamp uint64 `json:"blocktime,omitempty"`
|
||||
|
@ -37,8 +35,6 @@ func NewTransactionOutputRaw(tx *transaction.Transaction, header *block.Header,
|
|||
return TransactionOutputRaw{
|
||||
Transaction: tx,
|
||||
TransactionMetadata: TransactionMetadata{
|
||||
SysFee: chain.SystemFee(tx),
|
||||
NetFee: chain.NetworkFee(tx),
|
||||
Blockhash: header.Hash(),
|
||||
Confirmations: confirmations,
|
||||
Timestamp: header.Timestamp,
|
||||
|
@ -49,8 +45,6 @@ func NewTransactionOutputRaw(tx *transaction.Transaction, header *block.Header,
|
|||
// MarshalJSON implements json.Marshaler interface.
|
||||
func (t TransactionOutputRaw) MarshalJSON() ([]byte, error) {
|
||||
output, err := json.Marshal(TransactionMetadata{
|
||||
SysFee: t.SysFee,
|
||||
NetFee: t.NetFee,
|
||||
Blockhash: t.Blockhash,
|
||||
Confirmations: t.Confirmations,
|
||||
Timestamp: t.Timestamp,
|
||||
|
@ -82,8 +76,6 @@ func (t *TransactionOutputRaw) UnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.SysFee = output.SysFee
|
||||
t.NetFee = output.NetFee
|
||||
t.Blockhash = output.Blockhash
|
||||
t.Confirmations = output.Confirmations
|
||||
t.Timestamp = output.Timestamp
|
||||
|
|
|
@ -798,7 +798,7 @@ func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *respons
|
|||
|
||||
var blockSysFee util.Fixed8
|
||||
for _, tx := range block.Transactions {
|
||||
blockSysFee += s.chain.SystemFee(tx)
|
||||
blockSysFee += tx.SystemFee
|
||||
}
|
||||
|
||||
return blockSysFee, nil
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/network"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -65,18 +64,14 @@ func initServerWithInMemoryChain(t *testing.T) (*core.Blockchain, *httptest.Serv
|
|||
|
||||
type FeerStub struct{}
|
||||
|
||||
func (fs *FeerStub) NetworkFee(*transaction.Transaction) util.Fixed8 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (fs *FeerStub) IsLowPriority(util.Fixed8) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (fs *FeerStub) FeePerByte(*transaction.Transaction) util.Fixed8 {
|
||||
func (fs *FeerStub) FeePerByte() util.Fixed8 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (fs *FeerStub) SystemFee(*transaction.Transaction) util.Fixed8 {
|
||||
return 0
|
||||
func (fs *FeerStub) GetUtilityTokenBalance(acc util.Uint160) util.Fixed8 {
|
||||
return util.Fixed8FromInt64(1000000)
|
||||
}
|
||||
|
|
|
@ -54,12 +54,12 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
"getapplicationlog": {
|
||||
{
|
||||
name: "positive",
|
||||
params: `["396d55aa14b6cd428d793e9e740d24f93f62d7ddcdc0f4fdadd4dfd89bdabd83"]`,
|
||||
params: `["0a0abf0188053113d0014e0cb9801d090a5d3e7640d76427fa1a3676e7cdf82e"]`,
|
||||
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
|
||||
check: func(t *testing.T, e *executor, acc interface{}) {
|
||||
res, ok := acc.(*result.ApplicationLog)
|
||||
require.True(t, ok)
|
||||
expectedTxHash, err := util.Uint256DecodeStringLE("396d55aa14b6cd428d793e9e740d24f93f62d7ddcdc0f4fdadd4dfd89bdabd83")
|
||||
expectedTxHash, err := util.Uint256DecodeStringLE("0a0abf0188053113d0014e0cb9801d090a5d3e7640d76427fa1a3676e7cdf82e")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedTxHash, res.TxHash)
|
||||
assert.Equal(t, 1, len(res.Executions))
|
||||
|
@ -170,11 +170,23 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
check: func(t *testing.T, e *executor, acc interface{}) {
|
||||
res, ok := acc.(*result.NEP5Balances)
|
||||
require.True(t, ok)
|
||||
rubles, err := util.Uint160DecodeStringLE(testContractHash)
|
||||
require.NoError(t, err)
|
||||
expected := result.NEP5Balances{
|
||||
Balances: []result.NEP5Balance{{
|
||||
Asset: rubles,
|
||||
Amount: "8.77",
|
||||
LastUpdated: 208,
|
||||
},
|
||||
{
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Amount: "10",
|
||||
LastUpdated: 1,
|
||||
}},
|
||||
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
||||
}
|
||||
require.Equal(t, testchain.PrivateKeyByID(0).Address(), res.Address)
|
||||
require.Equal(t, 1, len(res.Balances))
|
||||
require.Equal(t, "8.77", res.Balances[0].Amount)
|
||||
require.Equal(t, testContractHash, res.Balances[0].Asset.StringLE())
|
||||
require.Equal(t, uint32(208), res.Balances[0].LastUpdated)
|
||||
require.ElementsMatch(t, expected.Balances, res.Balances)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -196,20 +208,56 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
check: func(t *testing.T, e *executor, acc interface{}) {
|
||||
res, ok := acc.(*result.NEP5Transfers)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, testchain.PrivateKeyByID(0).Address(), res.Address)
|
||||
|
||||
assetHash, err := util.Uint160DecodeStringLE(testContractHash)
|
||||
rublesHash, err := util.Uint160DecodeStringLE(testContractHash)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(res.Received))
|
||||
require.Equal(t, "10", res.Received[0].Amount)
|
||||
require.Equal(t, assetHash, res.Received[0].Asset)
|
||||
require.Equal(t, address.Uint160ToString(assetHash), res.Received[0].Address)
|
||||
|
||||
require.Equal(t, 1, len(res.Sent))
|
||||
require.Equal(t, "1.23", res.Sent[0].Amount)
|
||||
require.Equal(t, assetHash, res.Sent[0].Asset)
|
||||
require.Equal(t, testchain.PrivateKeyByID(1).Address(), res.Sent[0].Address)
|
||||
blockSendRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(208))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(blockSendRubles.Transactions))
|
||||
txSendRublesHash := blockSendRubles.Transactions[0].Hash()
|
||||
blockRecieveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(207))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(blockRecieveRubles.Transactions))
|
||||
txRecieveRublesHash := blockRecieveRubles.Transactions[1].Hash()
|
||||
blockRecieveGAS, err := e.chain.GetBlock(e.chain.GetHeaderHash(1))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(blockRecieveGAS.Transactions))
|
||||
txRecieveGASHash := blockRecieveGAS.Transactions[0].Hash()
|
||||
require.NoError(t, err)
|
||||
expected := result.NEP5Transfers{
|
||||
Sent: []result.NEP5Transfer{{
|
||||
Timestamp: blockSendRubles.Timestamp,
|
||||
Asset: rublesHash,
|
||||
Address: testchain.PrivateKeyByID(1).Address(),
|
||||
Amount: "1.23",
|
||||
Index: 208,
|
||||
NotifyIndex: 0,
|
||||
TxHash: txSendRublesHash,
|
||||
}},
|
||||
Received: []result.NEP5Transfer{
|
||||
{
|
||||
Timestamp: blockRecieveRubles.Timestamp,
|
||||
Asset: rublesHash,
|
||||
Address: address.Uint160ToString(rublesHash),
|
||||
Amount: "10",
|
||||
Index: 207,
|
||||
NotifyIndex: 0,
|
||||
TxHash: txRecieveRublesHash,
|
||||
},
|
||||
{
|
||||
Timestamp: blockRecieveGAS.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Address: testchain.MultisigAddress(),
|
||||
Amount: "10",
|
||||
Index: 1,
|
||||
NotifyIndex: 0,
|
||||
TxHash: txRecieveGASHash,
|
||||
},
|
||||
},
|
||||
Address: testchain.PrivateKeyByID(0).Address(),
|
||||
}
|
||||
require.Equal(t, expected.Address, res.Address)
|
||||
require.ElementsMatch(t, expected.Sent, res.Sent)
|
||||
require.ElementsMatch(t, expected.Received, res.Received)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -254,7 +302,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
"getassetstate": {
|
||||
{
|
||||
name: "positive",
|
||||
params: `["8ef63ccd6f4ea20a93e7f4e84b2d43f778077612b241d617e42e1750cca4f2c5"]`,
|
||||
params: `["f882fb865bab84b99623f21eedd902286af7da8d8a4609d7acefce04c851dc1c"]`,
|
||||
result: func(e *executor) interface{} { return &result.AssetState{} },
|
||||
check: func(t *testing.T, e *executor, as interface{}) {
|
||||
res, ok := as.(*result.AssetState)
|
||||
|
@ -327,24 +375,24 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
"getblock": {
|
||||
{
|
||||
name: "positive",
|
||||
params: "[2, 1]",
|
||||
params: "[3, 1]",
|
||||
result: func(e *executor) interface{} { return &result.Block{} },
|
||||
check: func(t *testing.T, e *executor, blockRes interface{}) {
|
||||
res, ok := blockRes.(*result.Block)
|
||||
require.True(t, ok)
|
||||
|
||||
block, err := e.chain.GetBlock(e.chain.GetHeaderHash(2))
|
||||
block, err := e.chain.GetBlock(e.chain.GetHeaderHash(3))
|
||||
require.NoErrorf(t, err, "could not get block")
|
||||
|
||||
assert.Equal(t, block.Hash(), res.Hash)
|
||||
for i := range res.Tx {
|
||||
tx := res.Tx[i]
|
||||
require.Equal(t, transaction.ContractType, tx.Transaction.Type)
|
||||
require.Equal(t, transaction.ContractType, tx.Type)
|
||||
|
||||
actualTx := block.Transactions[i]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, actualTx.Nonce, tx.Transaction.Nonce)
|
||||
require.Equal(t, block.Transactions[i].Hash(), tx.Transaction.Hash())
|
||||
require.Equal(t, actualTx.Nonce, tx.Nonce)
|
||||
require.Equal(t, block.Transactions[i].Hash(), tx.Hash())
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -436,7 +484,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
|
||||
var expectedBlockSysFee util.Fixed8
|
||||
for _, tx := range block.Transactions {
|
||||
expectedBlockSysFee += e.chain.SystemFee(tx)
|
||||
expectedBlockSysFee += tx.SystemFee
|
||||
}
|
||||
return &expectedBlockSysFee
|
||||
},
|
||||
|
@ -473,7 +521,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
params: `["` + testchain.MultisigAddress() + `"]`,
|
||||
result: func(*executor) interface{} {
|
||||
// hash of the issueTx
|
||||
h, _ := util.Uint256DecodeStringBE("3b76c9b726ffa9074a69441bf946c4c70b83474b3cf522ea3ba9dcd71c1a3db8")
|
||||
h, _ := util.Uint256DecodeStringBE("d3a4f2249fe33b18bde73901c1ecc66200485f1c1dcd941b406a630b479090ae")
|
||||
amount := util.Fixed8FromInt64(1 * 8) // (endHeight - startHeight) * genAmount[0]
|
||||
return &result.ClaimableInfo{
|
||||
Spents: []result.Claimable{
|
||||
|
@ -532,7 +580,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
"gettransactionheight": {
|
||||
{
|
||||
name: "positive",
|
||||
params: `["a05ea6d90b761ec5430f29d25036fdad04efe731b6a5906f4fd1e19048dee0f2"]`,
|
||||
params: `["0e873d5d565a03c6cd39efa3b446e1901b4636c448a22bc7e8c259c5a28a2eda"]`,
|
||||
result: func(e *executor) interface{} {
|
||||
h := 1
|
||||
return &h
|
||||
|
@ -744,7 +792,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
"sendrawtransaction": {
|
||||
{
|
||||
name: "positive",
|
||||
params: `["80000b000000316e851039019d39dfc2c37d6c3fee19fd580987b0040000000001241a237db30af3b33d29518288e0c9b542475733f78f4c6d7416cba89c1f1a67010001dcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d0030d3dec3862300316e851039019d39dfc2c37d6c3fee19fd58098701420c40c3c3fedb73e36a8e78bae80ba07c20b34f5af6bba36dccbe0cdc7d4e1237f85eec36e63cb762e1e6ce031a6c752e1ce19a3994d15191a6b75f1a02ede7f9d117290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"]`,
|
||||
params: `["80000b000000316e851039019d39dfc2c37d6c3fee19fd5809870000000000000000a267050000000000b00400000000017a03a89832a347c4fb53af1f526d0d930b14ab6eb01629ce20ffbaeaeef58af3010001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a0030d3dec3862300316e851039019d39dfc2c37d6c3fee19fd58098701420c40b6aeec1d2699194b842f399448b395d98bbb287dc89ea9e5ce3bb99a1c8c9bf933f55b69db6709b44e6a5c8b28b97018466479e5d500e414a0874c37abab262d290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"]`,
|
||||
result: func(e *executor) interface{} {
|
||||
v := true
|
||||
return &v
|
||||
|
@ -867,16 +915,26 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
checkErrGetResult(t, body, true)
|
||||
})
|
||||
|
||||
wif := testchain.WIF(0)
|
||||
acc, err := wallet.NewAccountFromWIF(wif)
|
||||
priv0 := testchain.PrivateKeyByID(0)
|
||||
acc0, err := wallet.NewAccountFromWIF(priv0.WIF())
|
||||
require.NoError(t, err)
|
||||
|
||||
addNetworkFee := func(tx *transaction.Transaction) {
|
||||
size := io.GetVarSize(tx)
|
||||
netFee, sizeDelta := core.CalculateNetworkFee(acc0.Contract.Script)
|
||||
tx.NetworkFee += netFee
|
||||
size += sizeDelta
|
||||
tx.NetworkFee = tx.NetworkFee.Add(util.Fixed8(int64(size) * int64(chain.FeePerByte())))
|
||||
}
|
||||
|
||||
newTx := func() *transaction.Transaction {
|
||||
height := chain.BlockHeight()
|
||||
tx := transaction.NewContractTX()
|
||||
tx.Nonce = height + 1
|
||||
tx.ValidUntilBlock = height + 10
|
||||
tx.Sender = acc.PrivateKey().GetScriptHash()
|
||||
require.NoError(t, acc.SignTx(tx))
|
||||
tx.Sender = acc0.PrivateKey().GetScriptHash()
|
||||
addNetworkFee(tx)
|
||||
require.NoError(t, acc0.SignTx(tx))
|
||||
return tx
|
||||
}
|
||||
|
||||
|
@ -905,7 +963,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
var res string
|
||||
err := json.Unmarshal(result, &res)
|
||||
require.NoErrorf(t, err, "could not parse response: %s", result)
|
||||
assert.Equal(t, "400000000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000", res)
|
||||
assert.Equal(t, "400000000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b000000000000000000000000000000000000000000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000", res)
|
||||
})
|
||||
|
||||
t.Run("getrawtransaction 2 arguments", func(t *testing.T) {
|
||||
|
@ -917,7 +975,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
var res string
|
||||
err := json.Unmarshal(result, &res)
|
||||
require.NoErrorf(t, err, "could not parse response: %s", result)
|
||||
assert.Equal(t, "400000000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000", res)
|
||||
assert.Equal(t, "400000000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b000000000000000000000000000000000000000000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b0000000000", res)
|
||||
})
|
||||
|
||||
t.Run("getrawtransaction 2 arguments, verbose", func(t *testing.T) {
|
||||
|
@ -1006,7 +1064,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
err := json.Unmarshal(res, &txOut)
|
||||
require.NoErrorf(t, err, "could not parse response: %s", res)
|
||||
assert.Equal(t, 0, txOut.N)
|
||||
assert.Equal(t, "0xdcb7e70846bb5a6828205b81b579562f0e2c15f7b3badd68d485b035882fc17d", txOut.Asset)
|
||||
assert.Equal(t, "0x787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a", txOut.Asset)
|
||||
assert.Equal(t, util.Fixed8FromInt64(100000000), txOut.Value)
|
||||
assert.Equal(t, testchain.MultisigAddress(), txOut.Address)
|
||||
})
|
||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
|
@ -70,7 +70,7 @@ func (c *ParameterContext) GetWitness(ctr *wallet.Contract) (*transaction.Witnes
|
|||
// AddSignature adds a signature for the specified contract and public key.
|
||||
func (c *ParameterContext) AddSignature(ctr *wallet.Contract, pub *keys.PublicKey, sig []byte) error {
|
||||
item := c.getItemForContract(ctr)
|
||||
if pubs, ok := vm.ParseMultiSigContract(ctr.Script); ok {
|
||||
if _, pubs, ok := vm.ParseMultiSigContract(ctr.Script); ok {
|
||||
if item.GetSignature(pub) != nil {
|
||||
return errors.New("signature is already added")
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ func (u Uint160) Reverse() (r Uint160) {
|
|||
return
|
||||
}
|
||||
|
||||
// Equals returns true if both Uint256 values are the same.
|
||||
// Equals returns true if both Uint160 values are the same.
|
||||
func (u Uint160) Equals(other Uint160) bool {
|
||||
return u == other
|
||||
}
|
||||
|
|
|
@ -36,65 +36,65 @@ func getNumOfThingsFromInstr(instr opcode.Opcode, param []byte) (int, bool) {
|
|||
// IsMultiSigContract checks whether the passed script is a multi-signature
|
||||
// contract.
|
||||
func IsMultiSigContract(script []byte) bool {
|
||||
_, ok := ParseMultiSigContract(script)
|
||||
_, _, ok := ParseMultiSigContract(script)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ParseMultiSigContract returns list of public keys from the verification
|
||||
// script of the contract.
|
||||
func ParseMultiSigContract(script []byte) ([][]byte, bool) {
|
||||
// ParseMultiSigContract returns number of signatures and list of public keys
|
||||
// from the verification script of the contract.
|
||||
func ParseMultiSigContract(script []byte) (int, [][]byte, bool) {
|
||||
var nsigs, nkeys int
|
||||
|
||||
ctx := NewContext(script)
|
||||
instr, param, err := ctx.Next()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
nsigs, ok := getNumOfThingsFromInstr(instr, param)
|
||||
if !ok {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
var pubs [][]byte
|
||||
for {
|
||||
instr, param, err = ctx.Next()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
if instr != opcode.PUSHDATA1 {
|
||||
break
|
||||
}
|
||||
if len(param) < 33 {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
pubs = append(pubs, param)
|
||||
nkeys++
|
||||
if nkeys > MaxArraySize {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
}
|
||||
if nkeys < nsigs {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
nkeys2, ok := getNumOfThingsFromInstr(instr, param)
|
||||
if !ok {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
if nkeys2 != nkeys {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
instr, _, err = ctx.Next()
|
||||
if err != nil || instr != opcode.PUSHNULL {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
instr, param, err = ctx.Next()
|
||||
if err != nil || instr != opcode.SYSCALL || binary.LittleEndian.Uint32(param) != multisigInteropID {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
instr, _, err = ctx.Next()
|
||||
if err != nil || instr != opcode.RET || ctx.ip != len(script) {
|
||||
return nil, false
|
||||
return nsigs, nil, false
|
||||
}
|
||||
return pubs, true
|
||||
return nsigs, pubs, true
|
||||
}
|
||||
|
||||
// IsSignatureContract checks whether the passed script is a signature check
|
||||
|
|
Loading…
Reference in a new issue