DERO-HE STARGATE Testnet Release29

This commit is contained in:
Captain 2021-11-24 09:20:51 +00:00
parent 49651db052
commit 4501db42a4
No known key found for this signature in database
GPG Key ID: 18CDB3ED5E85D2D4
25 changed files with 224 additions and 111 deletions

View File

@ -18,6 +18,8 @@ package block
import "fmt" import "fmt"
import "time" import "time"
import "hash"
import "sync"
import "strings" import "strings"
import "encoding/binary" import "encoding/binary"
@ -28,6 +30,10 @@ import "github.com/deroproject/derohe/pow"
const MINIBLOCK_SIZE = 68 const MINIBLOCK_SIZE = 68
var hasherPool = sync.Pool{
New: func() interface{} { return sha3.New256() },
}
// it should be exactly 68 bytes after serialization // it should be exactly 68 bytes after serialization
// structure size 1 + 6 + 4 + 4 + 16 +32 + 5 bytes // structure size 1 + 6 + 4 + 4 + 16 +32 + 5 bytes
type MiniBlock struct { type MiniBlock struct {
@ -91,9 +97,17 @@ func (mbl *MiniBlock) GetMiniID() uint32 {
} }
// this function gets the block identifier hash, this is only used to deduplicate mini blocks // this function gets the block identifier hash, this is only used to deduplicate mini blocks
func (mbl *MiniBlock) GetHash() (hash crypto.Hash) { func (mbl *MiniBlock) GetHash() (result crypto.Hash) {
ser := mbl.Serialize() ser := mbl.Serialize()
return sha3.Sum256(ser[:]) sha := hasherPool.Get().(hash.Hash)
sha.Reset()
sha.Write(ser[:])
x := sha.Sum(nil)
copy(result[:], x[:])
hasherPool.Put(sha)
return result
// return sha3.Sum256(ser[:])
} }
// Get PoW hash , this is very slow function // Get PoW hash , this is very slow function

View File

@ -192,7 +192,7 @@ func (c *MiniBlocksCollection) GetAllTips() (mbls []MiniBlock) {
clone := map[uint32]MiniBlock{} clone := map[uint32]MiniBlock{}
var clone_list []MiniBlock clone_list := make([]MiniBlock, 0, 64)
for k, v := range c.Collection { for k, v := range c.Collection {
clone[k] = v clone[k] = v
clone_list = append(clone_list, v) clone_list = append(clone_list, v)
@ -273,6 +273,8 @@ func (c *MiniBlocksCollection) GetGenesisFromMiniBlock(mbl MiniBlock) (genesis [
// this works in all cases, but it may return truncated history,all returns must be checked for connectivity // this works in all cases, but it may return truncated history,all returns must be checked for connectivity
func (c *MiniBlocksCollection) GetEntireMiniBlockHistory(mbl MiniBlock) (history []MiniBlock) { func (c *MiniBlocksCollection) GetEntireMiniBlockHistory(mbl MiniBlock) (history []MiniBlock) {
history = make([]MiniBlock, 0, 128)
if mbl.Genesis { if mbl.Genesis {
history = append(history, mbl) history = append(history, mbl)
return return
@ -301,6 +303,7 @@ func (c *MiniBlocksCollection) GetEntireMiniBlockHistory(mbl MiniBlock) (history
// gets the genesis from the tips // gets the genesis from the tips
// this function only works, if the miniblock has been expanded // this function only works, if the miniblock has been expanded
func GetGenesisFromMiniBlock(mbl MiniBlock) (genesis []MiniBlock) { func GetGenesisFromMiniBlock(mbl MiniBlock) (genesis []MiniBlock) {
if mbl.Genesis { if mbl.Genesis {
genesis = append(genesis, mbl) genesis = append(genesis, mbl)
return return
@ -325,20 +328,36 @@ func GetGenesisFromMiniBlock(mbl MiniBlock) (genesis []MiniBlock) {
// get entire history,its in sorted form // get entire history,its in sorted form
func GetEntireMiniBlockHistory(mbls ...MiniBlock) (history []MiniBlock) { func GetEntireMiniBlockHistory(mbls ...MiniBlock) (history []MiniBlock) {
var queue []MiniBlock queue := make([]MiniBlock, 0, 128)
queue = append(queue, mbls...) queue = append(queue, mbls...)
history = make([]MiniBlock, 0, 128)
unique := make([]MiniBlock, 0, 128)
unique_map := map[crypto.Hash]MiniBlock{}
for len(queue) > 0 { for len(queue) > 0 {
item := queue[0] item := queue[0]
queue = queue[1:] // Dequeue queue = queue[1:] // Dequeue
history = append(history, item) //mini blocks might be duplicated if _, ok := unique_map[item.GetHash()]; !ok {
if !item.Genesis { unique_map[item.GetHash()] = item
queue = append(queue, item.PastMiniBlocks...) history = append(history, item) //mini blocks might be duplicated
if !item.Genesis {
queue = append(queue, item.PastMiniBlocks...)
}
} }
} }
for _, v := range unique_map {
unique = append(unique, v)
}
history = MiniBlocks_Unique(history) history = MiniBlocks_Unique(history)
if len(unique) != len(history) {
panic("result mismatch")
}
history = MiniBlocks_SortByTimeAsc(history) // sort on the basis of timestamps history = MiniBlocks_SortByTimeAsc(history) // sort on the basis of timestamps
return return
@ -347,6 +366,7 @@ func GetEntireMiniBlockHistory(mbls ...MiniBlock) (history []MiniBlock) {
// this sorts by distance, in descending order // this sorts by distance, in descending order
// if distance is equal, then it sorts by its id which is collision free // if distance is equal, then it sorts by its id which is collision free
func MiniBlocks_SortByDistanceDesc(mbls []MiniBlock) (sorted []MiniBlock) { func MiniBlocks_SortByDistanceDesc(mbls []MiniBlock) (sorted []MiniBlock) {
sorted = make([]MiniBlock, 0, len(mbls))
sorted = append(sorted, mbls...) sorted = append(sorted, mbls...)
sort.SliceStable(sorted, func(i, j int) bool { // sort descending on the basis of Distance sort.SliceStable(sorted, func(i, j int) bool { // sort descending on the basis of Distance
if sorted[i].Distance == sorted[j].Distance { if sorted[i].Distance == sorted[j].Distance {
@ -360,6 +380,7 @@ func MiniBlocks_SortByDistanceDesc(mbls []MiniBlock) (sorted []MiniBlock) {
// this sorts by timestamp,ascending order // this sorts by timestamp,ascending order
// if timestamp is equal, then it sorts by its id which is collision free // if timestamp is equal, then it sorts by its id which is collision free
func MiniBlocks_SortByTimeAsc(mbls []MiniBlock) (sorted []MiniBlock) { func MiniBlocks_SortByTimeAsc(mbls []MiniBlock) (sorted []MiniBlock) {
sorted = make([]MiniBlock, 0, len(mbls))
sorted = append(sorted, mbls...) sorted = append(sorted, mbls...)
sort.SliceStable(sorted, func(i, j int) bool { // sort on the basis of timestamps sort.SliceStable(sorted, func(i, j int) bool { // sort on the basis of timestamps
if sorted[i].Timestamp == sorted[j].Timestamp { if sorted[i].Timestamp == sorted[j].Timestamp {
@ -371,6 +392,7 @@ func MiniBlocks_SortByTimeAsc(mbls []MiniBlock) (sorted []MiniBlock) {
} }
func MiniBlocks_Unique(mbls []MiniBlock) (unique []MiniBlock) { func MiniBlocks_Unique(mbls []MiniBlock) (unique []MiniBlock) {
unique = make([]MiniBlock, 0, len(mbls))
unique_map := map[crypto.Hash]MiniBlock{} unique_map := map[crypto.Hash]MiniBlock{}
for _, mbl := range mbls { for _, mbl := range mbls {
unique_map[mbl.GetHash()] = mbl unique_map[mbl.GetHash()] = mbl

View File

@ -71,6 +71,7 @@ type Blockchain struct {
cache_Get_Difficulty_At_Tips *lru.Cache // used to cache some outputs cache_Get_Difficulty_At_Tips *lru.Cache // used to cache some outputs
cache_BlockPast *lru.Cache // used to cache a blocks past cache_BlockPast *lru.Cache // used to cache a blocks past
cache_BlockHeight *lru.Cache // used to cache a blocks past cache_BlockHeight *lru.Cache // used to cache a blocks past
cache_VersionMerkle *lru.Cache // used to cache a versions merkle root
integrator_address rpc.Address // integrator rewards will be given to this address integrator_address rpc.Address // integrator rewards will be given to this address
@ -153,11 +154,15 @@ func Blockchain_Start(params map[string]interface{}) (*Blockchain, error) {
return nil, err return nil, err
} }
if chain.cache_BlockPast, err = lru.New(100 * 1024); err != nil { // temporary cache for a blocks past if chain.cache_BlockPast, err = lru.New(1024); err != nil { // temporary cache for a blocks past
return nil, err return nil, err
} }
if chain.cache_BlockHeight, err = lru.New(100 * 1024); err != nil { // temporary cache for a blocks height if chain.cache_BlockHeight, err = lru.New(10 * 1024); err != nil { // temporary cache for a blocks height
return nil, err
}
if chain.cache_VersionMerkle, err = lru.New(1024); err != nil { // temporary cache for a snapshot version
return nil, err return nil, err
} }
@ -231,11 +236,11 @@ func Blockchain_Start(params map[string]interface{}) (*Blockchain, error) {
top_block_topo_index := chain.Load_TOPO_HEIGHT() top_block_topo_index := chain.Load_TOPO_HEIGHT()
if top_block_topo_index < 10 { if top_block_topo_index < 4 {
return return
} }
top_block_topo_index -= 10 top_block_topo_index -= 4
blid, err := chain.Load_Block_Topological_order_at_index(top_block_topo_index) blid, err := chain.Load_Block_Topological_order_at_index(top_block_topo_index)
if err != nil { if err != nil {
@ -689,7 +694,7 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro
if tx.SCDATA.Has(rpc.SCACTION, rpc.DataUint64) { if tx.SCDATA.Has(rpc.SCACTION, rpc.DataUint64) {
if rpc.SC_INSTALL == rpc.SC_ACTION(tx.SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64)) { if rpc.SC_INSTALL == rpc.SC_ACTION(tx.SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64)) {
txid := tx.GetHash() txid := tx.GetHash()
if txid[31] < 0x80 { // last byte should be more than 0x80 if txid[0] < 0x80 || txid[31] < 0x80 { // last byte should be more than 0x80
block_logger.Error(fmt.Errorf("Invalid SCID"), "SCID installing tx must end with >0x80 byte", "txid", cbl.Txs[i].GetHash()) block_logger.Error(fmt.Errorf("Invalid SCID"), "SCID installing tx must end with >0x80 byte", "txid", cbl.Txs[i].GetHash())
return errormsg.ErrTXDoubleSpend, false return errormsg.ErrTXDoubleSpend, false
} }
@ -1188,6 +1193,17 @@ func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) error {
return err return err
} }
if tx.TransactionType == transaction.SC_TX {
if tx.SCDATA.Has(rpc.SCACTION, rpc.DataUint64) {
if rpc.SC_INSTALL == rpc.SC_ACTION(tx.SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64)) {
txid := tx.GetHash()
if txid[0] < 0x80 || txid[31] < 0x80 { // last byte should be more than 0x80
return fmt.Errorf("Invalid SCID ID,it must not start with 0x80")
}
}
}
}
if err := chain.Verify_Transaction_NonCoinbase_CheckNonce_Tips(hf_version, tx, chain.Get_TIPS()); err != nil { // transaction verification failed if err := chain.Verify_Transaction_NonCoinbase_CheckNonce_Tips(hf_version, tx, chain.Get_TIPS()); err != nil { // transaction verification failed
logger.V(1).Error(err, "Incoming TX nonce verification failed", "txid", txhash, "stacktrace", globals.StackTrace(false)) logger.V(1).Error(err, "Incoming TX nonce verification failed", "txid", txhash, "stacktrace", globals.StackTrace(false))
return fmt.Errorf("Incoming TX %s nonce verification failed, err %s", txhash, err) return fmt.Errorf("Incoming TX %s nonce verification failed, err %s", txhash, err)

View File

@ -18,7 +18,6 @@ package blockchain
import "fmt" import "fmt"
import "math/big" import "math/big"
import "crypto/rand"
import "path/filepath" import "path/filepath"
import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/globals"
@ -55,9 +54,7 @@ func (s *storage) Initialize(params map[string]interface{}) (err error) {
} }
func (s *storage) IsBalancesIntialized() bool { func (s *storage) IsBalancesIntialized() bool {
var err error var err error
var buf [64]byte
var balancehash, random_hash [32]byte var balancehash, random_hash [32]byte
balance_ss, _ := s.Balance_store.LoadSnapshot(0) // load most recent snapshot balance_ss, _ := s.Balance_store.LoadSnapshot(0) // load most recent snapshot
@ -65,12 +62,10 @@ func (s *storage) IsBalancesIntialized() bool {
// avoid hardcoding any hash // avoid hardcoding any hash
if balancehash, err = balancetree.Hash(); err == nil { if balancehash, err = balancetree.Hash(); err == nil {
if _, err = rand.Read(buf[:]); err == nil { random_tree, _ := balance_ss.GetTree(config.SC_META)
random_tree, _ := balance_ss.GetTree(string(buf[:])) if random_hash, err = random_tree.Hash(); err == nil {
if random_hash, err = random_tree.Hash(); err == nil { if random_hash == balancehash {
if random_hash == balancehash { return false
return false
}
} }
} }
} }
@ -300,6 +295,11 @@ func (chain *Blockchain) Load_Block_Topological_order_at_index(index_pos int64)
//load store hash from 2 tree //load store hash from 2 tree
func (chain *Blockchain) Load_Merkle_Hash(version uint64) (hash crypto.Hash, err error) { func (chain *Blockchain) Load_Merkle_Hash(version uint64) (hash crypto.Hash, err error) {
if hashi, ok := chain.cache_VersionMerkle.Get(version); ok {
hash = hashi.(crypto.Hash)
return
}
ss, err := chain.Store.Balance_store.LoadSnapshot(version) ss, err := chain.Store.Balance_store.LoadSnapshot(version)
if err != nil { if err != nil {
return return
@ -324,5 +324,9 @@ func (chain *Blockchain) Load_Merkle_Hash(version uint64) (hash crypto.Hash, err
for i := range balance_merkle_hash { for i := range balance_merkle_hash {
hash[i] = balance_merkle_hash[i] ^ meta_merkle_hash[i] hash[i] = balance_merkle_hash[i] ^ meta_merkle_hash[i]
} }
if chain.cache_enabled { //set in cache
chain.cache_VersionMerkle.Add(version, hash)
}
return hash, nil return hash, nil
} }

View File

@ -49,7 +49,7 @@ func clean_up_valid_cache() {
current_time := time.Now() current_time := time.Now()
transaction_valid_cache.Range(func(k, value interface{}) bool { transaction_valid_cache.Range(func(k, value interface{}) bool {
first_seen := value.(time.Time) first_seen := value.(time.Time)
if current_time.Sub(first_seen).Round(time.Second).Seconds() > 3600 { if current_time.Sub(first_seen).Round(time.Second).Seconds() > 360 {
transaction_valid_cache.Delete(k) transaction_valid_cache.Delete(k)
} }
return true return true

View File

@ -26,6 +26,7 @@ import "bufio"
import "strings" import "strings"
import "strconv" import "strconv"
import "runtime" import "runtime"
import "runtime/debug"
import "math/big" import "math/big"
import "os/signal" import "os/signal"
import "io/ioutil" import "io/ioutil"
@ -89,9 +90,24 @@ var Exit_In_Progress = make(chan bool)
var logger logr.Logger var logger logr.Logger
func dump(filename string) {
f, err := os.Create(filename)
if err != nil {
fmt.Printf("err creating file %s\n", err)
return
}
runtime.GC()
debug.WriteHeapDump(f.Fd())
err = f.Close()
if err != nil {
fmt.Printf("err closing file %s\n", err)
}
}
func main() { func main() {
var err error var err error
globals.Arguments, err = docopt.Parse(command_line, nil, true, config.Version.String(), false) globals.Arguments, err = docopt.Parse(command_line, nil, true, config.Version.String(), false)
if err != nil { if err != nil {
@ -781,6 +797,12 @@ restart_loop:
case command == "gc": case command == "gc":
runtime.GC() runtime.GC()
case command == "heap":
if len(line_parts) == 1 {
fmt.Printf("heap needs a filename to write\n")
break
}
dump(line_parts[1])
case command == "ban": case command == "ban":

View File

@ -103,7 +103,7 @@ var Mainnet = CHAIN_CONFIG{Name: "mainnet",
} }
var Testnet = CHAIN_CONFIG{Name: "testnet", // testnet will always have last 3 bytes 0 var Testnet = CHAIN_CONFIG{Name: "testnet", // testnet will always have last 3 bytes 0
Network_ID: uuid.FromBytesOrNil([]byte{0x59, 0xd7, 0xf7, 0xe9, 0xdd, 0x48, 0xd5, 0xfd, 0x13, 0x0a, 0xf6, 0xe0, 0x70, 0x00, 0x00, 0x00}), Network_ID: uuid.FromBytesOrNil([]byte{0x59, 0xd7, 0xf7, 0xe9, 0xdd, 0x48, 0xd5, 0xfd, 0x13, 0x0a, 0xf6, 0xe0, 0x71, 0x00, 0x00, 0x00}),
P2P_Default_Port: 40401, P2P_Default_Port: 40401,
RPC_Default_Port: 40402, RPC_Default_Port: 40402,
Wallet_RPC_Default_Port: 40403, Wallet_RPC_Default_Port: 40403,

View File

@ -30,4 +30,5 @@ var Mainnet_seed_nodes = []string{
// some seed node for testnet // some seed node for testnet
var Testnet_seed_nodes = []string{ var Testnet_seed_nodes = []string{
"68.183.12.117:40401", "68.183.12.117:40401",
"167.99.145.53:40401",
} }

View File

@ -20,4 +20,4 @@ import "github.com/blang/semver/v4"
// right now it has to be manually changed // right now it has to be manually changed
// do we need to include git commitsha?? // do we need to include git commitsha??
var Version = semver.MustParse("3.4.87-1.DEROHE.STARGATE+24112021") var Version = semver.MustParse("3.4.89-1.DEROHE.STARGATE+22112021")

View File

@ -14,6 +14,8 @@ import (
// no method is available to handle the request. // no method is available to handle the request.
type Assigner interface { type Assigner interface {
// Assign returns the handler for the named method, or nil. // Assign returns the handler for the named method, or nil.
// The implementation can obtain the complete request from ctx using the
// jrpc2.InboundRequest function.
Assign(ctx context.Context, method string) Handler Assign(ctx context.Context, method string) Handler
// Names returns a slice of all known method names for the assigner. The // Names returns a slice of all known method names for the assigner. The

View File

@ -1,6 +1,8 @@
// Package channel defines a communications channel that can encode/transmit // Package channel defines a basic communications channel.
// and decode/receive data records with a configurable framing discipline, and //
// provides some simple framing implementations. // A Channel encodes/transmits and decodes/receives data records over an
// unstructured stream, using a configurable framing discipline. This package
// provides some basic framing implementations.
// //
// Channels // Channels
// //

View File

@ -10,6 +10,10 @@ const bufSize = 4096
// RawJSON is a framing that transmits and receives records on r and wc, in which // RawJSON is a framing that transmits and receives records on r and wc, in which
// each record is defined by being a complete JSON value. No padding or other // each record is defined by being a complete JSON value. No padding or other
// separation is added. // separation is added.
//
// A RawJSON channel has no out-of-band framing, so the channel cannot usually
// recover after a message that is not syntactically valid JSON. Applications
// that need a channel to survive invalid JSON should avoid this framing.
func RawJSON(r io.Reader, wc io.WriteCloser) Channel { func RawJSON(r io.Reader, wc io.WriteCloser) Channel {
return jsonc{wc: wc, dec: json.NewDecoder(r), buf: make([]byte, bufSize)} return jsonc{wc: wc, dec: json.NewDecoder(r), buf: make([]byte, bufSize)}
} }

View File

@ -57,18 +57,16 @@ func (c Code) Err() error {
return codeError(c) return codeError(c)
} }
// Pre-defined standard error codes defined by the JSON-RPC specification. // Error codes from and including -32768 to -32000 are reserved for pre-defined
// errors by the JSON-RPC specification. These constants cover the standard
// codes and implementation-specific codes used by the jrpc2 module.
const ( const (
ParseError Code = -32700 // Invalid JSON received by the server ParseError Code = -32700 // [std] Invalid JSON received by the server
InvalidRequest Code = -32600 // The JSON sent is not a valid request object InvalidRequest Code = -32600 // [std] The JSON sent is not a valid request object
MethodNotFound Code = -32601 // The method does not exist or is unavailable MethodNotFound Code = -32601 // [std] The method does not exist or is unavailable
InvalidParams Code = -32602 // Invalid method parameters InvalidParams Code = -32602 // [std] Invalid method parameters
InternalError Code = -32603 // Internal JSON-RPC error InternalError Code = -32603 // [std] Internal JSON-RPC error
)
// The JSON-RPC 2.0 specification reserves the range -32000 to -32099 for
// implementation-defined server errors. These are used by the jrpc2 package.
const (
NoError Code = -32099 // Denotes a nil error (used by FromError) NoError Code = -32099 // Denotes a nil error (used by FromError)
SystemError Code = -32098 // Errors from the operating environment SystemError Code = -32098 // Errors from the operating environment
Cancelled Code = -32097 // Request cancelled (context.Canceled) Cancelled Code = -32097 // Request cancelled (context.Canceled)
@ -91,6 +89,10 @@ var stdError = map[Code]string{
// Register adds a new Code value with the specified message string. This // Register adds a new Code value with the specified message string. This
// function will panic if the proposed value is already registered with a // function will panic if the proposed value is already registered with a
// different string. // different string.
//
// Registering a code allows you to control the string returned by the String
// method for the code value you specify. It is not necessary to register a
// code before using it. An unregistered code renders a generic string.
func Register(value int32, message string) Code { func Register(value int32, message string) Code {
code := Code(value) code := Code(value)
if s, ok := stdError[code]; ok && s != message { if s, ok := stdError[code]; ok && s != message {
@ -102,7 +104,7 @@ func Register(value int32, message string) Code {
// FromError returns a Code to categorize the specified error. // FromError returns a Code to categorize the specified error.
// If err == nil, it returns code.NoError. // If err == nil, it returns code.NoError.
// If err is an ErrCoder, it returns the reported code value. // If err is (or wraps) an ErrCoder, it returns the reported code value.
// If err is context.Canceled, it returns code.Cancelled. // If err is context.Canceled, it returns code.Cancelled.
// If err is context.DeadlineExceeded, it returns code.DeadlineExceeded. // If err is context.DeadlineExceeded, it returns code.DeadlineExceeded.
// Otherwise it returns code.SystemError. // Otherwise it returns code.SystemError.

View File

@ -84,7 +84,7 @@ To create a client we need a channel:
conn, err := net.Dial("tcp", "localhost:8080") conn, err := net.Dial("tcp", "localhost:8080")
... ...
ch := channel.RawJSON(conn, conn) ch := channel.Line(conn, conn)
cli := jrpc2.NewClient(ch, nil) // nil for default options cli := jrpc2.NewClient(ch, nil) // nil for default options
To send a single RPC, use the Call method: To send a single RPC, use the Call method:

View File

@ -47,10 +47,6 @@ var errEmptyMethod = &Error{Code: code.InvalidRequest, Message: "empty method na
// errInvalidRequest is the error reported for an invalid request object or batch. // errInvalidRequest is the error reported for an invalid request object or batch.
var errInvalidRequest = &Error{Code: code.ParseError, Message: "invalid request value"} var errInvalidRequest = &Error{Code: code.ParseError, Message: "invalid request value"}
// errChannelClosed is the error reported to a pending callback when the client
// channel has closed before the call completed.
var errChannelClosed = &Error{Code: code.Cancelled, Message: "client channel terminated"}
// errEmptyBatch is the error reported for an empty request batch. // errEmptyBatch is the error reported for an empty request batch.
var errEmptyBatch = &Error{Code: code.InvalidRequest, Message: "empty request batch"} var errEmptyBatch = &Error{Code: code.InvalidRequest, Message: "empty request batch"}

View File

@ -7,52 +7,44 @@ import (
"fmt" "fmt"
"log" "log"
"strings" "strings"
"sync"
"github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/channel"
"github.com/creachadair/jrpc2/code" "github.com/creachadair/jrpc2/code"
"github.com/creachadair/jrpc2/handler" "github.com/creachadair/jrpc2/handler"
"github.com/creachadair/jrpc2/server"
) )
var ( var (
s *jrpc2.Server ctx = context.Background()
ctx = context.Background()
sch, cch = channel.Direct()
cli = jrpc2.NewClient(cch, nil)
setup sync.Once
) )
type Msg struct { type Msg struct {
Text string `json:"msg"` Text string `json:"msg"`
} }
func startServer() { func startServer() server.Local {
setup.Do(func() { return server.NewLocal(handler.Map{
s = jrpc2.NewServer(handler.Map{ "Hello": handler.New(func(ctx context.Context) string {
"Hello": handler.New(func(ctx context.Context) string { return "Hello, world!"
return "Hello, world!" }),
}), "Echo": handler.New(func(_ context.Context, args []json.RawMessage) []json.RawMessage {
"Echo": handler.New(func(_ context.Context, args []json.RawMessage) []json.RawMessage { return args
return args }),
}), "Log": handler.New(func(ctx context.Context, msg Msg) (bool, error) {
"Log": handler.New(func(ctx context.Context, msg Msg) (bool, error) { fmt.Println("Log:", msg.Text)
fmt.Println("Log:", msg.Text) return true, nil
return true, nil }),
}), }, nil)
}, nil).Start(sch)
})
} }
func ExampleNewServer() { func ExampleNewServer() {
// Construct a new server with methods "Hello" and "Log". // Construct a new server with methods "Hello" and "Log".
startServer() loc := startServer()
defer loc.Close()
// We can query the server for its current status information, including a // We can query the server for its current status information, including a
// list of its methods. // list of its methods.
si := s.ServerInfo() si := loc.Server.ServerInfo()
fmt.Println(strings.Join(si.Methods, "\n")) fmt.Println(strings.Join(si.Methods, "\n"))
// Output: // Output:
@ -62,10 +54,10 @@ func ExampleNewServer() {
} }
func ExampleClient_Call() { func ExampleClient_Call() {
startServer() loc := startServer()
defer loc.Close()
// var cli = jrpc2.NewClient(cch, nil) rsp, err := loc.Client.Call(ctx, "Hello", nil)
rsp, err := cli.Call(ctx, "Hello", nil)
if err != nil { if err != nil {
log.Fatalf("Call: %v", err) log.Fatalf("Call: %v", err)
} }
@ -79,11 +71,11 @@ func ExampleClient_Call() {
} }
func ExampleClient_CallResult() { func ExampleClient_CallResult() {
startServer() loc := startServer()
defer loc.Close()
// var cli = jrpc2.NewClient(cch, nil)
var msg string var msg string
if err := cli.CallResult(ctx, "Hello", nil, &msg); err != nil { if err := loc.Client.CallResult(ctx, "Hello", nil, &msg); err != nil {
log.Fatalf("CallResult: %v", err) log.Fatalf("CallResult: %v", err)
} }
fmt.Println(msg) fmt.Println(msg)
@ -92,10 +84,10 @@ func ExampleClient_CallResult() {
} }
func ExampleClient_Batch() { func ExampleClient_Batch() {
startServer() loc := startServer()
defer loc.Close()
// var cli = jrpc2.NewClient(cch, nil) rsps, err := loc.Client.Batch(ctx, []jrpc2.Spec{
rsps, err := cli.Batch(ctx, []jrpc2.Spec{
{Method: "Hello"}, {Method: "Hello"},
{Method: "Log", Params: Msg{"Sing it!"}, Notify: true}, {Method: "Log", Params: Msg{"Sing it!"}, Notify: true},
}) })
@ -172,8 +164,10 @@ type strictParams struct {
func (strictParams) DisallowUnknownFields() {} func (strictParams) DisallowUnknownFields() {}
func ExampleResponse_UnmarshalResult() { func ExampleResponse_UnmarshalResult() {
// var cli = jrpc2.NewClient(cch, nil) loc := startServer()
rsp, err := cli.Call(ctx, "Echo", []string{"alpha", "oscar", "kilo"}) defer loc.Close()
rsp, err := loc.Client.Call(ctx, "Echo", []string{"alpha", "oscar", "kilo"})
if err != nil { if err != nil {
log.Fatalf("Call: %v", err) log.Fatalf("Call: %v", err)
} }

View File

@ -88,6 +88,17 @@ func New(fn interface{}) Func {
return fi.Wrap() return fi.Wrap()
} }
// NewStrict acts as New, but enforces strict field checking on an argument of
// struct type.
func NewStrict(fn interface{}) Func {
fi, err := Check(fn)
if err != nil {
panic(err)
}
fi.strictFields = true
return fi.Wrap()
}
var ( var (
ctxType = reflect.TypeOf((*context.Context)(nil)).Elem() // type context.Context ctxType = reflect.TypeOf((*context.Context)(nil)).Elem() // type context.Context
errType = reflect.TypeOf((*error)(nil)).Elem() // type error errType = reflect.TypeOf((*error)(nil)).Elem() // type error
@ -355,8 +366,8 @@ func (o Obj) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &base); err != nil { if err := json.Unmarshal(data, &base); err != nil {
return filterJSONError("decoding", "object", err) return filterJSONError("decoding", "object", err)
} }
for key, val := range base { for key, arg := range o {
arg, ok := o[key] val, ok := base[key]
if !ok { if !ok {
continue continue
} else if err := json.Unmarshal(val, arg); err != nil { } else if err := json.Unmarshal(val, arg); err != nil {

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/code"
"github.com/creachadair/jrpc2/handler" "github.com/creachadair/jrpc2/handler"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
) )
@ -111,6 +112,26 @@ func TestPositional(t *testing.T) {
} }
} }
func TestNewStrict(t *testing.T) {
type arg struct {
A, B string
}
fn := handler.NewStrict(func(ctx context.Context, arg *arg) error { return nil })
req := mustParseRequest(t, `{
"jsonrpc": "2.0",
"id": 100,
"method": "f",
"params": {
"A": "foo",
"Z": 25
}}`)
rsp, err := fn(context.Background(), req)
if got := code.FromError(err); got != code.InvalidParams {
t.Errorf("Handler returned (%+v, %v), want InvalidParms", rsp, err)
}
}
// Verify that the handling of pointer-typed arguments does not incorrectly // Verify that the handling of pointer-typed arguments does not incorrectly
// introduce another pointer indirection. // introduce another pointer indirection.
func TestNew_pointerRegression(t *testing.T) { func TestNew_pointerRegression(t *testing.T) {
@ -120,18 +141,15 @@ func TestNew_pointerRegression(t *testing.T) {
t.Logf("Got argument struct: %+v", got) t.Logf("Got argument struct: %+v", got)
return nil return nil
}) })
req, err := jrpc2.ParseRequests([]byte(`{ req := mustParseRequest(t, `{
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": "foo", "id": "foo",
"method": "bar", "method": "bar",
"params":{ "params":{
"alpha": "xyzzy", "alpha": "xyzzy",
"bravo": 23 "bravo": 23
}}`)) }}`)
if err != nil { if _, err := call.Handle(context.Background(), req); err != nil {
t.Fatalf("Parse request failed: %v", err)
}
if _, err := call.Handle(context.Background(), req[0]); err != nil {
t.Errorf("Handle failed: %v", err) t.Errorf("Handle failed: %v", err)
} }
want := argStruct{A: "xyzzy", B: 23} want := argStruct{A: "xyzzy", B: 23}
@ -165,11 +183,8 @@ func TestPositional_decode(t *testing.T) {
{`{"jsonrpc":"2.0","id":6,"method":"add","params":{"unknown":"field"}}`, 0, true}, {`{"jsonrpc":"2.0","id":6,"method":"add","params":{"unknown":"field"}}`, 0, true},
} }
for _, test := range tests { for _, test := range tests {
req, err := jrpc2.ParseRequests([]byte(test.input)) req := mustParseRequest(t, test.input)
if err != nil { got, err := call(context.Background(), req)
t.Fatalf("ParseRequests %#q: unexpected error: %v", test.input, err)
}
got, err := call(context.Background(), req[0])
if !test.bad { if !test.bad {
if err != nil { if err != nil {
t.Errorf("Call %#q: unexpected error: %v", test.input, err) t.Errorf("Call %#q: unexpected error: %v", test.input, err)
@ -360,3 +375,14 @@ func TestObjUnmarshal(t *testing.T) {
} }
} }
} }
func mustParseRequest(t *testing.T, text string) *jrpc2.Request {
t.Helper()
req, err := jrpc2.ParseRequests([]byte(text))
if err != nil {
t.Fatalf("ParseRequests: %v", err)
} else if len(req) != 1 {
t.Fatalf("Wrong number of requests: got %d, want 1", len(req))
}
return req[0]
}

View File

@ -278,7 +278,7 @@ func (lg Logger) Printf(msg string, args ...interface{}) {
} }
// StdLogger adapts a *log.Logger to a Logger. If logger == nil, the returned // StdLogger adapts a *log.Logger to a Logger. If logger == nil, the returned
// function sends logs to the default logger . // function sends logs to the default logger.
func StdLogger(logger *log.Logger) Logger { func StdLogger(logger *log.Logger) Logger {
if logger == nil { if logger == nil {
return func(text string) { log.Output(2, text) } return func(text string) { log.Output(2, text) }

View File

@ -465,14 +465,15 @@ func (s *Server) pushReq(ctx context.Context, wantID bool, method string, params
id := strconv.FormatInt(s.callID, 10) id := strconv.FormatInt(s.callID, 10)
s.callID++ s.callID++
cbctx, cancel := context.WithCancel(ctx)
jid = json.RawMessage(id) jid = json.RawMessage(id)
rsp = &Response{ rsp = &Response{
ch: make(chan *jmessage, 1), ch: make(chan *jmessage, 1),
id: id, id: id,
cancel: func() {}, cancel: cancel,
} }
s.call[id] = rsp s.call[id] = rsp
go s.waitCallback(ctx, id, rsp) go s.waitCallback(cbctx, id, rsp)
} }
s.log("Posting server %s %q %s", kind, method, string(bits)) s.log("Posting server %s %q %s", kind, method, string(bits))
@ -575,11 +576,8 @@ func (s *Server) stop(err error) {
// Cancel any in-flight requests that made it out of the queue, and // Cancel any in-flight requests that made it out of the queue, and
// terminate any pending callback invocations. // terminate any pending callback invocations.
for id, rsp := range s.call { for id, rsp := range s.call {
rsp.ch <- &jmessage{
ID: json.RawMessage(id),
E: errChannelClosed,
}
delete(s.call, id) delete(s.call, id)
rsp.cancel()
} }
for id, cancel := range s.used { for id, cancel := range s.used {
cancel() cancel()

View File

@ -65,7 +65,7 @@ func main() {
log.Printf("Connected to %v", conn.RemoteAddr()) log.Printf("Connected to %v", conn.RemoteAddr())
// Start up the client, and enable logging to stderr. // Start up the client, and enable logging to stderr.
cli := jrpc2.NewClient(channel.RawJSON(conn, conn), &jrpc2.ClientOptions{ cli := jrpc2.NewClient(channel.Line(conn, conn), &jrpc2.ClientOptions{
OnNotify: func(req *jrpc2.Request) { OnNotify: func(req *jrpc2.Request) {
var params json.RawMessage var params json.RawMessage
req.UnmarshalParams(&params) req.UnmarshalParams(&params)

View File

@ -96,7 +96,7 @@ func main() {
log.Fatalln("Listen:", err) log.Fatalln("Listen:", err)
} }
log.Printf("Listening at %v...", lst.Addr()) log.Printf("Listening at %v...", lst.Addr())
acc := server.NetAccepter(lst, channel.RawJSON) acc := server.NetAccepter(lst, channel.Line)
server.Loop(acc, server.Static(mux), &server.LoopOptions{ server.Loop(acc, server.Static(mux), &server.LoopOptions{
ServerOptions: &jrpc2.ServerOptions{ ServerOptions: &jrpc2.ServerOptions{
Logger: jrpc2.StdLogger(nil), Logger: jrpc2.StdLogger(nil),

View File

@ -3,8 +3,8 @@ module github.com/creachadair/jrpc2/tools
go 1.17 go 1.17
require ( require (
github.com/creachadair/jrpc2 v0.30.1 github.com/creachadair/jrpc2 v0.30.3
github.com/creachadair/wschannel v0.0.0-20210930050814-ee1a57283ef3 github.com/creachadair/wschannel v0.0.0-20211118152247-10d58f4f0def
) )
require ( require (

View File

@ -1,8 +1,7 @@
github.com/creachadair/jrpc2 v0.26.0/go.mod h1:w+GXZGc+NwsH0xsUOgeLBIIRM0jBOSTXhv28KaWGRZU= github.com/creachadair/jrpc2 v0.30.3 h1:fz8xYfTmIgxJXvr9HAoz0XBOpNklyixE7Hnh6iQP/4o=
github.com/creachadair/jrpc2 v0.30.1 h1:brsyJY1US3f5mxS3IaYoc8kH2O1MNcWUKiBmGswUeE8= github.com/creachadair/jrpc2 v0.30.3/go.mod h1:w+GXZGc+NwsH0xsUOgeLBIIRM0jBOSTXhv28KaWGRZU=
github.com/creachadair/jrpc2 v0.30.1/go.mod h1:w+GXZGc+NwsH0xsUOgeLBIIRM0jBOSTXhv28KaWGRZU= github.com/creachadair/wschannel v0.0.0-20211118152247-10d58f4f0def h1:FV0vHCqItsi0b3LwaEKyxj0su3VKdvbenCOkXnCAXnI=
github.com/creachadair/wschannel v0.0.0-20210930050814-ee1a57283ef3 h1:b0LJF4h+tv81wui0UKrszHi+yny5B/UJA10bHimYy0Y= github.com/creachadair/wschannel v0.0.0-20211118152247-10d58f4f0def/go.mod h1:/9Csuxj8r9h0YXexL0WmkahIhd85BleYWz7nt42ZgDc=
github.com/creachadair/wschannel v0.0.0-20210930050814-ee1a57283ef3/go.mod h1:ZW4LjPekGnPGBDEgssmCLAOIObRDGv2SQay/pT+5ZwM=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=

View File

@ -280,7 +280,7 @@ rebuild_tx:
if tx.SCDATA.Has(rpc.SCACTION, rpc.DataUint64) { if tx.SCDATA.Has(rpc.SCACTION, rpc.DataUint64) {
if rpc.SC_INSTALL == rpc.SC_ACTION(tx.SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64)) { if rpc.SC_INSTALL == rpc.SC_ACTION(tx.SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64)) {
txid := tx.GetHash() txid := tx.GetHash()
if txid[31] < 0x80 { // last byte should be more than 0x80 if txid[0] < 0x80 || txid[31] < 0x80 { // last byte should be more than 0x80
if retry_count <= 20 { if retry_count <= 20 {
//fmt.Printf("rebuilding tx %s retry_count %d\n", txid, retry_count) //fmt.Printf("rebuilding tx %s retry_count %d\n", txid, retry_count)
goto rebuild_tx goto rebuild_tx