1616import java .util .ArrayList ;
1717import java .util .Arrays ;
1818import java .util .Collection ;
19+ import java .util .Collections ;
1920import java .util .Date ;
2021import java .util .Enumeration ;
2122import java .util .LinkedHashMap ;
8586import utils .ObserverMessage ;
8687import utils .Pair ;
8788import utils .SysTray ;
89+ import utils .TransactionTimestampComparator ;
8890import utils .UpdateUtil ;
8991import webserver .WebService ;
9092
9193public class Controller extends Observable {
9294
9395 private static final Logger LOGGER = LogManager .getLogger (Controller .class );
94- private String version = "0.26.10 " ;
95- private String buildTime = "2018-06-13 07:57 :00 UTC" ;
96+ private String version = "0.26.11 " ;
97+ private String buildTime = "2018-09-25 12:29 :00 UTC" ;
9698 private long buildTimestamp ;
9799
98- public static final String releaseVersion = "0.26.10 " ;
100+ public static final String releaseVersion = "0.26.11 " ;
99101
100102 // TODO ENUM would be better here
101103 public static final int STATUS_NO_CONNECTIONS = 0 ;
@@ -408,7 +410,7 @@ public void run() {
408410 }
409411 });
410412
411- // TIMER TO SEND HEIGHT TO NETWORK EVERY 5 MIN
413+ // TIMER TO SEND HEIGHT TO RANDOM PEER
412414
413415 this .timerPeerHeightUpdate .cancel ();
414416 this .timerPeerHeightUpdate = new Timer ();
@@ -426,7 +428,7 @@ public void run() {
426428 }
427429 };
428430
429- this .timerPeerHeightUpdate .schedule (action , 5 * 60 * 1000 , 5 * 60 * 1000 );
431+ this .timerPeerHeightUpdate .schedule (action , 30 * 1000 , 30 * 1000 );
430432
431433 // REGISTER DATABASE OBSERVER
432434 this .addObserver (DBSet .getInstance ().getTransactionMap ());
@@ -579,44 +581,50 @@ public void deleteWalletObserver(Observer o) {
579581 }
580582
581583 private boolean isStopping = false ;
584+ private Object stoppingLock = new Object ();
582585
583586 public void stopAll () {
584- // PREVENT MULTIPLE CALLS
585- if (!this .isStopping ) {
586- this .isStopping = true ;
587-
588- // STOP SENDING OUR HEIGHT TO PEERS
589- this .timerPeerHeightUpdate .cancel ();
590-
591- // STOP BLOCK PROCESSOR
592- LOGGER .info (Lang .getInstance ().translate ("Stopping block processor" ));
593- ClosingDialog .getInstance ().updateProgress ("Stopping block processor" );
594- this .synchronizer .stop ();
595-
596- // STOP BLOCK GENERATOR
597- LOGGER .info (Lang .getInstance ().translate ("Stopping block generator" ));
598- ClosingDialog .getInstance ().updateProgress ("Stopping block generator" );
599- this .blockGenerator .shutdown ();
600-
601- // STOP MESSAGE PROCESSOR
602- LOGGER .info (Lang .getInstance ().translate ("Stopping message processor" ));
603- ClosingDialog .getInstance ().updateProgress ("Stopping message processor" );
604- this .network .stop ();
605-
606- // CLOSE DATABASE
607- LOGGER .info (Lang .getInstance ().translate ("Closing database" ));
608- ClosingDialog .getInstance ().updateProgress ("Closing database" );
609- DBSet .getInstance ().close ();
587+ // Prevent multiple calls.
588+ // This method can be called via JVM shutdown hook (e.g. signal), API 'stop' or GUI 'exit', among others.
589+ // In particular, ClosingDialog.getInstance() below can trigger a call to stopAll().
590+ // We want all successive calls to block until the first call has finished.
591+ synchronized (this .stoppingLock ) {
592+ if (!this .isStopping ) {
593+ this .isStopping = true ;
594+
595+ // STOP SENDING OUR HEIGHT TO PEERS
596+ this .timerPeerHeightUpdate .cancel ();
597+
598+ // STOP BLOCK PROCESSOR
599+ LOGGER .info (Lang .getInstance ().translate ("Stopping block processor" ));
600+ ClosingDialog .getInstance ().updateProgress ("Stopping block processor" );
601+ this .synchronizer .stop ();
602+
603+ // STOP BLOCK GENERATOR
604+ LOGGER .info (Lang .getInstance ().translate ("Stopping block generator" ));
605+ ClosingDialog .getInstance ().updateProgress ("Stopping block generator" );
606+ this .blockGenerator .shutdown ();
607+
608+ // STOP MESSAGE PROCESSOR
609+ LOGGER .info (Lang .getInstance ().translate ("Stopping message processor" ));
610+ ClosingDialog .getInstance ().updateProgress ("Stopping message processor" );
611+ this .network .stop ();
612+
613+ // CLOSE DATABASE
614+ LOGGER .info (Lang .getInstance ().translate ("Closing database" ));
615+ ClosingDialog .getInstance ().updateProgress ("Closing database" );
616+ DBSet .getInstance ().close ();
610617
611- // CLOSE WALLET
612- LOGGER .info (Lang .getInstance ().translate ("Closing wallet" ));
613- ClosingDialog .getInstance ().updateProgress ("Closing wallet" );
614- this .wallet .close ();
618+ // CLOSE WALLET
619+ LOGGER .info (Lang .getInstance ().translate ("Closing wallet" ));
620+ ClosingDialog .getInstance ().updateProgress ("Closing wallet" );
621+ this .wallet .close ();
615622
616- ClosingDialog .getInstance ().updateProgress ("Creating database backup" );
617- createDataCheckpoint ();
623+ ClosingDialog .getInstance ().updateProgress ("Creating database backup" );
624+ createDataCheckpoint ();
618625
619- LOGGER .info (Lang .getInstance ().translate ("Closed." ));
626+ LOGGER .info (Lang .getInstance ().translate ("Closed." ));
627+ }
620628 }
621629 }
622630
@@ -649,6 +657,19 @@ public List<Peer> getActivePeers() {
649657 }
650658
651659 public void walletSyncStatusUpdate (int height ) {
660+ /*
661+ * Prevent deadlock when a new block arrives from network while we're resyncing wallet.
662+ *
663+ * New block arrival locks Controller and wants Synchronizer,
664+ * but it's possible Synchronizer is locked (e.g. by BlockGenerator) while performing a wallet sync
665+ * and this.setChanged() would want a lock on Controller too, causing a deadlock.
666+ *
667+ * We avoid this by testing for block processing status and exiting early.
668+ */
669+
670+ if (DBSet .getInstance ().getBlockMap ().isProcessing ())
671+ return ;
672+
652673 this .setChanged ();
653674 this .notifyObservers (new ObserverMessage (ObserverMessage .WALLET_SYNC_STATUS , height ));
654675 }
@@ -671,9 +692,6 @@ public void onConnect(Peer peer) {
671692 if (DBSet .getInstance ().isStopped ())
672693 return ;
673694
674- // GET HEIGHT
675- int height = this .blockChain .getHeight ();
676-
677695 if (NTP .getTime () >= Transaction .getPOWFIX_RELEASE ()) {
678696 // SEND FOUNDMYSELF MESSAGE
679697 peer .sendMessage (MessageFactory .getInstance ().createFindMyselfMessage (Controller .getInstance ().getFoundMyselfID ()));
@@ -683,8 +701,19 @@ public void onConnect(Peer peer) {
683701 }
684702
685703 // SEND HEIGHT MESSAGE
686- LOGGER .trace ("Sending our height " + height + " to peer " + peer .getAddress ());
687- peer .sendMessage (MessageFactory .getInstance ().createHeightMessage (height ));
704+ getSendMyHeightToPeer (peer );
705+
706+ // Resend any unconfirmed transactions
707+ List <Transaction > transactions = DBSet .getInstance ().getTransactionMap ().getTransactions ();
708+
709+ // Sort transactions chronologically
710+ Collections .sort (transactions , new TransactionTimestampComparator ());
711+
712+ // Send unconfirmed transactions
713+ for (Transaction transaction : transactions ) {
714+ Message message = MessageFactory .getInstance ().createTransactionMessage (transaction );
715+ peer .sendMessage (message );
716+ }
688717
689718 if (this .status == STATUS_NO_CONNECTIONS ) {
690719 // UPDATE STATUS
@@ -743,12 +772,17 @@ public void onDisconnect(Peer peer) {
743772 // in case they attempt to access DB
744773 if (this .isStopping )
745774 return ;
746-
747- // NOTIFY
748- this .setChanged ();
749- this .notifyObservers (new ObserverMessage (ObserverMessage .NETWORK_STATUS , this .status ));
750775 }
751776 }
777+
778+ // NOTIFY, but in separate thread to avoid MapDB interrupt issue
779+ new Thread () {
780+ @ Override
781+ public void run () {
782+ Controller .getInstance ().setChanged ();
783+ Controller .getInstance ().notifyObservers (new ObserverMessage (ObserverMessage .NETWORK_STATUS , Controller .getInstance ().status ));
784+ }
785+ }.start ();
752786 }
753787
754788 public void onError (Peer peer ) {
@@ -830,10 +864,25 @@ public void onMessage(Message message) {
830864
831865 case Message .BLOCK_TYPE :
832866
867+ // Don't process if we're synchronizing
868+ if (this .status == STATUS_SYNCHRONIZING )
869+ break ;
870+
833871 BlockMessage blockMessage = (BlockMessage ) message ;
834872
835- // ASK BLOCK FROM BLOCKCHAIN
873+ // Get block from message
836874 block = blockMessage .getBlock ();
875+ LOGGER .trace ("Received block from peer " + message .getSender ().getAddress ());
876+
877+ // Compare to our blockchain tip
878+ Block blockchainTip = this .blockChain .getLastBlock ();
879+ if (blockchainTip .getHeight () == blockMessage .getHeight () && Arrays .equals (blockchainTip .getSignature (), block .getSignature ())) {
880+ // We have this block already but update our peer DB to reflect peer's height anyway
881+ synchronized (this .peerHeight ) {
882+ this .peerHeight .put (message .getSender (), blockMessage .getHeight ());
883+ }
884+ break ;
885+ }
837886
838887 boolean isNewBlockValid = this .blockChain .isNewBlockValid (block );
839888
@@ -846,6 +895,18 @@ public void onMessage(Message message) {
846895 if (this .isProcessingWalletSynchronize ())
847896 break ;
848897
898+ /*
899+ * Prevent deadlock when a new block arrives from network while we're resyncing wallet.
900+ *
901+ * New block arrival locks Controller and wants Synchronizer,
902+ * but it's possible Synchronizer is locked (e.g. by BlockGenerator) while performing a wallet sync
903+ * and this.setChanged() would want a lock on Controller too, causing a deadlock.
904+ *
905+ * We avoid this by testing for block processing status and exiting early.
906+ */
907+ if (DBSet .getInstance ().getBlockMap ().isProcessing ())
908+ break ;
909+
849910 // CHECK IF VALID
850911 if (isNewBlockValid && this .synchronizer .process (block )) {
851912 LOGGER .info (Lang .getInstance ().translate ("received new valid block" ) + " " + block .getHeight ());
@@ -858,6 +919,9 @@ public void onMessage(Message message) {
858919 excludes .add (message .getSender ());
859920 this .network .broadcast (message , excludes );
860921
922+ // Let sender know we've updated
923+ this .getSendMyHeightToPeer (message .getSender ());
924+
861925 // UPDATE ALL PEER HEIGHTS TO OUR HEIGHT
862926 /*
863927 * synchronized(this.peerHeight) { for(Peer peer: this.peerHeight.keySet()) { this.peerHeight.put(peer, this.blockChain.getHeight()); }
@@ -983,9 +1047,22 @@ public void update() {
9831047 peer = this .getMaxHeightPeer ();
9841048
9851049 if (peer != null ) {
1050+ // Make a note of pre-sync height so we can tell if anything happened
1051+ int preSyncHeight = this .blockChain .getHeight ();
1052+
9861053 // SYNCHRONIZE FROM PEER
9871054 LOGGER .info ("Synchronizing using peer " + peer .getAddress ().getHostAddress () + " with height " + peerHeight .get (peer ) + " - ping " + peer .getPing () + "ms" );
9881055 this .synchronizer .synchronize (peer );
1056+
1057+ // If our height has changed, notify our peers
1058+ if (this .blockChain .getHeight () > preSyncHeight ) {
1059+ Block blockchainTip = this .blockChain .getLastBlock ();
1060+ LOGGER .debug ("Sending our new height " + blockchainTip .getHeight () + " to peers" );
1061+ Message message = MessageFactory .getInstance ().createHeightMessage (blockchainTip .getHeight ());
1062+
1063+ List <Peer > excludes = new ArrayList <Peer >();
1064+ this .network .broadcast (message , excludes );
1065+ }
9891066 }
9901067
9911068 Thread .sleep (5 * 1000 );
@@ -999,6 +1076,8 @@ public void update() {
9991076 // DISHONEST PEER
10001077 this .network .onError (peer , e .getMessage ());
10011078 }
1079+
1080+ return ;
10021081 }
10031082
10041083 if (this .peerHeight .size () == 0 ) {
@@ -1354,10 +1433,17 @@ public long getNextBlockGeneratingBalance(Block parent) {
13541433 // FORGE
13551434
13561435 public void newBlockGenerated (Block newBlock ) {
1357- this .synchronizer .process (newBlock );
1436+ // Only process if we have connections and are not synchronizing
1437+ if (this .status == STATUS_OK ) {
1438+ if (this .synchronizer .process (newBlock )) {
1439+ LOGGER .info ("Forged new block " + newBlock .getHeight ());
13581440
1359- // BROADCAST
1360- this .broadcastBlock (newBlock );
1441+ // BROADCAST
1442+ this .broadcastBlock (newBlock );
1443+ } else {
1444+ LOGGER .info ("Couldn't forge new block" );
1445+ }
1446+ }
13611447 }
13621448
13631449 public List <Transaction > getUnconfirmedTransactions () {
@@ -1655,6 +1741,9 @@ public Pair<Transaction, Integer> sendMessage(PrivateKeyAccount sender, Account
16551741
16561742 public Block getBlockByHeight (int parseInt ) {
16571743 byte [] b = DBSet .getInstance ().getHeightMap ().getBlockByHeight (parseInt );
1744+ if (b == null )
1745+ return null ;
1746+
16581747 return DBSet .getInstance ().getBlockMap ().get (b );
16591748 }
16601749
0 commit comments