mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 13:47:19 +00:00
Implement rpc server method: sendrawtransaction (#174)
* Added new config attributes: 'SecondsPerBlock','LowPriorityThreshold' * Added new files: * Added new method: CompareTo * Fixed empty Slice case * Added new methods: LessThan, GreaterThan, Equal, CompareTo * Added new method: InputIntersection * Added MaxTransactionSize, GroupOutputByAssetID * Added ned method: ScriptHash * Added new method: IsDoubleSpend * Refactor blockchainer, Added Feer interface, Verify and GetMemPool method * 1) Added MemPool 2) Added new methods to satisfy the blockchainer interface: IsLowPriority, Verify, GetMemPool * Added new methods: RelayTxn, RelayDirectly * Fixed tests * Implemented RPC server method sendrawtransaction * Refactor getrawtransaction, sendrawtransaction in separate methods * Moved 'secondsPerBlock' to config file * Implemented Kim suggestions: 1) Fixed data race issues 2) refactor Verify method 3) Get rid of unused InputIntersection method due to refactoring Verify method 4) Fixed bug in https://github.com/CityOfZion/neo-go/pull/174#discussion_r264108135 5) minor simplications of the code * Fixed minor issues related to 1) space 2) getter methods do not need pointer on the receiver 3) error message 4) refactoring CompareTo method in uint256.go * Fixed small issues * Use sync.RWMutex instead of sync.Mutex * Refined (R)Lock/(R)Unlock * return error instead of bool in Verify methods
This commit is contained in:
parent
a5e85d69de
commit
095653af23
22 changed files with 879 additions and 68 deletions
|
@ -43,6 +43,8 @@ type (
|
|||
ProtocolConfiguration struct {
|
||||
Magic NetMode `yaml:"Magic"`
|
||||
AddressVersion int64 `yaml:"AddressVersion"`
|
||||
SecondsPerBlock int `yaml:"SecondsPerBlock"`
|
||||
LowPriorityThreshold float64 `yaml:"LowPriorityThreshold"`
|
||||
MaxTransactionsPerBlock int64 `yaml:"MaxTransactionsPerBlock"`
|
||||
StandbyValidators []string `yaml:"StandbyValidators"`
|
||||
SeedList []string `yaml:"SeedList"`
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
ProtocolConfiguration:
|
||||
Magic: 7630401
|
||||
AddressVersion: 23
|
||||
SecondsPerBlock: 15
|
||||
LowPriorityThreshold: 0.001
|
||||
StandbyValidators:
|
||||
- 03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c
|
||||
- 02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
ProtocolConfiguration:
|
||||
Magic: 56753
|
||||
AddressVersion: 23
|
||||
SecondsPerBlock: 15
|
||||
LowPriorityThreshold: 0.000
|
||||
StandbyValidators:
|
||||
- 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2
|
||||
- 02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e
|
||||
- 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699
|
||||
- 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62
|
||||
- 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2
|
||||
- 02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e
|
||||
- 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699
|
||||
- 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62
|
||||
SeedList:
|
||||
- 127.0.0.1:20334
|
||||
- 127.0.0.1:20335
|
||||
- 127.0.0.1:20336
|
||||
- 127.0.0.1:20334
|
||||
- 127.0.0.1:20335
|
||||
- 127.0.0.1:20336
|
||||
SystemFee:
|
||||
EnrollmentTransaction: 1000
|
||||
IssueTransaction: 500
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
ProtocolConfiguration:
|
||||
Magic: 1953787457
|
||||
AddressVersion: 23
|
||||
SecondsPerBlock: 15
|
||||
LowPriorityThreshold: 0.000
|
||||
StandbyValidators:
|
||||
- 0327da12b5c40200e9f65569476bbff2218da4f32548ff43b6387ec1416a231ee8
|
||||
- 026ce35b29147ad09e4afe4ec4a7319095f08198fa8babbe3c56e970b143528d22
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
ProtocolConfiguration:
|
||||
Magic: 56753
|
||||
AddressVersion: 23
|
||||
SecondsPerBlock: 15
|
||||
LowPriorityThreshold: 0.000
|
||||
StandbyValidators:
|
||||
- 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2
|
||||
- 02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e
|
||||
- 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699
|
||||
- 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62
|
||||
- 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2
|
||||
- 02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e
|
||||
- 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699
|
||||
- 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62
|
||||
SeedList:
|
||||
- 127.0.0.1:20334
|
||||
- 127.0.0.1:20335
|
||||
- 127.0.0.1:20336
|
||||
- 127.0.0.1:20334
|
||||
- 127.0.0.1:20335
|
||||
- 127.0.0.1:20336
|
||||
SystemFee:
|
||||
EnrollmentTransaction: 1000
|
||||
IssueTransaction: 500
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
@ -12,12 +13,12 @@ import (
|
|||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// tuning parameters
|
||||
const (
|
||||
secondsPerBlock = 15
|
||||
headerBatchCount = 2000
|
||||
version = "0.0.1"
|
||||
)
|
||||
|
@ -55,6 +56,8 @@ type Blockchain struct {
|
|||
|
||||
// Whether we will verify received blocks.
|
||||
verifyBlocks bool
|
||||
|
||||
memPool MemPool
|
||||
}
|
||||
|
||||
type headersOpFunc func(headerList *HeaderHashList)
|
||||
|
@ -69,10 +72,11 @@ func NewBlockchain(ctx context.Context, s storage.Store, cfg config.ProtocolConf
|
|||
headersOpDone: make(chan struct{}),
|
||||
blockCache: NewCache(),
|
||||
verifyBlocks: false,
|
||||
memPool: NewMemPool(50000),
|
||||
}
|
||||
|
||||
go bc.run(ctx)
|
||||
if err := bc.init(); err != nil {
|
||||
go bc.run(ctx)
|
||||
if err := bc.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -457,6 +461,10 @@ func (bc *Blockchain) headerListLen() (n int) {
|
|||
|
||||
// GetTransaction returns a TX and its height by the given hash.
|
||||
func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) {
|
||||
if tx, ok := bc.memPool.TryGetValue(hash); ok {
|
||||
return tx, 0, nil // the height is not actually defined for memPool transaction. Not sure if zero is a good number in this case.
|
||||
}
|
||||
|
||||
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesReverse())
|
||||
b, err := bc.Get(key)
|
||||
if err != nil {
|
||||
|
@ -506,9 +514,17 @@ func (bc *Blockchain) GetHeader(hash util.Uint256) (*Header, error) {
|
|||
return block.Header(), nil
|
||||
}
|
||||
|
||||
// HasBlock return true if the blockchain contains he given
|
||||
// HasTransaction return true if the blockchain contains he given
|
||||
// transaction hash.
|
||||
func (bc *Blockchain) HasTransaction(hash util.Uint256) bool {
|
||||
if bc.memPool.ContainsKey(hash) {
|
||||
return true
|
||||
}
|
||||
|
||||
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesReverse())
|
||||
if _, err := bc.Get(key); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -596,7 +612,7 @@ func (bc *Blockchain) GetConfig() config.ProtocolConfiguration {
|
|||
// transaction package because of a import cycle problem. Perhaps we should think to re-design
|
||||
// the code base to avoid this situation.
|
||||
func (bc *Blockchain) References(t *transaction.Transaction) map[util.Uint256]*transaction.Output {
|
||||
references := make(map[util.Uint256]*transaction.Output)
|
||||
references := make(map[util.Uint256]*transaction.Output, 0)
|
||||
|
||||
for prevHash, inputs := range t.GroupInputsByPrevHash() {
|
||||
if tx, _, err := bc.GetTransaction(prevHash); err != nil {
|
||||
|
@ -641,8 +657,279 @@ func (bc *Blockchain) SystemFee(t *transaction.Transaction) util.Fixed8 {
|
|||
return bc.GetConfig().SystemFee.TryGetValue(t.Type)
|
||||
}
|
||||
|
||||
// IsLowPriority flags a trnsaction as low priority if the network fee is less than
|
||||
// LowPriorityThreshold
|
||||
func (bc *Blockchain) IsLowPriority(t *transaction.Transaction) bool {
|
||||
return bc.NetworkFee(t) < util.NewFixed8FromFloat(bc.GetConfig().LowPriorityThreshold)
|
||||
}
|
||||
|
||||
// GetMemPool returns the memory pool of the blockchain.
|
||||
func (bc *Blockchain) GetMemPool() MemPool {
|
||||
return bc.memPool
|
||||
}
|
||||
|
||||
// Verify verifies whether a transaction is bonafide or not.
|
||||
// Golang implementation of Verify method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L270).
|
||||
func (bc *Blockchain) Verify(t *transaction.Transaction) error {
|
||||
if t.Size() > transaction.MaxTransactionSize {
|
||||
return errors.Errorf("invalid transaction size = %d. It shoud be less then MaxTransactionSize = %d", t.Size(), transaction.MaxTransactionSize)
|
||||
}
|
||||
if ok := bc.verifyInputs(t); !ok {
|
||||
return errors.New("invalid transaction's inputs")
|
||||
}
|
||||
if ok := bc.memPool.Verify(t); !ok {
|
||||
return errors.New("invalid transaction due to conflicts with the memory pool")
|
||||
}
|
||||
if IsDoubleSpend(bc.Store, t) {
|
||||
return errors.New("invalid transaction caused by double spending")
|
||||
}
|
||||
if ok := bc.verifyOutputs(t); !ok {
|
||||
return errors.New("invalid transaction's outputs")
|
||||
}
|
||||
if ok := bc.verifyResults(t); !ok {
|
||||
return errors.New("invalid transaction's results")
|
||||
}
|
||||
|
||||
for _, a := range t.Attributes {
|
||||
if a.Usage == transaction.ECDH02 || a.Usage == transaction.ECDH03 {
|
||||
return errors.Errorf("invalid attribute's usage = %s ", a.Usage)
|
||||
}
|
||||
}
|
||||
|
||||
return bc.VerifyWitnesses(t)
|
||||
}
|
||||
|
||||
func (bc *Blockchain) verifyInputs(t *transaction.Transaction) bool {
|
||||
for i := 1; i < len(t.Inputs); i++ {
|
||||
for j := 0; j < i; j++ {
|
||||
if t.Inputs[i].PrevHash == t.Inputs[j].PrevHash && t.Inputs[i].PrevIndex == t.Inputs[j].PrevIndex {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (bc *Blockchain) verifyOutputs(t *transaction.Transaction) bool {
|
||||
for assetID, outputs := range t.GroupOutputByAssetID() {
|
||||
assetState := bc.GetAssetState(assetID)
|
||||
if assetState == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if assetState.Expiration < bc.blockHeight+1 && assetState.AssetType != transaction.GoverningToken && assetState.AssetType != transaction.UtilityToken {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, out := range outputs {
|
||||
if int64(out.Amount)%int64(math.Pow10(8-int(assetState.Precision))) != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (bc *Blockchain) verifyResults(t *transaction.Transaction) bool {
|
||||
results := bc.GetTransationResults(t)
|
||||
if results == nil {
|
||||
return false
|
||||
}
|
||||
var resultsDestroy []*transaction.Result
|
||||
var resultsIssue []*transaction.Result
|
||||
for _, re := range results {
|
||||
if re.Amount.GreaterThan(util.Fixed8(0)) {
|
||||
resultsDestroy = append(resultsDestroy, re)
|
||||
}
|
||||
|
||||
if re.Amount.LessThan(util.Fixed8(0)) {
|
||||
resultsIssue = append(resultsIssue, re)
|
||||
}
|
||||
}
|
||||
if len(resultsDestroy) > 1 {
|
||||
return false
|
||||
}
|
||||
if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != utilityTokenTX().Hash() {
|
||||
return false
|
||||
}
|
||||
if bc.SystemFee(t).GreaterThan(util.Fixed8(0)) && (len(resultsDestroy) == 0 || resultsDestroy[0].Amount.LessThan(bc.SystemFee(t))) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch t.Type {
|
||||
case transaction.MinerType, transaction.ClaimType:
|
||||
for _, r := range resultsIssue {
|
||||
if r.AssetID != utilityTokenTX().Hash() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
break
|
||||
case transaction.IssueType:
|
||||
for _, r := range resultsIssue {
|
||||
if r.AssetID == utilityTokenTX().Hash() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
break
|
||||
default:
|
||||
if len(resultsIssue) > 0 {
|
||||
return false
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetTransationResults returns the transaction results aggregate by assetID.
|
||||
// Golang of GetTransationResults method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L207)
|
||||
func (bc *Blockchain) GetTransationResults(t *transaction.Transaction) []*transaction.Result {
|
||||
var tempResults []*transaction.Result
|
||||
var results []*transaction.Result
|
||||
tempGroupResult := make(map[util.Uint256]util.Fixed8)
|
||||
|
||||
if references := bc.References(t); references == nil {
|
||||
return nil
|
||||
} else {
|
||||
for _, output := range references {
|
||||
tempResults = append(tempResults, &transaction.Result{
|
||||
AssetID: output.AssetID,
|
||||
Amount: output.Amount,
|
||||
})
|
||||
}
|
||||
for _, output := range t.Outputs {
|
||||
tempResults = append(tempResults, &transaction.Result{
|
||||
AssetID: output.AssetID,
|
||||
Amount: -output.Amount,
|
||||
})
|
||||
}
|
||||
for _, r := range tempResults {
|
||||
if amount, ok := tempGroupResult[r.AssetID]; ok {
|
||||
tempGroupResult[r.AssetID] = amount.Add(r.Amount)
|
||||
} else {
|
||||
tempGroupResult[r.AssetID] = r.Amount
|
||||
}
|
||||
}
|
||||
|
||||
results = []*transaction.Result{} // this assignment is necessary. (Most of the time amount == 0 and results is the empty slice.)
|
||||
for assetID, amount := range tempGroupResult {
|
||||
if amount != util.Fixed8(0) {
|
||||
results = append(results, &transaction.Result{
|
||||
AssetID: assetID,
|
||||
Amount: amount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// GetScriptHashesForVerifying returns all the ScriptHashes of a transaction which will be use
|
||||
// to verify whether the transaction is bonafide or not.
|
||||
// Golang implementation of GetScriptHashesForVerifying method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L190)
|
||||
func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([]util.Uint160, error) {
|
||||
references := bc.References(t)
|
||||
if references == nil {
|
||||
return nil, errors.New("Invalid operation")
|
||||
}
|
||||
hashes := make(map[util.Uint160]bool)
|
||||
for _, i := range t.Inputs {
|
||||
h := references[i.PrevHash].ScriptHash
|
||||
if _, ok := hashes[h]; !ok {
|
||||
hashes[h] = true
|
||||
}
|
||||
}
|
||||
for _, a := range t.Attributes {
|
||||
if a.Usage == transaction.Script {
|
||||
h, err := util.Uint160DecodeBytes(a.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := hashes[h]; !ok {
|
||||
hashes[h] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for a, outputs := range t.GroupOutputByAssetID() {
|
||||
as := bc.GetAssetState(a)
|
||||
if as == nil {
|
||||
return nil, errors.New("Invalid operation")
|
||||
}
|
||||
if as.AssetType == transaction.DutyFlag {
|
||||
for _, o := range outputs {
|
||||
h := o.ScriptHash
|
||||
if _, ok := hashes[h]; !ok {
|
||||
hashes[h] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// convert hashes to []util.Uint160
|
||||
hashesResult := make([]util.Uint160, 0, len(hashes))
|
||||
for h := range hashes {
|
||||
hashesResult = append(hashesResult, h)
|
||||
}
|
||||
|
||||
return hashesResult, nil
|
||||
|
||||
}
|
||||
|
||||
// VerifyWitnesses verify the scripts (witnesses) that come with a transactions.
|
||||
// Golang implementation of VerifyWitnesses method in C# (https://github.com/neo-project/neo/blob/master/neo/SmartContract/Helper.cs#L87).
|
||||
// Unfortunately the IVerifiable interface could not be implemented because we can't move the References method in blockchain.go to the transaction.go file
|
||||
func (bc *Blockchain) VerifyWitnesses(t *transaction.Transaction) error {
|
||||
hashes, err := bc.GetScriptHashesForVerifying(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
witnesses := t.Scripts
|
||||
if len(hashes) != len(witnesses) {
|
||||
return errors.Errorf("expected len(hashes) == len(witnesses). got: %d != %d", len(hashes), len(witnesses))
|
||||
}
|
||||
for i := 0; i < len(hashes); i++ {
|
||||
verification := witnesses[i].VerificationScript
|
||||
|
||||
if len(verification) == 0 {
|
||||
/*TODO: replicate following C# code:
|
||||
using (ScriptBuilder sb = new ScriptBuilder())
|
||||
{
|
||||
sb.EmitAppCall(hashes[i].ToArray());
|
||||
verification = sb.ToArray();
|
||||
}
|
||||
*/
|
||||
|
||||
} else {
|
||||
if h, err := witnesses[i].ScriptHash(); err != nil || hashes[i] != h {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
/*TODO: replicate following C# code:
|
||||
using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, Fixed8.Zero))
|
||||
{
|
||||
engine.LoadScript(verification);
|
||||
engine.LoadScript(verifiable.Witnesses[i].InvocationScript);
|
||||
if (!engine.Execute()) return false;
|
||||
if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) return false;
|
||||
}*/
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hashAndIndexToBytes(h util.Uint256, index uint32) []byte {
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, index)
|
||||
return append(h.BytesReverse(), buf...)
|
||||
}
|
||||
|
||||
func (bc *Blockchain) secondsPerBlock() int {
|
||||
return bc.config.SecondsPerBlock
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ type Blockchainer interface {
|
|||
GetAccountState(util.Uint160) *AccountState
|
||||
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||
References(t *transaction.Transaction) map[util.Uint256]*transaction.Output
|
||||
FeePerByte(t *transaction.Transaction) util.Fixed8
|
||||
SystemFee(t *transaction.Transaction) util.Fixed8
|
||||
NetworkFee(t *transaction.Transaction) util.Fixed8
|
||||
Feer // fee interface
|
||||
Verify(t *transaction.Transaction) error
|
||||
GetMemPool() MemPool
|
||||
}
|
||||
|
|
14
pkg/core/feer.go
Normal file
14
pkg/core/feer.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/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(t *transaction.Transaction) bool
|
||||
FeePerByte(t *transaction.Transaction) util.Fixed8
|
||||
SystemFee(t *transaction.Transaction) util.Fixed8
|
||||
}
|
267
pkg/core/mem_pool.go
Normal file
267
pkg/core/mem_pool.go
Normal file
|
@ -0,0 +1,267 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// PoolItem represents a transaction in the the Memory pool.
|
||||
type PoolItem struct {
|
||||
txn *transaction.Transaction
|
||||
timeStamp time.Time
|
||||
fee Feer
|
||||
}
|
||||
|
||||
// PoolItems slice of PoolItem
|
||||
type PoolItems []*PoolItem
|
||||
|
||||
// MemPool stores the unconfirms transactions.
|
||||
type MemPool struct {
|
||||
lock *sync.RWMutex
|
||||
unsortedTxn map[util.Uint256]*PoolItem
|
||||
unverifiedTxn map[util.Uint256]*PoolItem
|
||||
sortedHighPrioTxn PoolItems
|
||||
sortedLowPrioTxn PoolItems
|
||||
unverifiedSortedHighPrioTxn PoolItems
|
||||
unverifiedSortedLowPrioTxn PoolItems
|
||||
|
||||
capacity int
|
||||
}
|
||||
|
||||
func (p PoolItems) Len() int { return len(p) }
|
||||
func (p PoolItems) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p PoolItems) Less(i, j int) bool { return p[i].CompareTo(p[j]) < 0 }
|
||||
|
||||
// CompareTo returns the difference between two PoolItems.
|
||||
// difference < 0 implies p < otherP.
|
||||
// difference = 0 implies p = otherP.
|
||||
// difference > 0 implies p > otherP.
|
||||
func (p PoolItem) CompareTo(otherP *PoolItem) int {
|
||||
if otherP == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if p.fee.IsLowPriority(p.txn) && p.fee.IsLowPriority(otherP.txn) {
|
||||
thisIsClaimTx := p.txn.Type == transaction.ClaimType
|
||||
otherIsClaimTx := otherP.txn.Type == transaction.ClaimType
|
||||
|
||||
if thisIsClaimTx != otherIsClaimTx {
|
||||
// This is a claim Tx and other isn't.
|
||||
if thisIsClaimTx {
|
||||
return 1
|
||||
}
|
||||
// The other is claim Tx and this isn't.
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Fees sorted ascending
|
||||
pFPB := p.fee.FeePerByte(p.txn)
|
||||
otherFPB := p.fee.FeePerByte(otherP.txn)
|
||||
if ret := pFPB.CompareTo(otherFPB); ret != 0 {
|
||||
return ret
|
||||
}
|
||||
|
||||
pNF := p.fee.NetworkFee(p.txn)
|
||||
otherNF := p.fee.NetworkFee(otherP.txn)
|
||||
if ret := pNF.CompareTo(otherNF); ret != 0 {
|
||||
return ret
|
||||
}
|
||||
|
||||
// Transaction hash sorted descending
|
||||
return otherP.txn.Hash().CompareTo(p.txn.Hash())
|
||||
}
|
||||
|
||||
// Count returns the total number of uncofirm transactions.
|
||||
func (mp MemPool) Count() int {
|
||||
mp.lock.RLock()
|
||||
defer mp.lock.RUnlock()
|
||||
|
||||
return len(mp.unsortedTxn) + len(mp.unverifiedTxn)
|
||||
}
|
||||
|
||||
// ContainsKey checks if a transactions hash is in the MemPool.
|
||||
func (mp MemPool) ContainsKey(hash util.Uint256) bool {
|
||||
mp.lock.RLock()
|
||||
defer mp.lock.RUnlock()
|
||||
|
||||
if _, ok := mp.unsortedTxn[hash]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := mp.unverifiedTxn[hash]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// TryAdd try to add the PoolItem to the MemPool.
|
||||
func (mp MemPool) TryAdd(hash util.Uint256, pItem *PoolItem) bool {
|
||||
var pool PoolItems
|
||||
|
||||
mp.lock.RLock()
|
||||
if _, ok := mp.unsortedTxn[hash]; ok {
|
||||
return false
|
||||
}
|
||||
mp.unsortedTxn[hash] = pItem
|
||||
mp.lock.RUnlock()
|
||||
|
||||
if pItem.fee.IsLowPriority(pItem.txn) {
|
||||
pool = mp.sortedLowPrioTxn
|
||||
} else {
|
||||
pool = mp.sortedHighPrioTxn
|
||||
}
|
||||
|
||||
mp.lock.Lock()
|
||||
pool = append(pool, pItem)
|
||||
sort.Sort(pool)
|
||||
mp.lock.Unlock()
|
||||
|
||||
if mp.Count() > mp.capacity {
|
||||
(&mp).RemoveOverCapacity()
|
||||
}
|
||||
mp.lock.RLock()
|
||||
_, ok := mp.unsortedTxn[hash]
|
||||
mp.lock.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// RemoveOverCapacity removes transactions with lowest fees until the total number of transactions
|
||||
// in the MemPool is within the capacity of the MemPool.
|
||||
func (mp *MemPool) RemoveOverCapacity() {
|
||||
for mp.Count()-mp.capacity > 0 {
|
||||
mp.lock.Lock()
|
||||
if minItem, argPosition := getLowestFeeTransaction(mp.sortedLowPrioTxn, mp.unverifiedSortedLowPrioTxn); minItem != nil {
|
||||
if argPosition == 1 {
|
||||
// minItem belongs to the mp.sortedLowPrioTxn slice.
|
||||
// The corresponding unsorted pool is is mp.unsortedTxn.
|
||||
delete(mp.unsortedTxn, minItem.txn.Hash())
|
||||
mp.sortedLowPrioTxn = append(mp.sortedLowPrioTxn[:0], mp.sortedLowPrioTxn[1:]...)
|
||||
} else {
|
||||
// minItem belongs to the mp.unverifiedSortedLowPrioTxn slice.
|
||||
// The corresponding unsorted pool is is mp.unverifiedTxn.
|
||||
delete(mp.unverifiedTxn, minItem.txn.Hash())
|
||||
mp.unverifiedSortedLowPrioTxn = append(mp.unverifiedSortedLowPrioTxn[:0], mp.unverifiedSortedLowPrioTxn[1:]...)
|
||||
|
||||
}
|
||||
} else if minItem, argPosition := getLowestFeeTransaction(mp.sortedHighPrioTxn, mp.unverifiedSortedHighPrioTxn); minItem != nil {
|
||||
if argPosition == 1 {
|
||||
// minItem belongs to the mp.sortedHighPrioTxn slice.
|
||||
// The corresponding unsorted pool is is mp.unsortedTxn.
|
||||
delete(mp.unsortedTxn, minItem.txn.Hash())
|
||||
mp.sortedHighPrioTxn = append(mp.sortedHighPrioTxn[:0], mp.sortedHighPrioTxn[1:]...)
|
||||
} else {
|
||||
// minItem belongs to the mp.unverifiedSortedHighPrioTxn slice.
|
||||
// The corresponding unsorted pool is is mp.unverifiedTxn.
|
||||
delete(mp.unverifiedTxn, minItem.txn.Hash())
|
||||
mp.unverifiedSortedHighPrioTxn = append(mp.unverifiedSortedHighPrioTxn[:0], mp.unverifiedSortedHighPrioTxn[1:]...)
|
||||
|
||||
}
|
||||
}
|
||||
mp.lock.Unlock()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NewPoolItem returns a new PoolItem.
|
||||
func NewPoolItem(t *transaction.Transaction, fee Feer) *PoolItem {
|
||||
return &PoolItem{
|
||||
txn: t,
|
||||
timeStamp: time.Now().UTC(),
|
||||
fee: fee,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMemPool returns a new MemPool struct.
|
||||
func NewMemPool(capacity int) MemPool {
|
||||
return MemPool{
|
||||
lock: new(sync.RWMutex),
|
||||
unsortedTxn: make(map[util.Uint256]*PoolItem),
|
||||
unverifiedTxn: make(map[util.Uint256]*PoolItem),
|
||||
capacity: capacity,
|
||||
}
|
||||
}
|
||||
|
||||
// TryGetValue returns a transactions if it esists in the memory pool.
|
||||
func (mp MemPool) TryGetValue(hash util.Uint256) (*transaction.Transaction, bool) {
|
||||
mp.lock.Lock()
|
||||
defer mp.lock.Unlock()
|
||||
if pItem, ok := mp.unsortedTxn[hash]; ok {
|
||||
return pItem.txn, ok
|
||||
}
|
||||
|
||||
if pItem, ok := mp.unverifiedTxn[hash]; ok {
|
||||
return pItem.txn, ok
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// getLowestFeeTransaction returns the PoolItem with the lowest fee amongst the "verifiedTxnSorted"
|
||||
// and "unverifiedTxnSorted" PoolItems along with a integer. The integer can assume two values, 1 and 2 which indicate
|
||||
// that the PoolItem with the lowest fee was found in "verifiedTxnSorted" respectively in "unverifiedTxnSorted".
|
||||
// "verifiedTxnSorted" and "unverifiedTxnSorted" are sorted slice order by transaction fee ascending. This means that
|
||||
// the transaction with lowest fee start at index 0.
|
||||
// Reference: GetLowestFeeTransaction method in C# (https://github.com/neo-project/neo/blob/master/neo/Ledger/MemoryPool.cs)
|
||||
func getLowestFeeTransaction(verifiedTxnSorted PoolItems, unverifiedTxnSorted PoolItems) (*PoolItem, int) {
|
||||
minItem := min(unverifiedTxnSorted)
|
||||
verifiedMin := min(verifiedTxnSorted)
|
||||
if verifiedMin == nil || (minItem != nil && verifiedMin.CompareTo(minItem) >= 0) {
|
||||
return minItem, 2
|
||||
}
|
||||
|
||||
minItem = verifiedMin
|
||||
return minItem, 1
|
||||
|
||||
}
|
||||
|
||||
// min return the minimum item in a ascending sorted slice of pool items.
|
||||
// The function can't be applied to unsorted slice!
|
||||
func min(sortedPool PoolItems) *PoolItem {
|
||||
if len(sortedPool) == 0 {
|
||||
return nil
|
||||
}
|
||||
return sortedPool[0]
|
||||
}
|
||||
|
||||
// GetVerifiedTransactions returns a slice of Input from all the transactions in the memory pool
|
||||
// whose hash is not included in excludedHashes.
|
||||
func (mp *MemPool) GetVerifiedTransactions() []*transaction.Transaction {
|
||||
var t []*transaction.Transaction
|
||||
|
||||
mp.lock.Lock()
|
||||
defer mp.lock.Unlock()
|
||||
for _, p := range mp.unsortedTxn {
|
||||
t = append(t, p.txn)
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// 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 MemPool) Verify(tx *transaction.Transaction) bool {
|
||||
count := 0
|
||||
inputs := make([]*transaction.Input, 0)
|
||||
for _, item := range mp.GetVerifiedTransactions() {
|
||||
if tx.Hash().Equals(item.Hash()) {
|
||||
inputs = append(inputs, item.Inputs...)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(inputs); i++ {
|
||||
for j := 0; j < len(tx.Inputs); j++ {
|
||||
if inputs[i].PrevHash.Equals(tx.Inputs[j].PrevHash) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count == 0
|
||||
}
|
|
@ -36,6 +36,6 @@ func (in *Input) EncodeBinary(w io.Writer) error {
|
|||
}
|
||||
|
||||
// Size returns the size in bytes of the Input
|
||||
func (in *Input) Size() int {
|
||||
func (in Input) Size() int {
|
||||
return in.PrevHash.Size() + 2 // 2 = sizeOf uint16
|
||||
}
|
||||
|
|
9
pkg/core/transaction/result.go
Normal file
9
pkg/core/transaction/result.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package transaction
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/util"
|
||||
|
||||
// Result represents the Result of a transaction.
|
||||
type Result struct {
|
||||
AssetID util.Uint256
|
||||
Amount util.Fixed8
|
||||
}
|
|
@ -10,6 +10,12 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxTransactionSize is the upper limit size in bytes that a transaction can reach. It is
|
||||
// set to be 102400.
|
||||
MaxTransactionSize = 102400
|
||||
)
|
||||
|
||||
// Transaction is a process recorded in the NEO blockchain.
|
||||
type Transaction struct {
|
||||
// The type of the transaction.
|
||||
|
@ -251,6 +257,15 @@ func (t *Transaction) GroupInputsByPrevHash() map[util.Uint256][]*Input {
|
|||
return m
|
||||
}
|
||||
|
||||
// GroupOutputByAssetID groups all TX outputs by their assetID.
|
||||
func (t Transaction) GroupOutputByAssetID() map[util.Uint256][]*Output {
|
||||
m := make(map[util.Uint256][]*Output)
|
||||
for _, out := range t.Outputs {
|
||||
m[out.AssetID] = append(m[out.AssetID], out)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Size returns the size of the transaction in term of bytes
|
||||
func (t *Transaction) Size() int {
|
||||
attrSize := util.GetVarSize(t.Attributes)
|
||||
|
|
|
@ -55,3 +55,8 @@ func (w *Witness) MarshalJSON() ([]byte, error) {
|
|||
func (w *Witness) Size() int {
|
||||
return util.GetVarSize(w.InvocationScript) + util.GetVarSize(w.VerificationScript)
|
||||
}
|
||||
|
||||
// ScriptHash returns the hash of the VerificationScript.
|
||||
func (w Witness) ScriptHash() (util.Uint160, error) {
|
||||
return util.Uint160FromScript(w.VerificationScript)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
|
@ -91,3 +92,32 @@ func (s *UnspentCoinState) DecodeBinary(r io.Reader) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsDoubleSpend verifies that the input transactions are not double spent.
|
||||
func IsDoubleSpend(s storage.Store, tx *transaction.Transaction) bool {
|
||||
if len(tx.Inputs) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for prevHash, inputs := range tx.GroupInputsByPrevHash() {
|
||||
unspent := &UnspentCoinState{}
|
||||
key := storage.AppendPrefix(storage.STCoin, prevHash.BytesReverse())
|
||||
if b, err := s.Get(key); err == nil {
|
||||
if err := unspent.DecodeBinary(bytes.NewReader(b)); err != nil {
|
||||
return false
|
||||
}
|
||||
if unspent == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, input := range inputs {
|
||||
if int(input.PrevIndex) >= len(unspent.states) || unspent.states[input.PrevIndex] == CoinStateSpent {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -77,6 +77,18 @@ func (chain testChain) GetTransaction(util.Uint256) (*transaction.Transaction, u
|
|||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) GetMemPool() core.MemPool {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) IsLowPriority(*transaction.Transaction) bool {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (chain testChain) Verify(*transaction.Transaction) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
type testDiscovery struct{}
|
||||
|
||||
func (d testDiscovery) BackFill(addrs ...string) {}
|
||||
|
|
15
pkg/network/relay_reason.go
Normal file
15
pkg/network/relay_reason.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package network
|
||||
|
||||
// RelayReason is the type which describes the different relay outcome
|
||||
type RelayReason uint8
|
||||
|
||||
// List of valid RelayReason.
|
||||
const (
|
||||
RelaySucceed RelayReason = iota
|
||||
RelayAlreadyExists
|
||||
RelayOutOfMemory
|
||||
RelayUnableToVerify
|
||||
RelayInvalid
|
||||
RelayPolicyFail
|
||||
RelayUnknown
|
||||
)
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/network/payload"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -310,3 +311,41 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RelayTxn a new transaction to the local node and the connected peers.
|
||||
// Reference: the method OnRelay in C#: https://github.com/neo-project/neo/blob/master/neo/Network/P2P/LocalNode.cs#L159
|
||||
func (s *Server) RelayTxn(t *transaction.Transaction) RelayReason {
|
||||
if t.Type == transaction.MinerType {
|
||||
return RelayInvalid
|
||||
}
|
||||
if s.chain.HasTransaction(t.Hash()) {
|
||||
return RelayAlreadyExists
|
||||
}
|
||||
if err := s.chain.Verify(t); err != nil {
|
||||
return RelayInvalid
|
||||
}
|
||||
// TODO: Implement Plugin.CheckPolicy?
|
||||
//if (!Plugin.CheckPolicy(transaction))
|
||||
// return RelayResultReason.PolicyFail;
|
||||
if ok := s.chain.GetMemPool().TryAdd(t.Hash(), core.NewPoolItem(t, s.chain)); !ok {
|
||||
return RelayOutOfMemory
|
||||
}
|
||||
|
||||
for p := range s.Peers() {
|
||||
payload := payload.NewInventory(payload.TXType, []util.Uint256{t.Hash()})
|
||||
s.RelayDirectly(p, payload)
|
||||
}
|
||||
|
||||
return RelaySucceed
|
||||
}
|
||||
|
||||
// RelayDirectly relay directly the inventory to the remote peers.
|
||||
// Reference: the method OnRelayDirectly in C#: https://github.com/neo-project/neo/blob/master/neo/Network/P2P/LocalNode.cs#L166
|
||||
func (s *Server) RelayDirectly(p Peer, inv *payload.Inventory) {
|
||||
if !p.Version().Relay {
|
||||
return
|
||||
}
|
||||
|
||||
p.WriteMsg(NewMessage(s.Net, CMDInv, inv))
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
@ -8,6 +9,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||
"github.com/CityOfZion/neo-go/pkg/network"
|
||||
"github.com/CityOfZion/neo-go/pkg/rpc/result"
|
||||
|
@ -183,7 +185,7 @@ Methods:
|
|||
|
||||
results = peers
|
||||
|
||||
case "getblocksysfee", "getcontractstate", "getrawmempool", "getstorage", "submitblock", "gettxout", "invoke", "invokefunction", "invokescript", "sendrawtransaction":
|
||||
case "getblocksysfee", "getcontractstate", "getrawmempool", "getstorage", "submitblock", "gettxout", "invoke", "invokefunction", "invokescript":
|
||||
|
||||
results = "TODO"
|
||||
|
||||
|
@ -227,37 +229,10 @@ Methods:
|
|||
results = "Invalid public account address"
|
||||
}
|
||||
case "getrawtransaction":
|
||||
param0, err := reqParams.ValueWithType(0, "string")
|
||||
if err != nil {
|
||||
resultsErr = err
|
||||
} else if txHash, err := util.Uint256DecodeString(param0.StringVal); err != nil {
|
||||
err = errors.Wrapf(err, "param at index 0, (%s), could not be decode to Uint256", param0.StringVal)
|
||||
resultsErr = NewInvalidParamsError(err.Error(), err)
|
||||
} else if tx, height, err := s.chain.GetTransaction(txHash); err != nil {
|
||||
err = errors.Wrapf(err, "Invalid transaction hash: %s", txHash)
|
||||
resultsErr = NewInvalidParamsError(err.Error(), err)
|
||||
} else if len(reqParams) >= 2 {
|
||||
_header := s.chain.GetHeaderHash(int(height))
|
||||
header, err := s.chain.GetHeader(_header)
|
||||
if err != nil {
|
||||
resultsErr = NewInvalidParamsError(err.Error(), err)
|
||||
}
|
||||
results, resultsErr = s.getrawtransaction(reqParams)
|
||||
|
||||
param1, _ := reqParams.ValueAt(1)
|
||||
switch v := param1.RawValue.(type) {
|
||||
|
||||
case int, float64, bool, string:
|
||||
if v == 0 || v == "0" || v == 0.0 || v == false || v == "false" {
|
||||
results = hex.EncodeToString(tx.Bytes())
|
||||
} else {
|
||||
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain)
|
||||
}
|
||||
default:
|
||||
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain)
|
||||
}
|
||||
} else {
|
||||
results = hex.EncodeToString(tx.Bytes())
|
||||
}
|
||||
case "sendrawtransaction":
|
||||
results, resultsErr = s.sendrawtransaction(reqParams)
|
||||
|
||||
default:
|
||||
resultsErr = NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil)
|
||||
|
@ -271,6 +246,86 @@ Methods:
|
|||
req.WriteResponse(w, results)
|
||||
}
|
||||
|
||||
func (s *Server) getrawtransaction(reqParams Params) (interface{}, error) {
|
||||
var resultsErr error
|
||||
var results interface{}
|
||||
|
||||
param0, err := reqParams.ValueWithType(0, "string")
|
||||
if err != nil {
|
||||
resultsErr = err
|
||||
} else if txHash, err := util.Uint256DecodeString(param0.StringVal); err != nil {
|
||||
resultsErr = errInvalidParams
|
||||
} else if tx, height, err := s.chain.GetTransaction(txHash); err != nil {
|
||||
err = errors.Wrapf(err, "Invalid transaction hash: %s", txHash)
|
||||
resultsErr = NewInvalidParamsError(err.Error(), err)
|
||||
} else if len(reqParams) >= 2 {
|
||||
_header := s.chain.GetHeaderHash(int(height))
|
||||
header, err := s.chain.GetHeader(_header)
|
||||
if err != nil {
|
||||
resultsErr = NewInvalidParamsError(err.Error(), err)
|
||||
}
|
||||
|
||||
param1, _ := reqParams.ValueAt(1)
|
||||
switch v := param1.RawValue.(type) {
|
||||
|
||||
case int, float64, bool, string:
|
||||
if v == 0 || v == "0" || v == 0.0 || v == false || v == "false" {
|
||||
results = hex.EncodeToString(tx.Bytes())
|
||||
} else {
|
||||
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain)
|
||||
}
|
||||
default:
|
||||
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain)
|
||||
}
|
||||
} else {
|
||||
results = hex.EncodeToString(tx.Bytes())
|
||||
}
|
||||
|
||||
return results, resultsErr
|
||||
}
|
||||
|
||||
func (s *Server) sendrawtransaction(reqParams Params) (interface{}, error) {
|
||||
var resultsErr error
|
||||
var results interface{}
|
||||
|
||||
param, err := reqParams.ValueWithType(0, "string")
|
||||
if err != nil {
|
||||
resultsErr = err
|
||||
} else if byteTx, err := hex.DecodeString(param.StringVal); err != nil {
|
||||
resultsErr = errInvalidParams
|
||||
} else {
|
||||
r := bytes.NewReader(byteTx)
|
||||
tx := &transaction.Transaction{}
|
||||
err = tx.DecodeBinary(r)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "transaction DecodeBinary failed")
|
||||
}
|
||||
relayReason := s.coreServer.RelayTxn(tx)
|
||||
switch relayReason {
|
||||
case network.RelaySucceed:
|
||||
results = true
|
||||
case network.RelayAlreadyExists:
|
||||
err = errors.New("block or transaction already exists and cannot be sent repeatedly")
|
||||
case network.RelayOutOfMemory:
|
||||
err = errors.New("the memory pool is full and no more transactions can be sent")
|
||||
case network.RelayUnableToVerify:
|
||||
err = errors.New("the block cannot be validated")
|
||||
case network.RelayInvalid:
|
||||
err = errors.New("block or transaction validation failed")
|
||||
case network.RelayPolicyFail:
|
||||
err = errors.New("one of the Policy filters failed")
|
||||
default:
|
||||
err = errors.New("unknown error")
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
resultsErr = NewInternalServerError(err.Error(), err)
|
||||
}
|
||||
}
|
||||
|
||||
return results, resultsErr
|
||||
}
|
||||
|
||||
func (s Server) validBlockHeight(param *Param) bool {
|
||||
return param.IntVal >= 0 && param.IntVal <= int(s.chain.BlockHeight())
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ var testRpcCases = []tc{
|
|||
{
|
||||
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["45a41306c846ea80290416143e8e856559818065be3f4e143c60e43a", 1] }`,
|
||||
method: "getrawtransaction_4",
|
||||
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params","data":"param at index 0, (45a41306c846ea80290416143e8e856559818065be3f4e143c60e43a), could not be decode to Uint256: expected string size of 64 got 56"},"id":1}`,
|
||||
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
|
||||
},
|
||||
|
||||
// Good case, valid transaction
|
||||
|
@ -216,6 +216,27 @@ var testRpcCases = []tc{
|
|||
method: "validateaddress_4",
|
||||
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
|
||||
},
|
||||
|
||||
// Good case
|
||||
{
|
||||
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "sendrawtransaction", "params": ["80000190274d792072617720636f6e7472616374207472616e73616374696f6e206465736372697074696f6e01949354ea0a8b57dfee1e257a1aedd1e0eea2e5837de145e8da9c0f101bfccc8e0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500a3e11100000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5004f2418010000001cc9c05cefffe6cdd7b182816a9152ec218d2ec0014140dbd3cddac5cb2bd9bf6d93701f1a6f1c9dbe2d1b480c54628bbb2a4d536158c747a6af82698edf9f8af1cac3850bcb772bd9c8e4ac38f80704751cc4e0bd0e67232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"] }`,
|
||||
method: "sendrawtransaction_1",
|
||||
expectedResult: `{"jsonrpc":"2.0","result":true,"id":1}`,
|
||||
},
|
||||
|
||||
/* Good case: TODO: uncomment this test case once https://github.com/CityOfZion/neo-go/issues/173 is fixed!
|
||||
{
|
||||
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "sendrawtransaction", "params": ["d1001b00046e616d6567d3d8602814a429a91afdbaa3914884a1c90c733101201cc9c05cefffe6cdd7b182816a9152ec218d2ec000000141403387ef7940a5764259621e655b3c621a6aafd869a611ad64adcc364d8dd1edf84e00a7f8b11b630a377eaef02791d1c289d711c08b7ad04ff0d6c9caca22cfe6232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"] }`,
|
||||
method: "sendrawtransaction_2",
|
||||
expectedResult: `{"jsonrpc":"2.0","result":true,"id":1}`,
|
||||
},*/
|
||||
|
||||
// Bad case, incorrect raw transaction
|
||||
{
|
||||
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "sendrawtransaction", "params": ["0274d792072617720636f6e7472616374207472616e73616374696f6e206465736372697074696f6e01949354ea0a8b57dfee1e257a1aedd1e0eea2e5837de145e8da9c0f101bfccc8e0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500a3e11100000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5004f2418010000001cc9c05cefffe6cdd7b182816a9152ec218d2ec0014140dbd3cddac5cb2bd9bf6d93701f1a6f1c9dbe2d1b480c54628bbb2a4d536158c747a6af82698edf9f8af1cac3850bcb772bd9c8e4ac38f80704751cc4e0bd0e67232103cbb45da6072c14761c9da545749d9cfd863f860c351066d16df480602a2024c6ac"] }`,
|
||||
method: "sendrawtransaction_1",
|
||||
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
|
|
|
@ -128,3 +128,26 @@ func (f Fixed8) Add(g Fixed8) Fixed8 {
|
|||
func (f Fixed8) Sub(g Fixed8) Fixed8 {
|
||||
return NewFixed8(f.Value() - g.Value())
|
||||
}
|
||||
|
||||
// LessThan implements Fixd8 < operator.
|
||||
func (f Fixed8) LessThan(g Fixed8) bool {
|
||||
return f.Value() < g.Value()
|
||||
}
|
||||
|
||||
// GreaterThan implements Fixd8 < operator.
|
||||
func (f Fixed8) GreaterThan(g Fixed8) bool {
|
||||
return f.Value() > g.Value()
|
||||
}
|
||||
|
||||
// Equal implements Fixd8 == operator.
|
||||
func (f Fixed8) Equal(g Fixed8) bool {
|
||||
return f.Value() == g.Value()
|
||||
}
|
||||
|
||||
// CompareTo returns the difference between the f and g.
|
||||
// difference < 0 implies f < g.
|
||||
// difference = 0 implies f = g.
|
||||
// difference > 0 implies f > g.
|
||||
func (f Fixed8) CompareTo(g Fixed8) int {
|
||||
return int(f.Value() - g.Value())
|
||||
}
|
||||
|
|
|
@ -65,20 +65,22 @@ func GetVarSize(value interface{}) int {
|
|||
valueLength := v.Len()
|
||||
valueSize := 0
|
||||
|
||||
switch reflect.ValueOf(value).Index(0).Interface().(type) {
|
||||
case io.Serializable:
|
||||
for i := 0; i < valueLength; i++ {
|
||||
elem := v.Index(i).Interface().(io.Serializable)
|
||||
valueSize += elem.Size()
|
||||
if valueLength != 0 {
|
||||
switch reflect.ValueOf(value).Index(0).Interface().(type) {
|
||||
case io.Serializable:
|
||||
for i := 0; i < valueLength; i++ {
|
||||
elem := v.Index(i).Interface().(io.Serializable)
|
||||
valueSize += elem.Size()
|
||||
}
|
||||
case uint8, int8:
|
||||
valueSize = valueLength
|
||||
case uint16, int16:
|
||||
valueSize = valueLength * 2
|
||||
case uint32, int32:
|
||||
valueSize = valueLength * 4
|
||||
case uint64, int64:
|
||||
valueSize = valueLength * 8
|
||||
}
|
||||
case uint8, int8:
|
||||
valueSize = valueLength
|
||||
case uint16, int16:
|
||||
valueSize = valueLength * 2
|
||||
case uint32, int32:
|
||||
valueSize = valueLength * 4
|
||||
case uint64, int64:
|
||||
valueSize = valueLength * 8
|
||||
}
|
||||
|
||||
return GetVarIntSize(valueLength) + valueSize
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -74,3 +75,9 @@ func (u Uint256) Size() int {
|
|||
func (u Uint256) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"0x` + u.String() + `"`), nil
|
||||
}
|
||||
|
||||
// CompareTo compares two Uint256 with each other. Possible output: 1, -1, 0
|
||||
// 1 implies u > other.
|
||||
// -1 implies u < other.
|
||||
// 0 implies u = other.
|
||||
func (u Uint256) CompareTo(other Uint256) int { return bytes.Compare(u[:], other[:]) }
|
||||
|
|
Loading…
Reference in a new issue