Index: nano.tcl ================================================================== --- nano.tcl +++ nano.tcl @@ -20,11 +20,11 @@ namespace eval ::nano::rpc {} namespace eval ::nano::rpc::client {} namespace eval ::nano::balance {} namespace eval ::nano::node::server {} namespace eval ::nano::node::bootstrap {} -namespace eval ::nano::node::p2p {} +namespace eval ::nano::node::realtime {} namespace eval ::nano::network::client {} namespace eval ::nano::network::server {} namespace eval ::nano::network::_dns {} # Constants @@ -41,10 +41,12 @@ "confirm_ack" "bulk_pull" "bulk_push" "frontier_req" "bulk_pull_blocks" + "node_id_handshake" + "bulk_pull_account" } set ::nano::balance::_conversion { GNano 1000000000000000000000000000000000000000 MNano 1000000000000000000000000000000000000 Gnano 1000000000000000000000000000000000 @@ -1092,10 +1094,14 @@ set frontierInfo [getFrontier $account] dict with frontierInfo {} set blockInfo [getPending $account $blockHash] + + if {![info exists representative]} { + set representative $account + } set amount [dict get $blockInfo amount] set blockArgs [list to $account previousBalance $balance \ amount $amount sourceBlock $blockHash \ signKey $signKey representative $representative] @@ -1536,11 +1542,13 @@ catch { set basis_node [dict create] set basis_node [dict get $basis "node"] } - set default_node [dict create] + set default_node [dict create \ + "client_id" [binary encode hex [::nano::internal::randomBytes 32]] + ] catch { set basis_node_database [dict create] set basis_node_database [dict get $basis "node" "database"] } @@ -1663,10 +1671,108 @@ return [binary format a32a32 \ $accountPubKey \ $end \ ] } + +proc ::nano::network::client::bulk_pull_account {account {minPending 0} {flags ""}} { + set accountPubKey [::nano::address::toPublicKey $account -binary] + set minPendingHex [format %032llx $minPending] + + if {[string length $minPendingHex] > 32} { + return -code error "Invalid amount: $minPending" + } + + set flagsInt 0 + foreach flagName $flags { + unset -nocomplain flagValue + + switch -- $flagName { + "pendingAddressOnly" { + set flagValue 1 + } + default { + if {[string is integer -strict $flagName]} { + set flagValue $flagName + } else { + return -code error "Invalid flag: $flagName" + } + } + } + + set flagsInt [expr {$flagsInt | $flagValue}] + } + + return [binary format a32H32c \ + $accountPubKey \ + $minPendingHex \ + $flagsInt \ + ] +} + +proc ::nano::network::client::bulk_pull_account_response {sock account {minPending 0} {flags ""}} { + set frontierBlockhash [::nano::network::_recv $sock 32] + set frontierBalance [::nano::network::_recv $sock 16] + + set frontierBlockhash [binary encode hex $frontierBlockhash] + set frontierBlockhash [string toupper $frontierBlockhash] + + set frontierBalance [binary encode hex $frontierBalance] + set frontierBalance 0x$frontierBalance + set frontierBalance [expr {$frontierBalance}] + + set fullPendingInfo true + if {[lsearch -exact $flags "pendingAddressOnly"] != -1} { + # XXX:TODO: We support numeric flags in the generation + # side but not here, should we ? + set fullPendingInfo false + } + + set pendingInfo [list] + while true { + if {$fullPendingInfo} { + set pendingBlockhash [binary encode hex [::nano::network::_recv $sock 32]] + set pendingAmount [binary encode hex [::nano::network::_recv $sock 16]] + + if {$pendingBlockhash eq $::nano::block::zero} { + break + } + + set pendingBlockhash [string toupper $pendingBlockhash] + set pendingAmount 0x$pendingAmount + set pendingAmount [expr {$pendingAmount}] + + lappend pendingInfo [dict create \ + blockhash $pendingBlockhash \ + amount $pendingAmount \ + ] + } else { + set pendingFrom [binary encode hex [::nano::network::_recv $sock 32]] + + if {$pendingFrom eq $::nano::address::zero} { + break + } + + set pendingFrom [::nano::address::fromPublicKey $pendingFrom] + + lappend pendingInfo [dict create \ + from $pendingFrom \ + ] + } + } + + set retval [dict create \ + frontier \ + [dict create \ + blockhash $frontierBlockhash \ + balance $frontierBalance \ + ] \ + pending $pendingInfo \ + ] + + return $retval +} proc ::nano::network::client::frontier_req {{startAccount ""} {age ""} {count ""}} { if {$startAccount eq ""} { set accountPubKey [binary decode hex $::nano::address::zero] } else { @@ -1681,30 +1787,61 @@ set count [expr {2**32-1}] } return [binary format a32ii $accountPubKey $age $count] } + +proc ::nano::network::client::node_id_handshake {nodeID} { + set nodeID [binary decode hex $nodeID] + + return $nodeID +} + +proc ::nano::network::_localIP {version} { + if {[info exists ::nano::network::_localIP($version)]} { + return $::nano::network::_localIP($version) + } + + ## XXX:TODO: Work out a better system for determining ones own IP + switch -exact -- $version { + v4 { + set url "http://ipv4.rkeene.org/whatismyip" + set localIPPrefix "::ffff:" + } + v6 { + set url "http://ipv6.rkeene.org/whatismyip" + set localIPPrefix "" + } + } + + set localIP [exec curl -sS $url] + if {$localIP eq ""} { + return -code error "Unable to lookup local IP $version" + } + + set localIP [string trim $localIP] + set localIP "${localIPPrefix}${localIP}" + + set ::nano::network::_localIP($version) $localIP + + return $::nano::network::_localIP($version) +} proc ::nano::network::client::keepalive {} { # Encode our local IP address in the packet set localIPs [list] foreach ipVersion {v4 v6} { - ## XXX:TODO: Work out a better system for determining ones own IP - switch -exact -- $ipVersion { - v4 { - if {![info exists ::nano::network::client::localIP(v4)]} { - set ::nano::network::client::localIP(v4) [exec curl -sS http://ipv4.rkeene.org/whatismyip] - } - set localIP "::ffff:${::nano::network::client::localIP(v4)}" - } - v6 { - if {![info exists ::nano::network::client::localIP(v6)]} { - set ::nano::network::client::localIP(v6) [exec curl -sS http://ipv6.rkeene.org/whatismyip] - } - set localIP $::nano::network::client::localIP(v6) - } - } + unset -nocomplain localIP + + catch { + set localIP [::nano::network::_localIP $ipVersion] + } + + if {![info exists localIP]} { + continue + } + lappend localIPs [binary decode hex [string map [list ":" ""] [::ip::normalize $localIP]]] } # Encode port as a 16-bit integer in network byte order (big endian) set localPort [dict get $::nano::node::configuration "node" "peering_port"] @@ -1747,11 +1884,11 @@ append message [::nano::network::client::${messageType} {*}$args] ::nano::node::log "Sending message [binary encode hex $message] to socket $sock" catch { - if {[dict get $sock "type"] eq "p2p"} { + if {[dict get $sock "type"] eq "realtime"} { set sockInfo $sock set sock [dict get $sock "socket"] } } @@ -1762,11 +1899,17 @@ chan configure $sock -translation binary -encoding binary puts -nonewline $sock $message flush $sock - return "" + set responseCommand ::nano::network::client::${messageType}_response + set response "" + if {[info command $responseCommand] ne ""} { + set response [$responseCommand $sock {*}$args] + } + + return $response } proc ::nano::node::bootstrap::peer {peer peerPort} { ::nano::node::log "Connecting to ${peer}:${peerPort}" @@ -1780,32 +1923,50 @@ } defer::defer close $sock ::nano::node::log "Connected to $peer:$peerPort ;; sock = $sock" - ::nano::network::client $sock "frontier_req" - while true { - set account [::nano::address::fromPublicKey [::nano::network::_recv $sock 32]] - set frontier [::nano::network::_recv $sock 32] - - puts "$account - [binary encode hex $frontier]" - } - - # XXX:TODO: Pull in some stuff - # XXX:TODO: When that stuff is pulled, add it to the ledger - - return - - ::nano::network::client $sock "bulk_pull" "xrb_3h57qc7u84uz96ox3suo6wz5hz17oi7e6bpgeg3sdkayn31ujxedkjp6i1kp" - if {[catch { - set result [::nano::network::_recv $sock 8] - puts "result=[binary encode hex $result]" -exit 0 - } err]} { - ::nano::node::log "Error: $err" - } - + if {$::nano::node::bootstrap::frontier_req_running} { + while true { + if {[llength $::nano::node::bootstrap::frontiers_to_pull] == 0} { + if {!$::nano::node::bootstrap::frontier_req_running} { + break + } + + ::nano::node::_sleep 100 0 + + continue + } + + set accountInfo [lindex $::nano::node::bootstrap::frontiers_to_pull 0] + set ::nano::node::bootstrap::frontiers_to_pull [lrange $::nano::node::bootstrap::frontiers_to_pull 1 end] + + set account [dict get $accountInfo "account"] + + ::nano::node::log "Pulling $accountInfo" + # XXX:TODO: Compare frontier, and supply our local one + ::nano::network::client $sock "bulk_pull" $account + while true { + ::nano::network::_recv $sock 32 + } + } + } else { + set ::nano::node::bootstrap::frontier_req_running true + defer::defer set ::nano::node::bootstrap::frontier_req_running false + + ::nano::node::log "Requesting frontiers" + # XXX:TODO: Age? + ::nano::network::client $sock "frontier_req" + while true { + set account [::nano::address::fromPublicKey [::nano::network::_recv $sock 32]] + set frontier [binary encode hex [::nano::network::_recv $sock 32]] + + lappend ::nano::node::bootstrap::frontiers_to_pull [dict create account $account frontier $frontier] + } + } + + return } proc ::nano::network::_dns::toIPList {name} { if {[::ip::version $name] > 0} { return [list $name] @@ -1855,19 +2016,21 @@ } }} $salt] $list } proc ::nano::node::bootstrap {} { + set ::nano::node::bootstrap::frontiers_to_pull [list] + set ::nano::node::bootstrap::frontier_req_running false + while true { set peerInfoList [::nano::network::getPeers] ::nano::node::log "Have [llength $peerInfoList] peers" foreach peerInfo $peerInfoList { set peer [dict get $peerInfo "address"] set peerPort [dict get $peerInfo "port"] -dict set ::nano::node::configuration node bootstrap_connections 1 if {[llength [info command ::nano::node::bootstrap::peer_*]] >= [dict get $::nano::node::configuration node bootstrap_connections]} { continue } set peerId [binary encode hex [::nano::internal::hashData "$peer:$peerPort" 5]] @@ -1902,11 +2065,11 @@ chan event $sock readable "" return $sock } - ::nano::node::log "Waiting in the event loop for socket $sock to become readable" + ::nano::node::log "Waiting in the event loop for socket $sock to become writable" yield chan event $sock writable "" chan event $sock readable "" @@ -1923,10 +2086,12 @@ } proc ::nano::network::_recv {sock bytes} { if {[info coroutine] ne ""} { chan event $sock readable [info coroutine] + } else { + chan configure $sock -blocking true } set retBuffer "" while {$bytes > 0} { @@ -1939,10 +2104,14 @@ if {$bufferLen == 0} { set chanError [chan configure $sock -error] if {$chanError ne ""} { return -code error "Short read on socket $sock ($bytes bytes remaining): $chanError" } + + if {[chan eof $sock]} { + return -code error "Short read on socket $sock ($bytes bytes remaining): EOF" + } continue } incr bytes -$bufferLen @@ -1952,12 +2121,14 @@ chan event $sock readable "" return $retBuffer } -proc ::nano::node::_sleep {ms} { - ::nano::node::log "Sleeping for $ms ms" +proc ::nano::node::_sleep {ms {verbose 1}} { + if {$verbose} { + ::nano::node::log "Sleeping for $ms ms" + } after $ms [info coroutine] yield } @@ -2056,11 +2227,11 @@ #9e1272edade3c247c738a4bd303eb0cfc3da298444bb9d13b8ffbced34ff036f4e1ff833324efc81c237776242928ef76a2cdfaa53f4c4530ee39bfff1977e26e382dd09ec8cafc2427cf817e9afe1f372ce81085ab4feb1f3de1f25ee818e5d000000008fc492fd20e57d048e000000204e7a62f25df739eaa224d403cb107b3f9caa0280113b0328fad3b402c465169006f988549a8b1e20e0a09b4b4dcae5397f6fcc4d507675f58c2b29ae02341b0a4fe562201a61bf27481aa4567c287136b4fd26b4840c93c42c7d1f5c518503d68ec561af4b8cf8 #9e1272edade3c247c738a4bd303eb0cfc3da298444bb9d13b8ffbced34ff036fa5e3647d3d296ec72baea013ba7fa1bf5c3357c33c90196f078ba091295e6e03e382dd09ec8cafc2427cf817e9afe1f372ce81085ab4feb1f3de1f25ee818e5d000000008fb2604ebd1fe098b8000000204e7a62f25df739eaa224d403cb107b3f9caa0280113b0328fad3b402c465165287cd9c61752dc9d011f666534dbdc10461e927503f9599791d73b1cca7fdc032d76db3f91e5b5c3d6206fa48b01bd08da4a89f2e880242e9917cfc3db80d0b9bfe8e6d1dd183d5 } proc ::nano::network::server {message {networkType "bootstrap"}} { - set message [binary scan $message a2ccccsa* \ + set messageParsed [binary scan $message a2ccccsa* \ packetMagic \ versionMax \ versionUsing \ versionMin \ messageTypeID \ @@ -2073,10 +2244,11 @@ } # XXX:TODO: Check versions and extensions set messageType [lindex $::nano::network::messageTypes $messageTypeID] +puts "*** Incoming: $messageType ($messageTypeID on $networkType) [binary encode hex $message]" set retval "" if {[catch { set retval [::nano::node::server::${messageType} $args] } err]} { @@ -2086,40 +2258,41 @@ } return $retval } -proc ::nano::node::p2p::incoming {socket} { +proc ::nano::node::realtime::incoming {socket} { set data [read $socket 8192] if {$data eq ""} { return } set remote [chan configure $socket -peer] - set response [::nano::network::server $data "p2p"] + set response [::nano::network::server $data "realtime"] if {$response eq ""} { return } # XXX:TODO: Send response - set peerSock [list type "p2p" remote $remote socket $socket] + set peerSock [list type "realtime" remote $remote socket $socket] #::nano::network::client $peerSock ... return } -proc ::nano::node::p2p {} { +proc ::nano::node::realtime {} { package require udp set peeringPort [dict get $::nano::node::configuration node peering_port] + set clientID [dict get $::nano::node::configuration node client_id] # Start a UDP listening socket set socket(v6) [udp_open $peeringPort ipv6 reuse] set socket(v4) [udp_open $peeringPort reuse] foreach {protocolVersion protocolSocket} [array get socket] { fconfigure $protocolSocket -blocking false -encoding binary -translation binary - chan event $protocolSocket readable [list ::nano::node::p2p::incoming $protocolSocket] + chan event $protocolSocket readable [list ::nano::node::realtime::incoming $protocolSocket] } # Periodically send keepalives to all known peers ## XXX:TODO: Limit this to only a few peers while true { @@ -2126,16 +2299,17 @@ foreach peerInfo [::nano::network::getPeers] { set peerAddress [dict get $peerInfo "address"] set peerPort [dict get $peerInfo "port"] set protocolVersion "v[::ip::version $peerAddress]" - set peerSock [list type "p2p" remote [list $peerAddress $peerPort] socket $socket(${protocolVersion})] + set peerSock [list type "realtime" remote [list $peerAddress $peerPort] socket $socket(${protocolVersion})] + ::nano::network::client $peerSock "node_id_handshake" $clientID ::nano::network::client $peerSock "keepalive" } - ::nano::node::_sleep [expr {5 * 60 * 1000}] + ::nano::node::_sleep [expr {1 * 60 * 1000}] } } proc ::nano::node::start {} { package require defer @@ -2142,11 +2316,11 @@ package require ip package require udp package require dns coroutine ::nano::node::bootstrap::run ::nano::node::bootstrap - coroutine ::nano::node::p2p::run ::nano::node::p2p + coroutine ::nano::node::realtime::run ::nano::node::realtime vwait ::nano::node::_FOREVER_ } # RPC Client