Add NULS supported. (#135)

Add NULS supported.
utxostat
kiss1987f4 2019-03-28 21:31:29 +08:00 committed by Martin Boehm
parent fa4a11c3a7
commit 954619efe9
8 changed files with 1922 additions and 0 deletions

View File

@ -20,6 +20,7 @@ import (
"blockbook/bchain/coins/monacoin"
"blockbook/bchain/coins/myriad"
"blockbook/bchain/coins/namecoin"
"blockbook/bchain/coins/nuls"
"blockbook/bchain/coins/pivx"
"blockbook/bchain/coins/qtum"
"blockbook/bchain/coins/vertcoin"
@ -79,6 +80,7 @@ func init() {
BlockChainFactories["Bellcoin"] = bellcoin.NewBellcoinRPC
BlockChainFactories["Qtum"] = qtum.NewQtumRPC
BlockChainFactories["Qtum Testnet"] = qtum.NewQtumRPC
BlockChainFactories["NULS"] = nuls.NewNulsRPC
}
// GetCoinNameFromConfig gets coin name and coin shortcut from config file

View File

@ -0,0 +1,151 @@
package nuls
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"bytes"
"encoding/binary"
"encoding/json"
"github.com/bsm/go-vlq"
"github.com/martinboehm/btcutil/base58"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
)
const (
MainnetMagic wire.BitcoinNet = 0xbd6b0cbf
TestnetMagic wire.BitcoinNet = 0xffcae2ce
RegtestMagic wire.BitcoinNet = 0xdcb7c1fc
)
var (
MainNetParams chaincfg.Params
TestNetParams chaincfg.Params
RegtestParams chaincfg.Params
)
func init() {
MainNetParams = chaincfg.MainNetParams
MainNetParams.Net = MainnetMagic
// Address encoding magics
MainNetParams.PubKeyHashAddrID = []byte{38} // base58 prefix: G
MainNetParams.ScriptHashAddrID = []byte{10} // base58 prefix: W
TestNetParams = chaincfg.TestNet3Params
TestNetParams.Net = TestnetMagic
// Address encoding magics
TestNetParams.PubKeyHashAddrID = []byte{140} // base58 prefix: y
TestNetParams.ScriptHashAddrID = []byte{19} // base58 prefix: 8 or 9
RegtestParams = chaincfg.RegressionNetParams
RegtestParams.Net = RegtestMagic
// Address encoding magics
RegtestParams.PubKeyHashAddrID = []byte{140} // base58 prefix: y
RegtestParams.ScriptHashAddrID = []byte{19} // base58 prefix: 8 or 9
}
// NulsParser handle
type NulsParser struct {
*btc.BitcoinParser
}
// NewNulsParser returns new NulsParser instance
func NewNulsParser(params *chaincfg.Params, c *btc.Configuration) *NulsParser {
return &NulsParser{BitcoinParser: btc.NewBitcoinParser(params, c)}
}
// GetChainParams contains network parameters for the main Gincoin network,
// the regression test Gincoin network, the test Gincoin network and
// the simulation test Gincoin network, in this order
func GetChainParams(chain string) *chaincfg.Params {
if !chaincfg.IsRegistered(&MainNetParams) {
err := chaincfg.Register(&MainNetParams)
if err == nil {
err = chaincfg.Register(&TestNetParams)
}
if err == nil {
err = chaincfg.Register(&RegtestParams)
}
if err != nil {
panic(err)
}
}
switch chain {
case "test":
return &TestNetParams
case "regtest":
return &RegtestParams
default:
return &MainNetParams
}
}
// PackedTxidLen returns length in bytes of packed txid
func (p *NulsParser) PackedTxidLen() int {
return 34
}
// GetAddrDescFromAddress returns internal address representation (descriptor) of given address
func (p *NulsParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) {
addressByte := base58.Decode(address)
return bchain.AddressDescriptor(addressByte), nil
}
// GetAddrDescFromVout returns internal address representation (descriptor) of given transaction output
func (p *NulsParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.AddressDescriptor, error) {
addressStr := output.ScriptPubKey.Hex
addressByte := base58.Decode(addressStr)
return bchain.AddressDescriptor(addressByte), nil
}
// GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable
func (p *NulsParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
var addrs []string
if addrDesc != nil {
addrs = append(addrs, base58.Encode(addrDesc))
}
return addrs, true, nil
}
// PackTx packs transaction to byte array
func (p *NulsParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
txBytes, error := json.Marshal(tx)
if error != nil {
return nil, error
}
buf := make([]byte, 4+vlq.MaxLen64)
binary.BigEndian.PutUint32(buf[0:4], height)
vlq.PutInt(buf[4:4+vlq.MaxLen64], blockTime)
resByes := bytes.Join([][]byte{buf, txBytes}, []byte(""))
return resByes, nil
}
// UnpackTx unpacks transaction from byte array
func (p *NulsParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
height := binary.BigEndian.Uint32(buf)
bt, _ := vlq.Int(buf[4 : 4+vlq.MaxLen64])
tx, err := p.ParseTx(buf[4+vlq.MaxLen64:])
if err != nil {
return nil, 0, err
}
tx.Blocktime = bt
return tx, height, nil
}
func (p *NulsParser) ParseTx(b []byte) (*bchain.Tx, error) {
tx := bchain.Tx{}
err := json.Unmarshal(b, &tx)
if err != nil {
return nil, err
}
return &tx, err
}

View File

@ -0,0 +1,343 @@
package nuls
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"encoding/json"
"math/big"
"reflect"
"testing"
)
var (
testTx1, testTx2 bchain.Tx
testTxPacked1 = "0001e240daadfbe7931e000000007b22686578223a22222c2274786964223a223030323036626231323431303861356664393865383238623138316666303162393237633063366234373764343531326266656638346366353266306663326136613161222c2276657273696f6e223a312c226c6f636b74696d65223a302c2276696e223a5b7b22636f696e62617365223a22222c2274786964223a223030323035343537616230373033623164313034363264343334373034386330626233353634653430663537616531663632353136393539343364653161633831306130222c22766f7574223a302c22736372697074536967223a7b22686578223a22227d2c2273657175656e6365223a302c22616464726573736573223a5b224e736535334d77524c424a31575555365365644d485141466643507442377734225d7d5d2c22766f7574223a5b7b2256616c7565536174223a3339393939393030303030302c2276616c7565223a302c226e223a302c227363726970745075624b6579223a7b22686578223a224e7365347a705a4873557555376835796d7632387063476277486a75336a6f56222c22616464726573736573223a5b224e7365347a705a4873557555376835796d7632387063476277486a75336a6f56225d7d7d5d2c22626c6f636b74696d65223a313535323335373834343137357d"
testTxPacked2 = "0007c91adaadfbb89946000000007b22686578223a22222c2274786964223a223030323037386139386633383163373134613036386436303565346265316565323139353438353736313165303938616262636333663530633536383066386164326535222c2276657273696f6e223a312c226c6f636b74696d65223a302c2276696e223a5b7b22636f696e62617365223a22222c2274786964223a223030323037613430646334623661633430376434396133633333616137353462623466303565343565353763323438313162313437653762663363616630363361383233222c22766f7574223a312c22736372697074536967223a7b22686578223a22227d2c2273657175656e6365223a302c22616464726573736573223a5b224e73653131397a326f53444a596b466b786d775944695974506642654e6b7169225d7d5d2c22766f7574223a5b7b2256616c7565536174223a3430303030303030303030302c2276616c7565223a302c226e223a302c227363726970745075624b6579223a7b22686578223a224e736534696b6a45383867324267734e7773737754646b53776953724b6a6a53222c22616464726573736573223a5b224e736534696b6a45383867324267734e7773737754646b53776953724b6a6a53225d7d7d2c7b2256616c7565536174223a373238363536353537303030302c2276616c7565223a302c226e223a312c227363726970745075624b6579223a7b22686578223a224e73653131397a326f53444a596b466b786d775944695974506642654e6b7169222c22616464726573736573223a5b224e73653131397a326f53444a596b466b786d775944695974506642654e6b7169225d7d7d5d2c22626c6f636b74696d65223a313535323335373435393535357d"
)
func init() {
testTx1 = bchain.Tx{
Hex: "",
Blocktime: 1552357844175,
Txid: "00206bb124108a5fd98e828b181ff01b927c0c6b477d4512bfef84cf52f0fc2a6a1a",
LockTime: 0,
Version: 1,
Vin: []bchain.Vin{
{
Txid: "00205457ab0703b1d10462d4347048c0bb3564e40f57ae1f6251695943de1ac810a0",
Vout: 0,
Sequence: 0,
Addresses: []string{
"Nse53MwRLBJ1WUU6SedMHQAFfCPtB7w4",
},
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(399999000000),
N: 0,
JsonValue: json.Number("0"),
ScriptPubKey: bchain.ScriptPubKey{
Hex: "Nse4zpZHsUuU7h5ymv28pcGbwHju3joV",
Addresses: []string{
"Nse4zpZHsUuU7h5ymv28pcGbwHju3joV",
},
},
},
},
//CoinSpecificData: []string{},
}
testTx2 = bchain.Tx{
Hex: "",
Blocktime: 1552357459555,
Txid: "002078a98f381c714a068d605e4be1ee21954857611e098abbcc3f50c5680f8ad2e5",
LockTime: 0,
Version: 1,
Vin: []bchain.Vin{
{
Txid: "00207a40dc4b6ac407d49a3c33aa754bb4f05e45e57c24811b147e7bf3caf063a823",
Vout: 1,
Sequence: 0,
Addresses: []string{
"Nse119z2oSDJYkFkxmwYDiYtPfBeNkqi",
},
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(400000000000),
N: 0,
JsonValue: json.Number("0"),
ScriptPubKey: bchain.ScriptPubKey{
Hex: "Nse4ikjE88g2BgsNwsswTdkSwiSrKjjS",
Addresses: []string{
"Nse4ikjE88g2BgsNwsswTdkSwiSrKjjS",
},
},
},
{
ValueSat: *big.NewInt(7286565570000),
N: 1,
JsonValue: json.Number("0"),
ScriptPubKey: bchain.ScriptPubKey{
Hex: "Nse119z2oSDJYkFkxmwYDiYtPfBeNkqi",
Addresses: []string{
"Nse119z2oSDJYkFkxmwYDiYtPfBeNkqi",
},
},
},
},
//CoinSpecificData: []string{},
}
}
func TestGetAddrDescFromAddress(t *testing.T) {
type args struct {
address string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "P2PKH",
args: args{address: "Nse4j39uEMuxx5j577h3K2MDLAQ64JZN"},
want: "042301ac78cd3eb193287e2c59e4cbd765c5c47d432c2fa1",
wantErr: false,
},
{
name: "P2PKH",
args: args{address: "Nse2e7U7nmGT8UHsvQ7JfksLtWwoLwrd"},
want: "0423018a90e66a64318f6af6d673487a6560f5686fd26a2e",
wantErr: false,
},
{
name: "P2PKH",
args: args{address: "NsdvMEP57nzxmBa5z18rx9sQsLgUfNtw"},
want: "04230124422cfe426573e476fd45d7c2a43a75a0b6b8c478",
wantErr: false,
},
}
parser := NewNulsParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parser.GetAddrDescFromAddress(tt.args.address)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
}
})
}
}
func TestGetAddrDescFromVout(t *testing.T) {
type args struct {
vout bchain.Vout
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "P2PK",
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "Nse4j39uEMuxx5j577h3K2MDLAQ64JZN"}}},
want: "042301ac78cd3eb193287e2c59e4cbd765c5c47d432c2fa1",
wantErr: false,
},
{
name: "P2PK",
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "Nse2e7U7nmGT8UHsvQ7JfksLtWwoLwrd"}}},
want: "0423018a90e66a64318f6af6d673487a6560f5686fd26a2e",
wantErr: false,
},
{
name: "P2PK",
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "NsdvMEP57nzxmBa5z18rx9sQsLgUfNtw"}}},
want: "04230124422cfe426573e476fd45d7c2a43a75a0b6b8c478",
wantErr: false,
},
}
parser := NewNulsParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parser.GetAddrDescFromVout(&tt.args.vout)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddrDescFromVout() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("GetAddrDescFromVout() = %v, want %v", h, tt.want)
}
})
}
}
func TestGetAddressesFromAddrDesc(t *testing.T) {
type args struct {
script string
}
tests := []struct {
name string
args args
want []string
want2 bool
wantErr bool
}{
{
name: "P2PKH",
args: args{script: "042301ac78cd3eb193287e2c59e4cbd765c5c47d432c2fa1"},
want: []string{"Nse4j39uEMuxx5j577h3K2MDLAQ64JZN"},
want2: true,
wantErr: false,
},
{
name: "P2PKH",
args: args{script: "0423018a90e66a64318f6af6d673487a6560f5686fd26a2e"},
want: []string{"Nse2e7U7nmGT8UHsvQ7JfksLtWwoLwrd"},
want2: true,
wantErr: false,
},
{
name: "P2PKH",
args: args{script: "04230124422cfe426573e476fd45d7c2a43a75a0b6b8c478"},
want: []string{"NsdvMEP57nzxmBa5z18rx9sQsLgUfNtw"},
want2: true,
wantErr: false,
},
}
parser := NewNulsParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.script)
got, got2, err := parser.GetAddressesFromAddrDesc(b)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddressesFromAddrDesc() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got2, tt.want2) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
}
})
}
}
func TestPackTx(t *testing.T) {
type args struct {
tx bchain.Tx
height uint32
blockTime int64
parser *NulsParser
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "test-1",
args: args{
tx: testTx1,
height: 123456,
blockTime: 1552357844175,
parser: NewNulsParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked1,
wantErr: false,
},
{
name: "test-2",
args: args{
tx: testTx2,
height: 510234,
blockTime: 1552357459555,
parser: NewNulsParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked2,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
if (err != nil) != tt.wantErr {
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("packTx() = %v, want %v", h, tt.want)
}
})
}
}
func TestUnpackTx(t *testing.T) {
type args struct {
packedTx string
parser *NulsParser
}
tests := []struct {
name string
args args
want *bchain.Tx
want1 uint32
wantErr bool
}{
{
name: "test-1",
args: args{
packedTx: testTxPacked1,
parser: NewNulsParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx1,
want1: 123456,
wantErr: false,
},
{
name: "test-2",
args: args{
packedTx: testTxPacked2,
parser: NewNulsParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx2,
want1: 510234,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.packedTx)
got, got1, err := tt.args.parser.UnpackTx(b)
if (err != nil) != tt.wantErr {
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("unpackTx() got = %+v, want %+v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

View File

@ -0,0 +1,635 @@
package nuls
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"bytes"
"encoding/base64"
"encoding/json"
"github.com/gobuffalo/packr/v2/file/resolver/encoding/hex"
"github.com/juju/errors"
"io"
"io/ioutil"
"math/big"
"net"
"net/http"
"runtime/debug"
"strconv"
"time"
"github.com/golang/glog"
)
// NulsRPC is an interface to JSON-RPC bitcoind service.
type NulsRPC struct {
*btc.BitcoinRPC
client http.Client
rpcURL string
user string
password string
}
// NewNulsRPC returns new NulsRPC instance.
func NewNulsRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
b, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {
return nil, err
}
var c btc.Configuration
err = json.Unmarshal(config, &c)
if err != nil {
return nil, errors.Annotatef(err, "Invalid configuration file")
}
transport := &http.Transport{
Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100, // necessary to not to deplete ports
}
s := &NulsRPC{
BitcoinRPC: b.(*btc.BitcoinRPC),
client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport},
rpcURL: c.RPCURL,
user: c.RPCUser,
password: c.RPCPass,
}
s.BitcoinRPC.RPCMarshaler = btc.JSONMarshalerV1{}
s.BitcoinRPC.ChainConfig.SupportsEstimateSmartFee = false
return s, nil
}
// Initialize initializes GincoinRPC instance.
func (n *NulsRPC) Initialize() error {
chainName := ""
params := GetChainParams(chainName)
// always create parser
n.BitcoinRPC.Parser = NewNulsParser(params, n.BitcoinRPC.ChainConfig)
// parameters for getInfo request
if params.Net == MainnetMagic {
n.BitcoinRPC.Testnet = false
n.BitcoinRPC.Network = "livenet"
} else {
n.BitcoinRPC.Testnet = true
n.BitcoinRPC.Network = "testnet"
}
glog.Info("rpc: block chain ", params.Name)
return nil
}
type CmdGetNetworkInfo struct {
Success bool `json:"success"`
Data struct {
LocalBestHeight int64 `json:"localBestHeight"`
NetBestHeight int `json:"netBestHeight"`
TimeOffset string `json:"timeOffset"`
InCount int8 `json:"inCount"`
OutCount int8 `json:"outCount"`
} `json:"data"`
}
type CmdGetVersionInfo struct {
Success bool `json:"success"`
Data struct {
MyVersion string `json:"myVersion"`
NewestVersion string `json:"newestVersion"`
NetworkVersion int `json:"networkVersion"`
Infromation string `json:"infromation"`
} `json:"data"`
}
type CmdGetBestBlockHash struct {
Success bool `json:"success"`
Data struct {
Value string `json:"value"`
} `json:"data"`
}
type CmdGetBestBlockHeight struct {
Success bool `json:"success"`
Data struct {
Value uint32 `json:"value"`
} `json:"data"`
}
type CmdTxBroadcast struct {
Success bool `json:"success"`
Data struct {
Value string `json:"value"`
} `json:"data"`
}
type CmdGetBlockHeader struct {
Success bool `json:"success"`
Data struct {
Hash string `json:"hash"`
PreHash string `json:"preHash"`
MerkleHash string `json:"merkleHash"`
StateRoot string `json:"stateRoot"`
Time int64 `json:"time"`
Height int64 `json:"height"`
TxCount int `json:"txCount"`
PackingAddress string `json:"packingAddress"`
ConfirmCount int `json:"confirmCount"`
ScriptSig string `json:"scriptSig"`
Size int `json:"size"`
Reward float64 `json:"reward"`
Fee float64 `json:"fee"`
} `json:"data"`
}
type CmdGetBlock struct {
Success bool `json:"success"`
Data struct {
Hash string `json:"hash"`
PreHash string `json:"preHash"`
MerkleHash string `json:"merkleHash"`
StateRoot string `json:"stateRoot"`
Time int64 `json:"time"`
Height int64 `json:"height"`
TxCount int `json:"txCount"`
PackingAddress string `json:"packingAddress"`
ConfirmCount int `json:"confirmCount"`
ScriptSig string `json:"scriptSig"`
Size int `json:"size"`
Reward float64 `json:"reward"`
Fee float64 `json:"fee"`
TxList []Tx `json:"txList"`
} `json:"data"`
}
type CmdGetTx struct {
Success bool `json:"success"`
Tx Tx `json:"data"`
}
type Tx struct {
Hash string `json:"hash"`
Type int `json:"type"`
Time int64 `json:"time"`
BlockHeight int64 `json:"blockHeight"`
Fee float64 `json:"fee"`
Value float64 `json:"value"`
Remark string `json:"remark"`
ScriptSig string `json:"scriptSig"`
Status int `json:"status"`
ConfirmCount int `json:"confirmCount"`
Size int `json:"size"`
Inputs []struct {
FromHash string `json:"fromHash"`
FromIndex uint32 `json:"fromIndex"`
Address string `json:"address"`
Value float64 `json:"value"`
} `json:"inputs"`
Outputs []struct {
Address string `json:"address"`
Value int64 `json:"value"`
LockTime int64 `json:"lockTime"`
} `json:"outputs"`
}
type CmdGetTxBytes struct {
Success bool `json:"success"`
Data struct {
Value string `json:"value"`
} `json:"data"`
}
func (n *NulsRPC) GetChainInfo() (*bchain.ChainInfo, error) {
networkInfo := CmdGetNetworkInfo{}
error := n.Call("/api/network/info", &networkInfo)
if error != nil {
return nil, error
}
versionInfo := CmdGetVersionInfo{}
error = n.Call("/api/client/version", &versionInfo)
if error != nil {
return nil, error
}
chainInfo := &bchain.ChainInfo{
Chain: "nuls",
Blocks: networkInfo.Data.NetBestHeight,
Headers: networkInfo.Data.NetBestHeight,
Bestblockhash: "",
Difficulty: networkInfo.Data.TimeOffset,
SizeOnDisk: networkInfo.Data.LocalBestHeight,
Version: versionInfo.Data.MyVersion,
Subversion: versionInfo.Data.NewestVersion,
ProtocolVersion: strconv.Itoa(versionInfo.Data.NetworkVersion),
Timeoffset: 0,
Warnings: versionInfo.Data.Infromation,
}
return chainInfo, nil
}
func (n *NulsRPC) GetBestBlockHash() (string, error) {
bestBlockHash := CmdGetBestBlockHash{}
error := n.Call("/api/block/newest/hash", &bestBlockHash)
if error != nil {
return "", error
}
return bestBlockHash.Data.Value, nil
}
func (n *NulsRPC) GetBestBlockHeight() (uint32, error) {
bestBlockHeight := CmdGetBestBlockHeight{}
error := n.Call("/api/block/newest/height", &bestBlockHeight)
if error != nil {
return 0, error
}
return bestBlockHeight.Data.Value, nil
}
func (n *NulsRPC) GetBlockHash(height uint32) (string, error) {
blockHeader := CmdGetBlockHeader{}
error := n.Call("/api/block/header/height/"+strconv.Itoa(int(height)), &blockHeader)
if error != nil {
return "", error
}
if !blockHeader.Success {
return "", bchain.ErrBlockNotFound
}
return blockHeader.Data.Hash, nil
}
func (n *NulsRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
uri := "/api/block/header/hash/" + hash
return n.getBlobkHeader(uri)
}
func (n *NulsRPC) GetBlockHeaderByHeight(height uint32) (*bchain.BlockHeader, error) {
uri := "/api/block/header/height/" + strconv.Itoa(int(height))
return n.getBlobkHeader(uri)
}
func (n *NulsRPC) getBlobkHeader(uri string) (*bchain.BlockHeader, error) {
blockHeader := CmdGetBlockHeader{}
error := n.Call(uri, &blockHeader)
if error != nil {
return nil, error
}
if !blockHeader.Success {
return nil, bchain.ErrBlockNotFound
}
nexHash, _ := n.GetBlockHash(uint32(blockHeader.Data.Height + 1))
header := &bchain.BlockHeader{
Hash: blockHeader.Data.Hash,
Prev: blockHeader.Data.PreHash,
Next: nexHash,
Height: uint32(blockHeader.Data.Height),
Confirmations: blockHeader.Data.ConfirmCount,
Size: blockHeader.Data.Size,
Time: blockHeader.Data.Time / 1000,
}
return header, nil
}
func (n *NulsRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
url := "/api/block/hash/" + hash
if hash == "" {
url = "/api/block/height/" + strconv.Itoa(int(height))
}
getBlock := CmdGetBlock{}
error := n.Call(url, &getBlock)
if error != nil {
return nil, error
}
if !getBlock.Success {
return nil, bchain.ErrBlockNotFound
}
nexHash, _ := n.GetBlockHash(uint32(getBlock.Data.Height + 1))
header := bchain.BlockHeader{
Hash: getBlock.Data.Hash,
Prev: getBlock.Data.PreHash,
Next: nexHash,
Height: uint32(getBlock.Data.Height),
Confirmations: getBlock.Data.ConfirmCount,
Size: getBlock.Data.Size,
Time: getBlock.Data.Time / 1000,
}
var txs []bchain.Tx
for _, rawTx := range getBlock.Data.TxList {
tx, err := converTx(rawTx)
if err != nil {
return nil, err
}
tx.Blocktime = header.Time
txs = append(txs, *tx)
}
block := &bchain.Block{
BlockHeader: header,
Txs: txs,
}
return block, nil
}
func (n *NulsRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
if hash == "" {
return nil, bchain.ErrBlockNotFound
}
getBlock := CmdGetBlock{}
error := n.Call("/api/block/hash/"+hash, &getBlock)
if error != nil {
return nil, error
}
if !getBlock.Success {
return nil, bchain.ErrBlockNotFound
}
nexHash, _ := n.GetBlockHash(uint32(getBlock.Data.Height + 1))
header := bchain.BlockHeader{
Hash: getBlock.Data.Hash,
Prev: getBlock.Data.PreHash,
Next: nexHash,
Height: uint32(getBlock.Data.Height),
Confirmations: getBlock.Data.ConfirmCount,
Size: getBlock.Data.Size,
Time: getBlock.Data.Time / 1000,
}
var txIds []string
for _, rawTx := range getBlock.Data.TxList {
txIds = append(txIds, rawTx.Hash)
}
blockInfo := &bchain.BlockInfo{
BlockHeader: header,
MerkleRoot: getBlock.Data.MerkleHash,
//Version: getBlock.Data.StateRoot,
Txids: txIds,
}
return blockInfo, nil
}
func (n *NulsRPC) GetMempool() ([]string, error) {
return nil, nil
}
func (n *NulsRPC) GetTransaction(txid string) (*bchain.Tx, error) {
if txid == "" {
return nil, bchain.ErrTxidMissing
}
getTx := CmdGetTx{}
error := n.Call("/api/tx/hash/"+txid, &getTx)
if error != nil {
return nil, error
}
if !getTx.Success {
return nil, bchain.ErrTxNotFound
}
tx, err := converTx(getTx.Tx)
if err != nil {
return nil, err
}
blockHeaderHeight := getTx.Tx.BlockHeight
blockHeader, e := n.GetBlockHeaderByHeight(uint32(blockHeaderHeight))
if blockHeader != nil {
tx.Blocktime = blockHeader.Time
}
hexBytys, e := n.GetTransactionSpecific(tx)
if e == nil {
var hex string
json.Unmarshal(hexBytys, &hex)
tx.Hex = hex
tx.CoinSpecificData = hex
}
return tx, nil
}
func (n *NulsRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
return nil, nil
}
func (n *NulsRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
if tx == nil {
return nil, bchain.ErrTxNotFound
}
if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok {
return csd, nil
}
getTxBytes := CmdGetTxBytes{}
error := n.Call("/api/tx/bytes?hash="+tx.Txid, &getTxBytes)
if error != nil {
return nil, error
}
if !getTxBytes.Success {
return nil, bchain.ErrTxNotFound
}
txBytes, byErr := base64.StdEncoding.DecodeString(getTxBytes.Data.Value)
if byErr != nil {
return nil, byErr
}
hexBytes := make([]byte, len(txBytes)*2)
hex.Encode(hexBytes, txBytes)
m, err := json.Marshal(string(hexBytes))
return json.RawMessage(m), err
}
func (n *NulsRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
return n.EstimateFee(blocks)
}
func (n *NulsRPC) EstimateFee(blocks int) (big.Int, error) {
return *big.NewInt(100000), nil
}
func (n *NulsRPC) SendRawTransaction(tx string) (string, error) {
broadcast := CmdTxBroadcast{}
req := struct {
TxHex string `json:"txHex"`
}{
TxHex: tx,
}
error := n.Post("/api/accountledger/transaction/broadcast", req, &broadcast)
if error != nil {
return "", error
}
if !broadcast.Success {
return "", bchain.ErrTxidMissing
}
return broadcast.Data.Value, nil
}
func (n *NulsRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]bchain.Outpoint, error) {
return nil, nil
}
func (n *NulsRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) {
return 0, nil
}
// Call calls Backend RPC interface, using RPCMarshaler interface to marshall the request
func (b *NulsRPC) Call(uri string, res interface{}) error {
url := b.rpcURL + uri
httpReq, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
httpReq.SetBasicAuth(b.user, b.password)
httpRes, err := b.client.Do(httpReq)
// in some cases the httpRes can contain data even if it returns error
// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
if httpRes != nil {
defer httpRes.Body.Close()
}
if err != nil {
return err
}
// if server returns HTTP error code it might not return json with response
// handle both cases
if httpRes.StatusCode != 200 {
err = safeDecodeResponse(httpRes.Body, &res)
if err != nil {
return errors.Errorf("%v %v", httpRes.Status, err)
}
return nil
}
return safeDecodeResponse(httpRes.Body, &res)
}
func (b *NulsRPC) Post(uri string, req interface{}, res interface{}) error {
url := b.rpcURL + uri
httpData, err := b.RPCMarshaler.Marshal(req)
if err != nil {
return err
}
httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(httpData))
if err != nil {
return err
}
httpReq.SetBasicAuth(b.user, b.password)
httpReq.Header.Set("Content-Type", "application/json")
httpRes, err := b.client.Do(httpReq)
// in some cases the httpRes can contain data even if it returns error
// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
if httpRes != nil {
defer httpRes.Body.Close()
}
if err != nil {
return err
}
// if server returns HTTP error code it might not return json with response
// handle both cases
if httpRes.StatusCode != 200 {
err = safeDecodeResponse(httpRes.Body, &res)
if err != nil {
return errors.Errorf("%v %v", httpRes.Status, err)
}
return nil
}
return safeDecodeResponse(httpRes.Body, &res)
}
func safeDecodeResponse(body io.ReadCloser, res *interface{}) (err error) {
var data []byte
defer func() {
if r := recover(); r != nil {
glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data))
debug.PrintStack()
if len(data) > 0 && len(data) < 2048 {
err = errors.Errorf("Error: %v", string(data))
} else {
err = errors.New("Internal error")
}
}
}()
data, err = ioutil.ReadAll(body)
if err != nil {
return err
}
//fmt.Println(string(data))
error := json.Unmarshal(data, res)
return error
}
func converTx(rawTx Tx) (*bchain.Tx, error) {
var lockTime int64 = 0
var vins = make([]bchain.Vin, 0)
var vouts []bchain.Vout
for _, input := range rawTx.Inputs {
vin := bchain.Vin{
Coinbase: "",
Txid: input.FromHash,
Vout: input.FromIndex,
ScriptSig: bchain.ScriptSig{},
Sequence: 0,
Addresses: []string{input.Address},
}
vins = append(vins, vin)
}
for index, output := range rawTx.Outputs {
vout := bchain.Vout{
ValueSat: *big.NewInt(output.Value),
//JsonValue: "",
//LockTime: output.LockTime,
N: uint32(index),
ScriptPubKey: bchain.ScriptPubKey{
Hex: output.Address,
Addresses: []string{
output.Address,
},
},
}
vouts = append(vouts, vout)
if lockTime < output.LockTime {
lockTime = output.LockTime
}
}
tx := &bchain.Tx{
Hex: "",
Txid: rawTx.Hash,
Version: 0,
LockTime: uint32(lockTime),
Vin: vins,
Vout: vouts,
Confirmations: uint32(rawTx.ConfirmCount),
Time: rawTx.Time / 1000,
}
return tx, nil
}

