Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F12428766
addrman.h
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
20 KB
Subscribers
None
addrman.h
View Options
// Copyright (c) 2012 Pieter Wuille
// Copyright (c) 2012-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_ADDRMAN_H
#define BITCOIN_ADDRMAN_H
#include
<netaddress.h>
#include
<protocol.h>
#include
<random.h>
#include
<sync.h>
#include
<timedata.h>
#include
<util/system.h>
#include
<cstdint>
#include
<map>
#include
<set>
#include
<vector>
/**
* Extended statistics about a CAddress
*/
class
CAddrInfo
:
public
CAddress
{
public
:
//! last try whatsoever by us (memory only)
int64_t
nLastTry
{
0
};
//! last counted attempt (memory only)
int64_t
nLastCountAttempt
{
0
};
private
:
//! where knowledge about this address first came from
CNetAddr
source
;
//! last successful connection by us
int64_t
nLastSuccess
{
0
};
//! connection attempts since last successful attempt
int
nAttempts
{
0
};
//! reference count in new sets (memory only)
int
nRefCount
{
0
};
//! in tried set? (memory only)
bool
fInTried
{
false
};
//! position in vRandom
int
nRandomPos
{
-1
};
friend
class
CAddrMan
;
public
:
ADD_SERIALIZE_METHODS
;
template
<
typename
Stream
,
typename
Operation
>
inline
void
SerializationOp
(
Stream
&
s
,
Operation
ser_action
)
{
READWRITEAS
(
CAddress
,
*
this
);
READWRITE
(
source
);
READWRITE
(
nLastSuccess
);
READWRITE
(
nAttempts
);
}
CAddrInfo
(
const
CAddress
&
addrIn
,
const
CNetAddr
&
addrSource
)
:
CAddress
(
addrIn
),
source
(
addrSource
)
{}
CAddrInfo
()
:
CAddress
(),
source
()
{}
//! Calculate in which "tried" bucket this entry belongs
int
GetTriedBucket
(
const
uint256
&
nKey
)
const
;
//! Calculate in which "new" bucket this entry belongs, given a certain
//! source
int
GetNewBucket
(
const
uint256
&
nKey
,
const
CNetAddr
&
src
)
const
;
//! Calculate in which "new" bucket this entry belongs, using its default
//! source
int
GetNewBucket
(
const
uint256
&
nKey
)
const
{
return
GetNewBucket
(
nKey
,
source
);
}
//! Calculate in which position of a bucket to store this entry.
int
GetBucketPosition
(
const
uint256
&
nKey
,
bool
fNew
,
int
nBucket
)
const
;
//! Determine whether the statistics about this entry are bad enough so that
//! it can just be deleted
bool
IsTerrible
(
int64_t
nNow
=
GetAdjustedTime
())
const
;
//! Calculate the relative chance this entry should be given when selecting
//! nodes to connect to
double
GetChance
(
int64_t
nNow
=
GetAdjustedTime
())
const
;
};
/** Stochastic address manager
*
* Design goals:
* * Keep the address tables in-memory, and asynchronously dump the entire
* table to peers.dat.
* * Make sure no (localized) attacker can fill the entire table with his
* nodes/addresses.
*
* To that end:
* * Addresses are organized into buckets.
* * Addresses that have not yet been tried go into 1024 "new" buckets.
* * Based on the address range (/16 for IPv4) of the source of
* information, 64 buckets are selected at random.
* * The actual bucket is chosen from one of these, based on the range in
* which the address itself is located.
* * One single address can occur in up to 8 different buckets to increase
* selection chances for addresses that
* are seen frequently. The chance for increasing this multiplicity
* decreases exponentially.
* * When adding a new address to a full bucket, a randomly chosen entry
* (with a bias favoring less recently seen
* ones) is removed from it first.
* * Addresses of nodes that are known to be accessible go into 256 "tried"
* buckets.
* * Each address range selects at random 8 of these buckets.
* * The actual bucket is chosen from one of these, based on the full
* address.
* * When adding a new good address to a full bucket, a randomly chosen
* entry (with a bias favoring less recently
* tried ones) is evicted from it, back to the "new" buckets.
* * Bucket selection is based on cryptographic hashing, using a
* randomly-generated 256-bit key, which should not
* be observable by adversaries.
* * Several indexes are kept for high performance. Defining DEBUG_ADDRMAN
* will introduce frequent (and expensive)
* consistency checks for the entire data structure.
*/
//! total number of buckets for tried addresses
#define ADDRMAN_TRIED_BUCKET_COUNT_LOG2 8
//! total number of buckets for new addresses
#define ADDRMAN_NEW_BUCKET_COUNT_LOG2 10
//! maximum allowed number of entries in buckets for new and tried addresses
#define ADDRMAN_BUCKET_SIZE_LOG2 6
//! over how many buckets entries with tried addresses from a single group (/16
//! for IPv4) are spread
#define ADDRMAN_TRIED_BUCKETS_PER_GROUP 8
//! over how many buckets entries with new addresses originating from a single
//! group are spread
#define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP 64
//! in how many buckets for entries with new addresses a single address may
//! occur
#define ADDRMAN_NEW_BUCKETS_PER_ADDRESS 8
//! how old addresses can maximally be
#define ADDRMAN_HORIZON_DAYS 30
//! after how many failed attempts we give up on a new node
#define ADDRMAN_RETRIES 3
//! how many successive failures are allowed ...
#define ADDRMAN_MAX_FAILURES 10
//! ... in at least this many days
#define ADDRMAN_MIN_FAIL_DAYS 7
//! how recent a successful connection should be before we allow an address to
//! be evicted from tried
#define ADDRMAN_REPLACEMENT_SECONDS (4 * 60 * 60)
//! the maximum percentage of nodes to return in a getaddr call
#define ADDRMAN_GETADDR_MAX_PCT 23
//! the maximum number of nodes to return in a getaddr call
#define ADDRMAN_GETADDR_MAX 2500
//! Convenience
#define ADDRMAN_TRIED_BUCKET_COUNT (1 << ADDRMAN_TRIED_BUCKET_COUNT_LOG2)
#define ADDRMAN_NEW_BUCKET_COUNT (1 << ADDRMAN_NEW_BUCKET_COUNT_LOG2)
#define ADDRMAN_BUCKET_SIZE (1 << ADDRMAN_BUCKET_SIZE_LOG2)
//! the maximum number of tried addr collisions to store
#define ADDRMAN_SET_TRIED_COLLISION_SIZE 10
/**
* Stochastical (IP) address manager
*/
class
CAddrMan
{
protected
:
//! critical section to protect the inner data structures
mutable
CCriticalSection
cs
;
private
:
//! last used nId
int
nIdCount
GUARDED_BY
(
cs
);
//! table with information about all nIds
std
::
map
<
int
,
CAddrInfo
>
mapInfo
GUARDED_BY
(
cs
);
//! find an nId based on its network address
std
::
map
<
CNetAddr
,
int
>
mapAddr
GUARDED_BY
(
cs
);
//! randomly-ordered vector of all nIds
std
::
vector
<
int
>
vRandom
GUARDED_BY
(
cs
);
// number of "tried" entries
int
nTried
GUARDED_BY
(
cs
);
//! list of "tried" buckets
int
vvTried
[
ADDRMAN_TRIED_BUCKET_COUNT
][
ADDRMAN_BUCKET_SIZE
]
GUARDED_BY
(
cs
);
//! number of (unique) "new" entries
int
nNew
GUARDED_BY
(
cs
);
//! list of "new" buckets
int
vvNew
[
ADDRMAN_NEW_BUCKET_COUNT
][
ADDRMAN_BUCKET_SIZE
]
GUARDED_BY
(
cs
);
//! last time Good was called (memory only)
int64_t
nLastGood
GUARDED_BY
(
cs
);
//! Holds addrs inserted into tried table that collide with existing
//! entries. Test-before-evict discipline used to resolve these collisions.
std
::
set
<
int
>
m_tried_collisions
;
protected
:
//! secret key to randomize bucket select with
uint256
nKey
;
//! Source of random numbers for randomization in inner loops
FastRandomContext
insecure_rand
;
//! Find an entry.
CAddrInfo
*
Find
(
const
CNetAddr
&
addr
,
int
*
pnId
=
nullptr
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! find an entry, creating it if necessary.
//! nTime and nServices of the found node are updated, if necessary.
CAddrInfo
*
Create
(
const
CAddress
&
addr
,
const
CNetAddr
&
addrSource
,
int
*
pnId
=
nullptr
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Swap two elements in vRandom.
void
SwapRandom
(
unsigned
int
nRandomPos1
,
unsigned
int
nRandomPos2
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Move an entry from the "new" table(s) to the "tried" table
void
MakeTried
(
CAddrInfo
&
info
,
int
nId
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Delete an entry. It must not be in tried, and have refcount 0.
void
Delete
(
int
nId
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Clear a position in a "new" table. This is the only place where entries
//! are actually deleted.
void
ClearNew
(
int
nUBucket
,
int
nUBucketPos
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Mark an entry "good", possibly moving it from "new" to "tried".
void
Good_
(
const
CService
&
addr
,
bool
test_before_evict
,
int64_t
time
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Add an entry to the "new" table.
bool
Add_
(
const
CAddress
&
addr
,
const
CNetAddr
&
source
,
int64_t
nTimePenalty
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Mark an entry as attempted to connect.
void
Attempt_
(
const
CService
&
addr
,
bool
fCountFailure
,
int64_t
nTime
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Select an address to connect to, if newOnly is set to true, only the new
//! table is selected from.
CAddrInfo
Select_
(
bool
newOnly
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! See if any to-be-evicted tried table entries have been tested and if so
//! resolve the collisions.
void
ResolveCollisions_
()
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Return a random to-be-evicted tried table address.
CAddrInfo
SelectTriedCollision_
()
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
#ifdef DEBUG_ADDRMAN
//! Perform consistency check. Returns an error code or zero.
int
Check_
()
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
#endif
//! Select several addresses at once.
void
GetAddr_
(
std
::
vector
<
CAddress
>
&
vAddr
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Mark an entry as currently-connected-to.
void
Connected_
(
const
CService
&
addr
,
int64_t
nTime
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
//! Update an entry's service bits.
void
SetServices_
(
const
CService
&
addr
,
ServiceFlags
nServices
)
EXCLUSIVE_LOCKS_REQUIRED
(
cs
);
public
:
/**
* serialized format:
* * version byte (currently 1)
* * 0x20 + nKey (serialized as if it were a vector, for backward
* compatibility)
* * nNew
* * nTried
* * number of "new" buckets XOR 2**30
* * all nNew addrinfos in vvNew
* * all nTried addrinfos in vvTried
* * for each bucket:
* * number of elements
* * for each element: index
*
* 2**30 is xorred with the number of buckets to make addrman deserializer
* v0 detect it as incompatible. This is necessary because it did not check
* the version number on deserialization.
*
* Notice that vvTried, mapAddr and vVector are never encoded explicitly;
* they are instead reconstructed from the other information.
*
* vvNew is serialized, but only used if ADDRMAN_UNKNOWN_BUCKET_COUNT didn't
* change, otherwise it is reconstructed as well.
*
* This format is more complex, but significantly smaller (at most 1.5 MiB),
* and supports changes to the ADDRMAN_ parameters without breaking the
* on-disk structure.
*
* We don't use ADD_SERIALIZE_METHODS since the serialization and
* deserialization code has very little in common.
*/
template
<
typename
Stream
>
void
Serialize
(
Stream
&
s
)
const
{
LOCK
(
cs
);
uint8_t
nVersion
=
1
;
s
<<
nVersion
;
s
<<
uint8_t
(
32
);
s
<<
nKey
;
s
<<
nNew
;
s
<<
nTried
;
int
nUBuckets
=
ADDRMAN_NEW_BUCKET_COUNT
^
(
1
<<
30
);
s
<<
nUBuckets
;
std
::
map
<
int
,
int
>
mapUnkIds
;
int
nIds
=
0
;
for
(
const
auto
&
entry
:
mapInfo
)
{
mapUnkIds
[
entry
.
first
]
=
nIds
;
const
CAddrInfo
&
info
=
entry
.
second
;
if
(
info
.
nRefCount
)
{
// this means nNew was wrong, oh ow
assert
(
nIds
!=
nNew
);
s
<<
info
;
nIds
++
;
}
}
nIds
=
0
;
for
(
const
auto
&
entry
:
mapInfo
)
{
const
CAddrInfo
&
info
=
entry
.
second
;
if
(
info
.
fInTried
)
{
// this means nTried was wrong, oh ow
assert
(
nIds
!=
nTried
);
s
<<
info
;
nIds
++
;
}
}
for
(
int
bucket
=
0
;
bucket
<
ADDRMAN_NEW_BUCKET_COUNT
;
bucket
++
)
{
int
nSize
=
0
;
for
(
int
i
=
0
;
i
<
ADDRMAN_BUCKET_SIZE
;
i
++
)
{
if
(
vvNew
[
bucket
][
i
]
!=
-1
)
nSize
++
;
}
s
<<
nSize
;
for
(
int
i
=
0
;
i
<
ADDRMAN_BUCKET_SIZE
;
i
++
)
{
if
(
vvNew
[
bucket
][
i
]
!=
-1
)
{
int
nIndex
=
mapUnkIds
[
vvNew
[
bucket
][
i
]];
s
<<
nIndex
;
}
}
}
}
template
<
typename
Stream
>
void
Unserialize
(
Stream
&
s
)
{
LOCK
(
cs
);
Clear
();
uint8_t
nVersion
;
s
>>
nVersion
;
uint8_t
nKeySize
;
s
>>
nKeySize
;
if
(
nKeySize
!=
32
)
{
throw
std
::
ios_base
::
failure
(
"Incorrect keysize in addrman deserialization"
);
}
s
>>
nKey
;
s
>>
nNew
;
s
>>
nTried
;
int
nUBuckets
=
0
;
s
>>
nUBuckets
;
if
(
nVersion
!=
0
)
{
nUBuckets
^=
(
1
<<
30
);
}
if
(
nNew
>
ADDRMAN_NEW_BUCKET_COUNT
*
ADDRMAN_BUCKET_SIZE
)
{
throw
std
::
ios_base
::
failure
(
"Corrupt CAddrMan serialization, nNew exceeds limit."
);
}
if
(
nTried
>
ADDRMAN_TRIED_BUCKET_COUNT
*
ADDRMAN_BUCKET_SIZE
)
{
throw
std
::
ios_base
::
failure
(
"Corrupt CAddrMan serialization, nTried exceeds limit."
);
}
// Deserialize entries from the new table.
for
(
int
n
=
0
;
n
<
nNew
;
n
++
)
{
CAddrInfo
&
info
=
mapInfo
[
n
];
s
>>
info
;
mapAddr
[
info
]
=
n
;
info
.
nRandomPos
=
vRandom
.
size
();
vRandom
.
push_back
(
n
);
if
(
nVersion
!=
1
||
nUBuckets
!=
ADDRMAN_NEW_BUCKET_COUNT
)
{
// In case the new table data cannot be used (nVersion unknown,
// or bucket count wrong), immediately try to give them a
// reference based on their primary source address.
int
nUBucket
=
info
.
GetNewBucket
(
nKey
);
int
nUBucketPos
=
info
.
GetBucketPosition
(
nKey
,
true
,
nUBucket
);
if
(
vvNew
[
nUBucket
][
nUBucketPos
]
==
-1
)
{
vvNew
[
nUBucket
][
nUBucketPos
]
=
n
;
info
.
nRefCount
++
;
}
}
}
nIdCount
=
nNew
;
// Deserialize entries from the tried table.
int
nLost
=
0
;
for
(
int
n
=
0
;
n
<
nTried
;
n
++
)
{
CAddrInfo
info
;
s
>>
info
;
int
nKBucket
=
info
.
GetTriedBucket
(
nKey
);
int
nKBucketPos
=
info
.
GetBucketPosition
(
nKey
,
false
,
nKBucket
);
if
(
vvTried
[
nKBucket
][
nKBucketPos
]
==
-1
)
{
info
.
nRandomPos
=
vRandom
.
size
();
info
.
fInTried
=
true
;
vRandom
.
push_back
(
nIdCount
);
mapInfo
[
nIdCount
]
=
info
;
mapAddr
[
info
]
=
nIdCount
;
vvTried
[
nKBucket
][
nKBucketPos
]
=
nIdCount
;
nIdCount
++
;
}
else
{
nLost
++
;
}
}
nTried
-=
nLost
;
// Deserialize positions in the new table (if possible).
for
(
int
bucket
=
0
;
bucket
<
nUBuckets
;
bucket
++
)
{
int
nSize
=
0
;
s
>>
nSize
;
for
(
int
n
=
0
;
n
<
nSize
;
n
++
)
{
int
nIndex
=
0
;
s
>>
nIndex
;
if
(
nIndex
>=
0
&&
nIndex
<
nNew
)
{
CAddrInfo
&
info
=
mapInfo
[
nIndex
];
int
nUBucketPos
=
info
.
GetBucketPosition
(
nKey
,
true
,
bucket
);
if
(
nVersion
==
1
&&
nUBuckets
==
ADDRMAN_NEW_BUCKET_COUNT
&&
vvNew
[
bucket
][
nUBucketPos
]
==
-1
&&
info
.
nRefCount
<
ADDRMAN_NEW_BUCKETS_PER_ADDRESS
)
{
info
.
nRefCount
++
;
vvNew
[
bucket
][
nUBucketPos
]
=
nIndex
;
}
}
}
}
// Prune new entries with refcount 0 (as a result of collisions).
int
nLostUnk
=
0
;
for
(
std
::
map
<
int
,
CAddrInfo
>::
const_iterator
it
=
mapInfo
.
begin
();
it
!=
mapInfo
.
end
();)
{
if
(
it
->
second
.
fInTried
==
false
&&
it
->
second
.
nRefCount
==
0
)
{
std
::
map
<
int
,
CAddrInfo
>::
const_iterator
itCopy
=
it
++
;
Delete
(
itCopy
->
first
);
nLostUnk
++
;
}
else
{
it
++
;
}
}
if
(
nLost
+
nLostUnk
>
0
)
{
LogPrint
(
BCLog
::
ADDRMAN
,
"addrman lost %i new and %i tried addresses due to "
"collisions
\n
"
,
nLostUnk
,
nLost
);
}
Check
();
}
void
Clear
()
{
LOCK
(
cs
);
std
::
vector
<
int
>
().
swap
(
vRandom
);
nKey
=
insecure_rand
.
rand256
();
for
(
size_t
bucket
=
0
;
bucket
<
ADDRMAN_NEW_BUCKET_COUNT
;
bucket
++
)
{
for
(
size_t
entry
=
0
;
entry
<
ADDRMAN_BUCKET_SIZE
;
entry
++
)
{
vvNew
[
bucket
][
entry
]
=
-1
;
}
}
for
(
size_t
bucket
=
0
;
bucket
<
ADDRMAN_TRIED_BUCKET_COUNT
;
bucket
++
)
{
for
(
size_t
entry
=
0
;
entry
<
ADDRMAN_BUCKET_SIZE
;
entry
++
)
{
vvTried
[
bucket
][
entry
]
=
-1
;
}
}
nIdCount
=
0
;
nTried
=
0
;
nNew
=
0
;
// Initially at 1 so that "never" is strictly worse.
nLastGood
=
1
;
mapInfo
.
clear
();
mapAddr
.
clear
();
}
CAddrMan
()
{
Clear
();
}
~
CAddrMan
()
{
nKey
.
SetNull
();
}
//! Return the number of (unique) addresses in all tables.
size_t
size
()
const
{
// TODO: Cache this in an atomic to avoid this overhead
LOCK
(
cs
);
return
vRandom
.
size
();
}
//! Consistency check
void
Check
()
{
#ifdef DEBUG_ADDRMAN
{
LOCK
(
cs
);
int
err
;
if
((
err
=
Check_
()))
{
LogPrintf
(
"ADDRMAN CONSISTENCY CHECK FAILED!!! err=%i
\n
"
,
err
);
}
}
#endif
}
//! Add a single address.
bool
Add
(
const
CAddress
&
addr
,
const
CNetAddr
&
source
,
int64_t
nTimePenalty
=
0
)
{
LOCK
(
cs
);
bool
fRet
=
false
;
Check
();
fRet
|=
Add_
(
addr
,
source
,
nTimePenalty
);
Check
();
if
(
fRet
)
{
LogPrint
(
BCLog
::
ADDRMAN
,
"Added %s from %s: %i tried, %i new
\n
"
,
addr
.
ToStringIPPort
(),
source
.
ToString
(),
nTried
,
nNew
);
}
return
fRet
;
}
//! Add multiple addresses.
bool
Add
(
const
std
::
vector
<
CAddress
>
&
vAddr
,
const
CNetAddr
&
source
,
int64_t
nTimePenalty
=
0
)
{
LOCK
(
cs
);
int
nAdd
=
0
;
Check
();
for
(
const
CAddress
&
a
:
vAddr
)
{
nAdd
+=
Add_
(
a
,
source
,
nTimePenalty
)
?
1
:
0
;
}
Check
();
if
(
nAdd
)
{
LogPrint
(
BCLog
::
ADDRMAN
,
"Added %i addresses from %s: %i tried, %i new
\n
"
,
nAdd
,
source
.
ToString
(),
nTried
,
nNew
);
}
return
nAdd
>
0
;
}
//! Mark an entry as accessible.
void
Good
(
const
CService
&
addr
,
bool
test_before_evict
=
true
,
int64_t
nTime
=
GetAdjustedTime
())
{
LOCK
(
cs
);
Check
();
Good_
(
addr
,
test_before_evict
,
nTime
);
Check
();
}
//! Mark an entry as connection attempted to.
void
Attempt
(
const
CService
&
addr
,
bool
fCountFailure
,
int64_t
nTime
=
GetAdjustedTime
())
{
LOCK
(
cs
);
Check
();
Attempt_
(
addr
,
fCountFailure
,
nTime
);
Check
();
}
//! See if any to-be-evicted tried table entries have been tested and if so
//! resolve the collisions.
void
ResolveCollisions
()
{
LOCK
(
cs
);
Check
();
ResolveCollisions_
();
Check
();
}
//! Randomly select an address in tried that another address is attempting
//! to evict.
CAddrInfo
SelectTriedCollision
()
{
CAddrInfo
ret
;
{
LOCK
(
cs
);
Check
();
ret
=
SelectTriedCollision_
();
Check
();
}
return
ret
;
}
/**
* Choose an address to connect to.
*/
CAddrInfo
Select
(
bool
newOnly
=
false
)
{
CAddrInfo
addrRet
;
{
LOCK
(
cs
);
Check
();
addrRet
=
Select_
(
newOnly
);
Check
();
}
return
addrRet
;
}
//! Return a bunch of addresses, selected at random.
std
::
vector
<
CAddress
>
GetAddr
()
{
Check
();
std
::
vector
<
CAddress
>
vAddr
;
{
LOCK
(
cs
);
GetAddr_
(
vAddr
);
}
Check
();
return
vAddr
;
}
//! Mark an entry as currently-connected-to.
void
Connected
(
const
CService
&
addr
,
int64_t
nTime
=
GetAdjustedTime
())
{
LOCK
(
cs
);
Check
();
Connected_
(
addr
,
nTime
);
Check
();
}
void
SetServices
(
const
CService
&
addr
,
ServiceFlags
nServices
)
{
LOCK
(
cs
);
Check
();
SetServices_
(
addr
,
nServices
);
Check
();
}
};
#endif
// BITCOIN_ADDRMAN_H
File Metadata
Details
Attached
Mime Type
text/x-c++
Expires
Sun, Dec 29, 19:37 (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4844970
Default Alt Text
addrman.h (20 KB)
Attached To
rSTAGING Bitcoin ABC staging
Event Timeline
Log In to Comment