diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index ae227d346..6d6779ffd 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -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() diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index d31b9a8a9..6452931ad 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -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 - } + mt := merkle.NewMerkleTree(append([]util.Uint256{consensusData.Hash()}, ctx.TransactionHashes...)...) + block.Block.MerkleRoot = mt.Root().Hash return block } diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index eaaacaa03..0b8d3dfc3 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -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) } +func (fs *feer) IsLowPriority(util.Fixed8) bool { return false } 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() diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 35a8a07fc..0ef370b8f 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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,16 +651,26 @@ 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) - if !ok { - continue + 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) - if !ok { - continue + 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 { @@ -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()) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 575f3d038..05f67e7e9 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -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 +} diff --git a/pkg/core/mempool/feer.go b/pkg/core/mempool/feer.go index 89b63dab5..91d81e4e8 100644 --- a/pkg/core/mempool/feer.go +++ b/pkg/core/mempool/feer.go @@ -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 } diff --git a/pkg/core/mempool/mem_pool.go b/pkg/core/mempool/mem_pool.go index 7dc38e2d4..bd566936a 100644 --- a/pkg/core/mempool/mem_pool.go +++ b/pkg/core/mempool/mem_pool.go @@ -25,11 +25,9 @@ var ( // item represents a transaction in the the Memory pool. type item struct { - txn *transaction.Transaction - timeStamp time.Time - perByteFee util.Fixed8 - netFee util.Fixed8 - isLowPrio bool + txn *transaction.Transaction + timeStamp time.Time + isLowPrio bool } // items is a slice of item. @@ -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), + txn: t, + timeStamp: time.Now().UTC(), } - 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) } diff --git a/pkg/core/mempool/mem_pool_test.go b/pkg/core/mempool/mem_pool_test.go index bd6bd47fb..c4dbfec75 100644 --- a/pkg/core/mempool/mem_pool_test.go +++ b/pkg/core/mempool/mem_pool_test.go @@ -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)) +} diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index cdc0cd97b..8706f1ea1 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -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, , 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 } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index cd35e942a..67bc5b832 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -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 -} diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index 021eb6b9c..7ea25e20f 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -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 +} diff --git a/pkg/core/opcode_price.go b/pkg/core/opcode_price.go new file mode 100644 index 000000000..06302c051 --- /dev/null +++ b/pkg/core/opcode_price.go @@ -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, +} diff --git a/pkg/core/transaction/transaction.go b/pkg/core/transaction/transaction.go index e2e399c2a..6099ddaa2 100644 --- a/pkg/core/transaction/transaction.go +++ b/pkg/core/transaction/transaction.go @@ -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") diff --git a/pkg/core/util.go b/pkg/core/util.go index 1c998d724..643e101e2 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -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 +} diff --git a/pkg/core/util_test.go b/pkg/core/util_test.go index 90d8dbbe7..090bfb8ac 100644 --- a/pkg/core/util_test.go +++ b/pkg/core/util_test.go @@ -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()) } diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 6ef6487aa..411afc88d 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -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") } diff --git a/pkg/rpc/client/nep5.go b/pkg/rpc/client/nep5.go index 88ccc4671..06302e8eb 100644 --- a/pkg/rpc/client/nep5.go +++ b/pkg/rpc/client/nep5.go @@ -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) } diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 3e53db23d..e0b8326bd 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -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) +} diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 8ceda6640..3bcf13f7a 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -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), }, } }, diff --git a/pkg/rpc/response/result/block.go b/pkg/rpc/response/result/block.go index ccdef6fd5..3e18d6133 100644 --- a/pkg/rpc/response/result/block.go +++ b/pkg/rpc/response/result/block.go @@ -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 -} diff --git a/pkg/rpc/response/result/tx_raw_output.go b/pkg/rpc/response/result/tx_raw_output.go index 886d865ed..ae47dda50 100644 --- a/pkg/rpc/response/result/tx_raw_output.go +++ b/pkg/rpc/response/result/tx_raw_output.go @@ -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 diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 817cba0ba..09c8dd11d 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -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 diff --git a/pkg/rpc/server/server_helper_test.go b/pkg/rpc/server/server_helper_test.go index c6ee3167e..b65897ab3 100644 --- a/pkg/rpc/server/server_helper_test.go +++ b/pkg/rpc/server/server_helper_test.go @@ -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) } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 062c01770..1d28ab001 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -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) }) diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 0957d415d..bae75785b 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ diff --git a/pkg/smartcontract/context/context.go b/pkg/smartcontract/context/context.go index b9dc12966..c92b1fcd3 100644 --- a/pkg/smartcontract/context/context.go +++ b/pkg/smartcontract/context/context.go @@ -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") } diff --git a/pkg/util/uint160.go b/pkg/util/uint160.go index 736d1fe78..0eddf3fc2 100644 --- a/pkg/util/uint160.go +++ b/pkg/util/uint160.go @@ -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 } diff --git a/pkg/vm/contract_checks.go b/pkg/vm/contract_checks.go index 257d3e3ec..e4acc3766 100644 --- a/pkg/vm/contract_checks.go +++ b/pkg/vm/contract_checks.go @@ -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