View File

@ -0,0 +1,67 @@
{
"coin": {
"name": "NULS",
"shortcut": "NULS",
"label": "NULS",
"alias": "nuls"
},
"ports": {
"backend_rpc": 8053,
"backend_message_queue": 38353,
"blockbook_internal": 9053,
"blockbook_public": 9153
},
"ipc": {
"rpc_url_template": "http://127.0.0.1:{{.Ports.BackendRPC}}",
"rpc_user": "rpc",
"rpc_pass": "rpc",
"rpc_timeout": 25,
"message_queue_binding_template": "tcp://127.0.0.1:{{.Ports.BackendMessageQueue}}"
},
"backend": {
"package_name": "backend-nuls",
"package_revision": "1.0-1",
"system_user": "nuls",
"version": "1.2.1",
"binary_url": "http://47.52.78.92/nuls-wallet/NULS-Wallet-linux64-1.2.1.tar.gz",
"verification_type": "sha256",
"verification_source": "8732e94415e9b6d51e9c7e1921002b4e43d78e2b12f2de636af53a562b4f4381",
"extract_command": "tar -C backend --strip 0 -xf",
"exclude_files": [],
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/nuls.sh --port {{.Ports.BackendRPC}} --dataDir {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend --pid-file /run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
"postinst_script_template": "",
"service_type": "forking",
"service_additional_params_template": "",
"protect_memory": false,
"mainnet": true,
"server_config_file": "bitcoin_like.conf",
"client_config_file": "bitcoin_like_client.conf",
"additional_params": {
"addnode": [
"mainnet.nuls"
]
}
},
"blockbook": {
"package_name": "blockbook-nuls",
"system_user": "blockbook-nuls",
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
"explorer_url": "",
"additional_params": "-resyncindexperiod=11000",
"block_chain": {
"parse": true,
"mempool_workers": 4,
"mempool_sub_workers": 8,
"block_addresses_to_keep": 300,
"xpub_magic": 76067358,
"slip44": 133,
"additional_params": {}
}
},
"meta": {
"package_maintainer": "NULS Core Team",
"package_maintainer_email": "ln@nuls.io"
}
}

174
tests/rpc/testdata/nuls.json vendored 100644
View File

@ -0,0 +1,174 @@
{
"blockHeight": 2092530,
"blockHash": "002038a2301c19dec173b6776369f4985a66a4c583c7fa26ae9db7a4369643e71370",
"blockTime": 1552392940,
"blockSize": 290,
"blockTxs": [
"0020d682b3832334ed03177a7c51a0d94c6cdaee313d14e147203d55ed0f66202234",
"0020ad9d437d9867c778c39a04c030fe941c7c07d9529cf534935471c7c8703ee7c7"
],
"txDetails": {
"0020d682b3832334ed03177a7c51a0d94c6cdaee313d14e147203d55ed0f66202234": {
"hex": "0100e069d471690100ffffffff000c170423015b98f40fb24b34d395a774aa5ea939c89999a7679e0bff0d00000000daf11f00000017042301a1bdaf958e76c84ff8a17189a6f1f4269ad4a54b7a050f0000000000daf11f00000017042301e60bf29198ed95d53013fdae781f1272a6ccdbda90d70e0000000000daf11f00000017042301751c960ae6c2f9052a53878c9e15f08ad4e79774accb130000000000daf11f00000017042301f69e4b0348458a4846f06dc4940fc1393edd3fcfea7e0e0000000000daf11f000000170423012cfbf9441677fcea3af313da99c01b485e117e9d4f5e0c0000000000daf11f00000017042301d6d33afa523fc78011aafc0c60e2e8dc45206e5676610c0000000000daf11f00000017042301e9e760b295568f955526b29e0d3a0c1dd3a1c50a4f5e0c0000000000daf11f000000170423017e8d9ff74a624f5ee2ef1a9d665385b7314c73a1750c0d0000000000daf11f000000170423012a3435e83852742f65574f297be94c48d9ab68fbb535350100000000daf11f000000170423017904ac680eb1b6a37652fe7fbc57d545d1447d48e55f0c0000000000daf11f00000017042301d2b1c26cdcb86896890bbb741e32d8057ad78cff9dbc180000000000daf11f00000000",
"txid": "0020d682b3832334ed03177a7c51a0d94c6cdaee313d14e147203d55ed0f66202234",
"blocktime": 1552392940,
"time": 1552392940,
"locktime": 2093530,
"version": 0,
"vin": [],
"vout": [
{
"value": 2.34818462,
"n": 0,
"scriptPubKey": {
"hex": "NsdykbfjmZVHYNaVrKVF89UzUgPKaRa9",
"Addresses": ["NsdykbfjmZVHYNaVrKVF89UzUgPKaRa9"]
}
},
{
"value": 0.00984442,
"n": 1,
"scriptPubKey": {
"hex": "Nse44mP6EJWbv9qPEPgi4sjtx8QmLzjU",
"Addresses": ["Nse44mP6EJWbv9qPEPgi4sjtx8QmLzjU"]
}
},
{
"value": 0.00972688,
"n": 2,
"scriptPubKey": {
"hex": "Nse8GNwGtoBhxGmwfetqnhnMART4MVGU",
"Addresses": ["Nse8GNwGtoBhxGmwfetqnhnMART4MVGU"]
}
},
{
"value": 0.01297324,
"n": 3,
"scriptPubKey": {
"hex": "Nse1KbUNjc8mEGAHgCRHXRWYR6zFa9dh",
"Addresses": ["Nse1KbUNjc8mEGAHgCRHXRWYR6zFa9dh"]
}
},
{
"value": 0.00949994,
"n": 4,
"scriptPubKey": {
"hex": "Nse9HUun2HwFAypdpBkbjXbQPMkM7nQD",
"Addresses": ["Nse9HUun2HwFAypdpBkbjXbQPMkM7nQD"]
}
},
{
"value": 0.00810575,
"n": 5,
"scriptPubKey": {
"hex": "NsdvtMNdncJC3q8un2DpBWacZL6YRzJp",
"Addresses": ["NsdvtMNdncJC3q8un2DpBWacZL6YRzJp"]
}
},
{
"value": 0.00811382,
"n": 6,
"scriptPubKey": {
"hex": "Nse7L6F3sEGc5NRghLtkRrVGaxwgXePS",
"Addresses": ["Nse7L6F3sEGc5NRghLtkRrVGaxwgXePS"]
}
},
{
"value": 0.00810575,
"n": 7,
"scriptPubKey": {
"hex": "Nse8W8pMG1E94HQSkieUYUyASBr6oRQr",
"Addresses": ["Nse8W8pMG1E94HQSkieUYUyASBr6oRQr"]
}
},
{
"value": 0.00855157,
"n": 8,
"scriptPubKey": {
"hex": "Nse1uGXjbCm9JJjmL88xYhnU59B6b3GR",
"Addresses": ["Nse1uGXjbCm9JJjmL88xYhnU59B6b3GR"]
}
},
{
"value": 0.20264373,
"n": 9,
"scriptPubKey": {
"hex": "NsdviSF29wA91UtGgYuN3HL6iMWLs2hc",
"Addresses": ["NsdviSF29wA91UtGgYuN3HL6iMWLs2hc"]
}
},
{
"value": 0.00810981,
"n": 10,
"scriptPubKey": {
"hex": "Nse1ZXaeJDcuteKwbxacdBnX46bzfcuQ",
"Addresses": ["Nse1ZXaeJDcuteKwbxacdBnX46bzfcuQ"]
}
},
{
"value": 0.01621149,
"n": 11,
"scriptPubKey": {
"hex": "Nse75MmS9stKYE11KCaLaVRaXHLprikS",
"Addresses": ["Nse75MmS9stKYE11KCaLaVRaXHLprikS"]
}
}
]
},
"0020ad9d437d9867c778c39a04c030fe941c7c07d9529cf534935471c7c8703ee7c7": {
"hex": "02004a53d471690100ffffffff03230020199146789cda517776c140a8638218b8fa29e3a1857573f9463df3e6fa1f56100167537e3b680000000000000000002300208fa6362e79b40b61791dd1484b79d61c9bebf4dc4986f347ba46e65feb9ee4dc00e02c504d00000000000000000000230020fe3ca565e667bd6b2b7889c1307d2006d0ce5697d13e98311c605ca802c7ef0f00603924cb2e0200000000000000000217042301405aa9eeca8af1edae196837bde4eef29bee040600664280d1000000000000000000170423016056a17c02cd53191ce2e647c5ed02d1c70e483607cdaed3c50100000000000000006b2102f23b91b10ca411dceccf873a313399f0c3a90b14e7b0b5f43642e33da882b7bc00473045022100c2521e5951d168e43f42010e8f1ba484cb445dcdb29d066caf0bce1c78476624022074df5ccdd4eda6f3adec5420efef85d7369b9b8266bbc392f5e76eef94f0b9a9",
"txid": "0020ad9d437d9867c778c39a04c030fe941c7c07d9529cf534935471c7c8703ee7c7",
"blocktime": 1552392940,
"time": 1552392934,
"locktime": 0,
"version": 0,
"vin": [
{
"txid": "0020199146789cda517776c140a8638218b8fa29e3a1857573f9463df3e6fa1f5610",
"vout": 1,
"sequence": 0,
"scriptSig": {
"hex": ""
},
"Addresses": ["Nsdz3WNA1bfGSmy5oYY3KGMjZSd1C61p"]
},
{
"txid": "00208fa6362e79b40b61791dd1484b79d61c9bebf4dc4986f347ba46e65feb9ee4dc",
"vout": 0,
"sequence": 0,
"scriptSig": {
"hex": ""
},
"Addresses": ["Nsdz3WNA1bfGSmy5oYY3KGMjZSd1C61p"]
},
{
"txid": "0020fe3ca565e667bd6b2b7889c1307d2006d0ce5697d13e98311c605ca802c7ef0f",
"vout": 0,
"sequence": 0,
"scriptSig": {
"hex": ""
},
"Addresses": ["Nsdz3WNA1bfGSmy5oYY3KGMjZSd1C61p"]
}
],
"vout": [
{
"value": 8998.00000000,
"n": 0,
"scriptPubKey": {
"hex": "Nsdx5SBU9xN9SKz6dEwcz85WdFaNj2g4",
"Addresses": ["Nsdx5SBU9xN9SKz6dEwcz85WdFaNj2g4"]
}
},
{
"value": 19491.71633415,
"n": 1,
"scriptPubKey": {
"hex": "Nsdz3WNA1bfGSmy5oYY3KGMjZSd1C61p",
"Addresses": ["Nsdz3WNA1bfGSmy5oYY3KGMjZSd1C61p"]
}
}
]
}
}
}

546
tests/sync/testdata/nuls.json vendored 100644
View File

@ -0,0 +1,546 @@
{
"connectBlocks": {
"syncRanges": [
{"lower": 2184003, "upper": 2184213}
],
"blocks": {
"2184003": {
"height": 2184003,
"hash": "0020e281a2e5095904f95e3d1f3b48da808c7511e478362f22e678bf68b976e1bbb6",
"noTxs": 1,
"txDetails": [
{
"hex": "",
"txid": "00208f0b652c397e761e0e2e1fc31f8b1b4f7fe28a1f35af382886c4d95c8dcb9715",
"blocktime": 1553317770,
"time": 1553317770,
"version": 0,
"vin": [],
"vout": [
{
"value": 0.21957008,
"n": 0,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "NsdteeXfqPcsmMStKeE2Es3dv7rSXjgF",
"Addresses": ["NsdteeXfqPcsmMStKeE2Es3dv7rSXjgF"]
}
},
{
"value": 0.00836727,
"n": 1,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "Nse4H3MD4thD2rZeW178J6AxUh4B5Dq3",
"Addresses": ["Nse4H3MD4thD2rZeW178J6AxUh4B5Dq3"]
}
},
{
"value": 0.00823145,
"n": 2,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "Nsdx6yBvJRtguGduzxkgxEPjkMebW9DC",
"Addresses": ["Nsdx6yBvJRtguGduzxkgxEPjkMebW9DC"]
}
},
{
"value": 0.00992713,
"n": 3,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "Nse72BM97n88MFyhWDp9iDPEbkw4fqVh",
"Addresses": ["Nse72BM97n88MFyhWDp9iDPEbkw4fqVh"]
}
},
{
"value": 0.41158111,
"n": 4,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "Nse2c2siYena4BvSn82iprugFJE4gUsJ",
"Addresses": ["Nse2c2siYena4BvSn82iprugFJE4gUsJ"]
}
},
{
"value": 0.00823145,
"n": 5,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "NsdxARm7PHjiPLgnhMMbXLR2n7R2eded",
"Addresses": ["NsdxARm7PHjiPLgnhMMbXLR2n7R2eded"]
}
},{
"value": 0.01243773,
"n": 6,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "NsdycgzrFdoev6nmxxMGJvXKTYzo36vj",
"Addresses": ["NsdycgzrFdoev6nmxxMGJvXKTYzo36vj"]
}
},
{
"value": 0.01009588,
"n": 7,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "Nse5p6NULbecLCe8mZVVzBcSMpaZy2bB",
"Addresses": ["Nse5p6NULbecLCe8mZVVzBcSMpaZy2bB"]
}
},{
"value": 0.00823145,
"n": 8,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "Nse4e7QYA3Mj9UUqcGpCbYKexwgtSCvz",
"Addresses": ["Nse4e7QYA3Mj9UUqcGpCbYKexwgtSCvz"]
}
},
{
"value": 0.00823145,
"n": 9,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "NsdyL8HuKgrQehmefzZgKo2aydfRJQSU",
"Addresses": ["NsdyL8HuKgrQehmefzZgKo2aydfRJQSU"]
}
},
{
"value": 0.01646950,
"n": 10,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "Nse8jHzR4cS1iyiJkewGHKEWqm5B7ZbA",
"Addresses": ["Nse8jHzR4cS1iyiJkewGHKEWqm5B7ZbA"]
}
},
{
"value": 0.00823145,
"n": 11,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "NsdveDWtHKGCbxVH1AKkMA2oB2VUv134",
"Addresses": ["NsdveDWtHKGCbxVH1AKkMA2oB2VUv134"]
}
},
{
"value": 0.01704543,
"n": 12,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "Nsdz1QdJb7xNE4bDNg13oXMfa2jtbh36",
"Addresses": ["Nsdz1QdJb7xNE4bDNg13oXMfa2jtbh36"]
}
},
{
"value": 0.03778238,
"n": 13,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "Nse4rPNt3S3HHZaRhuXRDfVFYSKA4TzR",
"Addresses": ["Nse4rPNt3S3HHZaRhuXRDfVFYSKA4TzR"]
}
},
{
"value": 0.41155642,
"n": 14,
"lockTime": 2185003,
"scriptPubKey": {
"hex": "Nse84iqmZipKg7Cn6LtV5uQshWwtdRMJ",
"Addresses": ["Nse84iqmZipKg7Cn6LtV5uQshWwtdRMJ"]
}
}
]
}
]
},
"2184004": {
"height": 2184004,
"hash": "002099f701eb56f8ff3486d10a4e981df04791bcc47f6749d10fbec0987bf425e8d7",
"noTxs": 1,
"txDetails": [
{
"hex": "",
"txid": "0020ecd57d05aa56098114d2284cf48ab6edceab2351aa43add3af09b3a956fb458e",
"blocktime": 1553317780,
"time": 1553317780,
"version": 0,
"vin": [],
"vout": [
{
"value": 2.39921315,
"n": 0,
"lockTime": 2185004,
"scriptPubKey": {
"hex": "NsdwtSWZrG5gwUDoR7VfxxCVzxYsNm3Y",
"Addresses": ["NsdwtSWZrG5gwUDoR7VfxxCVzxYsNm3Y"]
}
}
]
}
]
},
"2184005": {
"height": 2184005,
"hash": "00203f9e06e59bda071f7af9fef59a53604e59a6640c52635f6e18101396b616de82",
"noTxs": 1,
"txDetails": [
{
"hex": "",
"txid": "002011a00b4561f8bb278d8e4744550368b217d61013fcd1eebc61f44a1c6a164765",
"blocktime": 1553317790,
"time": 1553317790,
"version": 0,
"vin": [],
"vout": [
{
"value": 0.57503254,
"n": 0,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nsduq2g457guft4bpj44BVFqVow6kyWi",
"Addresses": ["Nsduq2g457guft4bpj44BVFqVow6kyWi"]
}
},
{
"value": 0.04152482,
"n": 1,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "NsdvZbRf6pY1ggVfwfBR8DVs4KwUyd7L",
"Addresses": ["NsdvZbRf6pY1ggVfwfBR8DVs4KwUyd7L"]
}
},
{
"value": 0.02018105,
"n": 2,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse7uEeE7aRrhFyio9ZLa2wpx7aZ6oBf",
"Addresses": ["Nse7uEeE7aRrhFyio9ZLa2wpx7aZ6oBf"]
}
},
{
"value": 0.01177228,
"n": 3,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse2EYyQXm5EntkGGNU1omz4AZVVXRyP",
"Addresses": ["Nse2EYyQXm5EntkGGNU1omz4AZVVXRyP"]
}
},
{
"value": 0.02932482,
"n": 4,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "NsdyewTUzSVwyyk55oL4WA7mR3tEt7cb",
"Addresses": ["NsdyewTUzSVwyyk55oL4WA7mR3tEt7cb"]
}
},
{
"value": 0.08304964,
"n": 5,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "NsdtpVCa8nMtcZoWcvBDDKdmvvvQiqB4",
"Addresses": ["NsdtpVCa8nMtcZoWcvBDDKdmvvvQiqB4"]
}
},{
"value": 0.16040263,
"n": 6,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "NsdupfMS5mSvxTquqKdAtzzqrbzGrEVX",
"Addresses": ["NsdupfMS5mSvxTquqKdAtzzqrbzGrEVX"]
}
},
{
"value": 0.02906321,
"n": 7,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nsdya19a18roRTWLKNZ4ivsswRLcQq3m",
"Addresses": ["Nsdya19a18roRTWLKNZ4ivsswRLcQq3m"]
}
},{
"value": 0.02074533,
"n": 8,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse3mU18MXLYTuGGvCXyk3U1Zmf3d129",
"Addresses": ["Nse3mU18MXLYTuGGvCXyk3U1Zmf3d129"]
}
},
{
"value": 0.04303632,
"n": 9,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse11hHcyLS9pidxowEB6Nupf6rp1y83",
"Addresses": ["Nse11hHcyLS9pidxowEB6Nupf6rp1y83"]
}
},
{
"value": 0.24783259,
"n": 10,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse3rftMFxSy3vhRsyyK1axLrD1f4gvF",
"Addresses": ["Nse3rftMFxSy3vhRsyyK1axLrD1f4gvF"]
}
},
{
"value": 0.03612242,
"n": 11,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse8aukpCpwW43qnogQN3WV2SHjbgUeU",
"Addresses": ["Nse8aukpCpwW43qnogQN3WV2SHjbgUeU"]
}
},
{
"value": 0.09346822,
"n": 12,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse31sLCY9iDtsU1Fb5nwMzHQx1SC2pW",
"Addresses": ["Nse31sLCY9iDtsU1Fb5nwMzHQx1SC2pW"]
}
},
{
"value": 0.04232209,
"n": 13,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse3heixrSj8Ad2iYymSqZLEASEx46ct",
"Addresses": ["Nse3heixrSj8Ad2iYymSqZLEASEx46ct"]
}
},
{
"value": 0.22471157,
"n": 14,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse16kQr7z2msh5gerMbPDCNyZJFFgui",
"Addresses": ["Nse16kQr7z2msh5gerMbPDCNyZJFFgui"]
}
},
{
"value": 0.02124410,
"n": 15,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "NsdwqY7WsekKmzmwdbgkack2VZMo1ehq",
"Addresses": ["NsdwqY7WsekKmzmwdbgkack2VZMo1ehq"]
}
},
{
"value": 0.04152482,
"n": 16,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nsdzsz17JDYQbEmZReG8vqveoXhRXVqL",
"Addresses": ["Nsdzsz17JDYQbEmZReG8vqveoXhRXVqL"]
}
},
{
"value": 0.20762411,
"n": 17,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse2rtdaQLBPiyoVmJH4BQHJn2VUSHjo",
"Addresses": ["Nse2rtdaQLBPiyoVmJH4BQHJn2VUSHjo"]
}
},
{
"value": 0.09136708,
"n": 18,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse7W2dvVKFT2UYoavU4vVFeSYG9rjb1",
"Addresses": ["Nse7W2dvVKFT2UYoavU4vVFeSYG9rjb1"]
}
},
{
"value": 0.03410019,
"n": 19,
"lockTime": 2185005,
"scriptPubKey": {
"hex": "Nse1PbFcq9TJirXsvsjMooZ5RM1A8y7C",
"Addresses": ["Nse1PbFcq9TJirXsvsjMooZ5RM1A8y7C"]
}
}
]
}
]
},
"2184031": {
"height": 2184031,
"hash": "00202ef14e5a5505e8b8658af6c15110c1b9cf4f5dead3cfb363bbaa5ffb2275ee6e",
"noTxs": 2,
"txDetails": [
{
"hex": "",
"txid": "00206d1d22475202bbb81faba6b0e9ba22fe35c7e9d1e542d3284c5002c5a2ef7ca5",
"blocktime": 1553318050,
"time": 1553318050,
"version": 0,
"vin": [],
"vout": [
{
"value": 1.14055774,
"n": 0,
"lockTime": 2185031,
"scriptPubKey": {
"hex": "Nse5cCyitYFJ7j6ncn5UCst7pc672QEL",
"Addresses": ["Nse5cCyitYFJ7j6ncn5UCst7pc672QEL"]
}
},
{
"value": 0.02054451,
"n": 1,
"lockTime": 2185031,
"scriptPubKey": {
"hex": "Nse24Dta8UYJtR6jZHSqWtrfdRpnfHYd",
"Addresses": ["Nse24Dta8UYJtR6jZHSqWtrfdRpnfHYd"]
}
},
{
"value": 0.35934295,
"n": 2,
"lockTime": 2185031,
"scriptPubKey": {
"hex": "Nse5S92swYm231iDVZNTrDurMcGCWBcM",
"Addresses": ["Nse5S92swYm231iDVZNTrDurMcGCWBcM"]
}
},
{
"value": 0.38161210,
"n": 3,
"lockTime": 2185031,
"scriptPubKey": {
"hex": "NsdviSF29wA91UtGgYuN3HL6iMWLs2hc",
"Addresses": ["NsdviSF29wA91UtGgYuN3HL6iMWLs2hc"]
}
},
{
"value": 0.32403928,
"n": 4,
"lockTime": 2185031,
"scriptPubKey": {
"hex": "Nse8nchECsiWQ2eyTgzjvH4DewAWqvpR",
"Addresses": ["Nse8nchECsiWQ2eyTgzjvH4DewAWqvpR"]
}
}
]
},
{
"hex": "",
"txid": "0020643416a8b1dad09ee2c1bb2d12aa88eef77a6462a6bd16c39fd3971d67d66919",
"blocktime": 1553318050,
"time": 1553318045,
"version": 0,
"vin": [
{
"txid": "00205462276c5fb51b465c3926dceb27a6a724e63410c146bb027fdd7f7f06bcb56f",
"vout": 0,
"sequence": 0,
"scriptSig": {
"hex": ""
}
}
],
"vout": [
{
"value": 5444.999,
"n": 0,
"lockTime": 0,
"scriptPubKey": {
"hex": "Nsdwnd4auFisFJKU6iDvBxTdPkeg8qkB",
"Addresses": ["Nsdwnd4auFisFJKU6iDvBxTdPkeg8qkB"]
}
}
]
}
]
},
"2184213": {
"height": 2184213,
"hash": "002052a452263860be5bba31a3aace06c1cb524d48cda3d04575c3881b10241cca06",
"noTxs": 1,
"txDetails": [
{
"hex": "",
"txid": "00204882a15d826d4401aa765510243f455f7f6a05b0e77a9edd420d366cabd98818",
"blocktime": 1553319870,
"time": 1553319870,
"version": 0,
"vin": [],
"vout": [
{
"value": 0.04070938,
"n": 0,
"lockTime": 2185213,
"scriptPubKey": {
"hex": "NsdwTB38bAwybyKTfUcxZoLGEHSVqpNE",
"Addresses": ["NsdwTB38bAwybyKTfUcxZoLGEHSVqpNE"]
}
},
{
"value": 0.20863453,
"n": 1,
"lockTime": 2185213,
"scriptPubKey": {
"hex": "Nse8Q7gk2gVGDdHk7rxasedRRjgVc18Q",
"Addresses": ["Nse8Q7gk2gVGDdHk7rxasedRRjgVc18Q"]
}
}
]
}
]
}
}
},
"handleFork": {
"syncRanges": [
{"lower": 2184210, "upper": 2184213}
],
"fakeBlocks": {
"2184210": {
"height": 2184210,
"hash": "00205087cb3e6964a51b2be17593660ed4fa460706742d0d97092541d604b7ef4eaa"
},
"2184211": {
"height": 2184211,
"hash": "00204ba087c1ab04a45cfef6c61c16f97b72d118dc08fc627d7216b4a38257e906c4"
},
"2184212": {
"height": 2184212,
"hash": "002029540932d67260a4dbfb7796ffe2a96bf3abbd5c4898814b8a3057c5392d0cbb"
},
"2184213": {
"height": 2184213,
"hash": "00209d3d7e5869f1f32d4bd75587691f426d21e28022e98bd7ef0ca76590c8a6eb76"
}
},
"realBlocks": {
"2184210": {
"height": 2184210,
"hash": "00200fc994cceb3aa75a9adbdbfa5d2b9dbfaddb8ca88f1cd90acc43e0e775abfff3"
},
"2184211": {
"height": 2184211,
"hash": "0020a3d90cf5962177a775f47d2edaed94b2519d0cde93711d5e95878c9c6d390b85"
},
"2184212": {
"height": 2184212,
"hash": "002027d151738f460437db0ce8aaa058cd5fa9230a4e59e5257cc53e059f05419c3c"
},
"2184213": {
"height": 2184213,
"hash": "002052a452263860be5bba31a3aace06c1cb524d48cda3d04575c3881b10241cca06"
}
}
}
}

View File

@ -125,5 +125,9 @@
"rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync",
"EstimateSmartFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"],
"sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"]
},
"nuls": {
"rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"],
"sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"]
}
}