From e38ea77ed5d75d9b07b782fc71a616f0a8949479 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 28 Jun 2024 10:26:14 -0700 Subject: [PATCH 01/31] chore: update checkpoints for mainnet and testnet Signed-off-by: HashEngineering --- .../org.darkcoin.production.checkpoints.txt | 389 ++++++++++++++- .../org.darkcoin.test.checkpoints.txt | 462 +++++++++++++++++- 2 files changed, 849 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/org.darkcoin.production.checkpoints.txt b/core/src/main/resources/org.darkcoin.production.checkpoints.txt index 0622ad8ba2..32a63796f1 100644 --- a/core/src/main/resources/org.darkcoin.production.checkpoints.txt +++ b/core/src/main/resources/org.darkcoin.production.checkpoints.txt @@ -1,6 +1,6 @@ TXT CHECKPOINTS 1 0 -3220 +3607 AAAAAAAAAAAkQCRAAAACQAIAAAAVH6nAAsNe/Kh1ihi+qsNyRhKz5QGUAyybpU/1KQIAADKEYBnXENihFvOEAHUsytpbQJf7QFGTQ3pFVWiHHeQnSlDbUvz/Ax7nQAIA AAAAAAAAAAC1ALUAAAAEgAIAAACPa0nYq+iMh51WsXwHxbWdw7esglfgOjOuEwrcgAIAACtgKaPRFHJ2L5fqDdPmUkCemb3cyIOqM23tbAGNYbkFjlrbUv//AB6YIQEA AAAAAAAAAAL4AvgAAAAGwAIAAAB8a2OpvV2j+WYn2Yyknb5gxQk+BlaGM2E/Sash6wAAAM2CiycIZqtxwBQJoBUSohXNRj+SoILtbjzEaFoHJvUD5WfbUsD/Px028AMA @@ -3221,3 +3221,390 @@ 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 diff --git a/core/src/main/resources/org.darkcoin.test.checkpoints.txt b/core/src/main/resources/org.darkcoin.test.checkpoints.txt index c7b5d31ae2..a053055bae 100644 --- a/core/src/main/resources/org.darkcoin.test.checkpoints.txt +++ b/core/src/main/resources/org.darkcoin.test.checkpoints.txt @@ -1,6 +1,6 @@ TXT CHECKPOINTS 1 0 -1335 +1795 AAAAAAAAAAAkECOYAAACQAIAAAC2vSBlF+IPfT99SBfBEbKlchhK3afBQPjYS9KqDAgAABE5bCPb4Xjjx3Gzp7s/CezmJlWPYawKPXVKt9/lIy/754vXVP//Dx4QcwEA AAAAAAAAAABIQCXnAAAEgAIAAACawFgULuwvdEP4oo4cVUCbItDd/zHYV2j0GgTfFwcAAIazSWKA2SoCbNJR3AqymqvWS9x6Ahsmd9McS5pQk6w23pTXVP//Ax68GQoA AAAAAAAAAADZAErXAAAGwAIAAAADyAGIUUymlSwxz+Kn1VWKDgT/31Nmomb5tsx0aAMAAOzHCNcqCWULtDmL4vMjNlvFgwfCDixx4kSCOB3xH8ZU3qXXVP//AB5amgAA @@ -1336,3 +1336,463 @@ AAAAAAJ7q7Hge/R0AAu1AAAAACDDiahD7+b/VZYgip8EJJM5wJr7dEp4hMKSzMnHxAAAAP98WO/qohXJ AAAAAAJ7q7MfH9BJAAu3QAAAACAFx67dgqcT+/XSLwvlyh1xGnRgZLciXgTsbZFA/wAAAIwlRg0wXyS0qlaiLAVTYIkmUl/qEjJ8zgS23/WW2OgZLKLdYq6LAR4XZAYA AAAAAAJ7q7RZNpvpAAu5gAAAACBeomVDHRFWvqw/G4O1g/o+N3NTczoMs41MZBKMJwcAAHsZERe1DOETqaXGDXzcfG7+Zf0lSX3iz24eavAgjCG2YOXeYrlnAx6ZnwIA AAAAAAJ7q7V/Pi4uAAu7wAAAACAnV9klj+LMWMYFbi0Gtf/fVthMUluFAamkaxjqdQEAAFnRsyrPMdSi1zLJ78/Ptg0GpX8fvWdKUKgl31Kh8VpuMhfgYlvOAR4yEQkA +AAAAAAJ7q7bJFv8ZAAu+AIAAACDKgWmjIo3UaTTV/R6PkqK1T64LnxlZRf9tJh7hiAAAAI1hWVzhTLvomGANTlYH6fgsZFO2AuBW66ULeny9LlWpwFbhYpuFAh4m2A0A +AAAAAAJ7q7jMQ4vvAAvAQAAAACB9Ku7pD9h73YTzIyUtmRhd11PcOuh5hhjaNJT9GQEAAJHUh49ZtWHT+ATZLHVQDV2sR2p2ooLjOxtDUe4Tjame+oniYjfFAR52QwQA +AAAAAAJ7q7nL9Ke2AAvCgAAAACDCHTenEtuPwEDypyV20X6qoXuscpGY4SQ7yhpzXAAAAIDpth1gGQoAe5to5NBppOyM0UMntfXdNDDcpWSpbOQXO8fjYoQqAh7Z4gsA +AAAAAAJ7q7rcM5CAAAvEwAAAACBxFBOYx7MkXgTGPNcvXgjun0pzjJ6zla+G4AWAJwAAAKaG9Oa4Eq6TC3V0NAW9svYGa01D2s+wrRksZIhlJls5RQflYv/+AR6xgAgA +AAAAAAJ7q7vZmtvkAAvHAAAAACA4IUq2uMaqxdPAnSRFA9PwqA6QVt/4nr+JgZh1jAEAANk4a7ni1XCy7uBOxTipuUceMOQAZGSkOlFKMS+6lHzGNUTmYtMiAh5NyQoA +AAAAAAJ7q7znSEJGAAvJQAAAACDsjUZgokfIsY8CzdVkrsdoHnNcBV0rQEcyhqaP0AAAAPhjFv5jGnZbUdhpLWuAAw+wrEDFsw+Ay9naY8AQlBpxLoznYl9yAR4X1A0A +AAAAAAJ7q73vFEe0AAvLgAAAACBHdel/UPRt3sp+JAM+k87PycGMWOkYj6L3b3RQ6gEAAMHcW5p43wC8GUyIm+OFKou1lPsLeKcbewvDO7zeSDsn8M3oYockAh7K1gsA +AAAAAAJ7q78GJv91AAvNwAAAACBHImjrpC4mATSYCJbWw0g+OZDAGOCL1UoqkizE6wEAAGfnV/Mno8nUlTnR5wnRdgqhQfh6VsIPVwElW1eF/6RgkB3qYvC1Ax6bEAYA +AAAAAAJ7q8ApdSxSAAvQAAAAACANSKMzpEQTVGqkkKlWdw259IfyLockWLp7HHXMjgEAAMcDmtGVVUUZgDYt47tyd4RLyCyywFC2CCR8bkh33beYhF3rYgHSAR6shgkA +AAAAAAJ7q8EvDsvfAAvSQAAAACBRPUBjz77gxdvsR2an5tuZ76pPDZcF/ArO6mdEVgIAAH4a6Yl3dAfKthojYEi7ZHHw+KVi1A6ZzA9xm3vfgF9qp57sYjUUAx7SmQQA +AAAAAAJ7q8JQ7LetAAvUgAAAACAq/bgpi5uEgD0HHM98HOUaGOq0ZeCugzsw2z9+TgAAAA9qsS8CAJdjosDL4yOxiAwuJ6i615uRxLJc3sCf8d4kYeLtYms8Ax6OpAgA +AAAAAAJ7q8NS8hleAAvWwAAAACABpr99ULM9GwAb9PDWU29xE2KJmZMVBHlJIDoHOAEAAPFaV4whiCz4IewJf6KoxGJvxRLd8UupKt9LHULri9sl0CXvYtOtAR4ipwQA +AAAAAAJ7q8ReKkRfAAvZAAAAACDHeelbu/FPVZSXW7PNFILJFSeAY7cUDMKAY6+gVwAAAAmVX+pOxMvIZ0fpgV8jWOC09LyS2A91yqkfTqNNAchDn2HwYjXvAR4qxQAA +AAAAAAJ7q8Ve+lqrAAvbQAAAACDMhd0r56buSDi70fPTrpmVqGceQMotgz+TQ+kaNwAAAM6s6IsbsWDj0o7lkzTplwY0fxu7VC48MI5ZFOQns1kymqLxYvkjAh4eCgMA +AAAAAAJ7q8Zj36UfAAvdgAAAACBwORe5Ao/D3an7X7VkQybwQ4+Wf0GtxmDK9NVKrAAAAI5D3dK9geVULSsRl46SlJexV6+U1Zbku19Si8vyaVWCH97yYkB1Ah6LDwcA +AAAAAAJ7q8dlQ86PAAvfwAAAACDA0X545awNj+e6s/6EeQlCKgguuY5qUDFoY0bAEQAAAPzbtFjxV3FDrRbYE+bX/tGTH4o+jogRssz0JO2ZV0J8vxP0Yi76AR6C/wwA +AAAAAAJ7q8hnOkDwAAviAAAAACAtchY/agQ7hgh6lELISxTWeluCRu7k5YacNJgI5wAAALdo+Pf4f7tm3np3D6ti/1puUkcOgsiWav2apeDo90tAfkz1YqX6AR45EQ0A +AAAAAAJ7q8lzl1KtAAvkQAAAACChdUVjfXZPAx+29nLrnWqOW/PtPNFnvNI48DxELAEAAIVGmXgRtLcDr0h8Smf6leLfQbrfz5IdrLf0z2nQokU7EIn2YrfaAR6CtQcA +AAAAAAJ7q8pzH2+PAAvmgAAAACDOMwmNNuyvzS30/BArvcak5fNblMc1oLemBg3mGAEAAD6+H9CAy+SWM0GCc41Ros+BQJLkzV5WWUi3+ue9KyQmDMD3YrSXAh5hxQ0A +AAAAAAJ7q8tjkWwqAAvowAAAACCxBrCrrC1O3DkFnlzNZ0OX3aHTLSuv3rFwWyN8SgAAAKRM9NAGa5S4llyUIsHqb4wnbiECqtli4EN8WFxjc6JlGP/4YnI5Ah4swwUA +AAAAAAJ7q8xn6ixlAAvrAAAAACB2q0Tt+S+A4p5mB3+s3q81J2Bt2jZHzbrPuSR1swEAAKfIUzMXQXoIq9zQsoi7MrSqyXCCHSjXRWuwj9tw3hzmPjb6YuAIAh65twcA +AAAAAAJ7q81nruzWAAvtQAAAACCR0Gf83ZUp9PaSgbInmVjKDLfUbyJrnb/V4UxbYQEAAHCbl9plugXaykXVFB3eDr6ylGnytGFQYZWQ4l6Yo2W9h3P7Yu0UAh7JLAkA +AAAAAAJ7q85wAk76AAvvgAAAACCUztcNaYkBODFv87Svrzigsd9wIzFoIHj4rgIBgAAAAJYdJR6IE77C+U/jlK8dxYc12lK9oI9cQkwmQg/ZyL5U0a/8YpD7AB460wwA +AAAAAAJ7q894VxMOAAvxwAAAACAjCXjH9u60C+RQbn/VBAV14qArllQNTkpZ8CK/JwAAAAr+Cpu3nV/NG5W3qMrOIYEIf4AllV/iJ4NFm+kR4Wp1k+39YmhnAR56TgoA +AAAAAAJ7q9CVj+vBAAv0AAAAACD7pMquyeNve74JiwMJLVA6Wikr6jsSeZnNeu8AAAAAAObwpML2ROZ9toDRXSRUfETLucll1tt1sAB31/NzOOzMdDD/YldeAR4eMEzG +AAAAAAKQfTOC977JAAv2QAAAACCB6cJrEUtOUTZ2kOVnvFvsn7o5Q6PYRFR5hWwYAAAAAD9k21SBiEdlsoqoWOiRHsG/1LLfbzMMXoOPrg71FK4PzQgAY2yrAh4VNgYA +AAAAAAKQfTSP06mBAAv4gAAAACBVVVTbWqO0rArpCeLI5VKEbywyDx+sLLkZxIJQAQEAAG8O/ljGbDIuAF3TvkdnUq184I1J+jjR8Bk4trUoMtbO1EABY2mXAR4TMwgA +AAAAAAKQfTWlc/TVAAv6wAAAACDoE6F7iTc5gTjSkQGmLAgzlPqRi0CsTq1W29reGQIAAFsd3QC/OGwFLUef9Xt9rmCV8t//1cqEjIBGCl2dQWnmX3YCY5zfAR7gagAA +AAAAAAKQfTaipcqlAAv9AAAAACC2mo5yt9k+VG7PIr5eJvizgRR8FXJGb+IFskNrCgIAACEUXn1sOWsSCqNMrFD5CVSABO3sTKLNh6tRtBvXwr4bPKoDY+PDAh4MAwkA +AAAAAAKQfTeg/fLuAAv/QAAAACD7wJ5SoHJICCZv567Ln29mHx02wFCEIhioALGZIAkAAOCl7q2NWWLI6OZEUipjE6c8vLtbp5+w8sXryem8Fvs6st8EY5MfBB6oGQ4A +AAAAAALWi9sTScUrAAwBgAAAACCWJ2VrDLoII56uT4ZLUOLEnnCBp54x98ezLAAAAAAAAKtK3KmdnrELfoPUXag0nTNR6IMW/ZUc/MUZdvm0xGQOtswFY///Dx7o3wQA +AAAAAALWi9wdcKZHAAwDwAAAACD1tKyVGJYXv0/SMn6ydKbwktVJ0LpSDHRk0n4ElQAAAFf8WoXzDZG0+eMspseICNy29hd/y4nXIUontlCG/1b7YPgGYw8vAx6SDQAA +AAAAAALWi90TC4JLAAwGAAAAACAsEoTsYd3p3+DqsG1I5He4JugEtuqkZQUoEogSSAAAAJhfTYyYdbofr7HHMrDxNtP1AWtRBfXYkmJsLF4l9HW99S8IY/51Ah4xoQYA +AAAAAALWi94hPGd2AAwIQAAAACA0U0z7cEdCDJXEkE03e55oQKmYL/h7AOYbCrmAdgEAAAgazKiveBbgtxLENqjpj4mEl/5dYgLHuetuBhK3cxe1W28JY9LGAR4hIQEA +AAAAAALWi98meGPmAAwKgAAAACAhtFaF4oKhictTwUJkfGPb60wn+NXycC6ZveVCagIAAHQU5xXziyMFIfFGXOp6C25joictoLCOjhpNRUgPQPYoTKsKY5iqAh5GMQwA +AAAAAALWi+AkI2lGAAwMwAAAACBXNUZP3n6fofse5NjgTua3CuUvlOlkyEhUAqJ5uAEAAAtvnfc/Out6I99crvVNJLNJg+7dDr64ZwtGYKwdd+6jbeALY9LKAR68pg0A +AAAAAALWi+Ehvnf1AAwPAAAAACDHR82hqktV2ZDldzj2cPfDS0P7gC9sAD2+nbjfDwAAAEauUq+8VK4rK2Ncf0USE8bBiVaAB4LFrJzTA+NGqMkIDh0NY/20AR5GMgcA +AAAAAALWi+I7g4sJAAwRQAAAACAwpNmP6gBQ8G2qsY3MTLC2wfoS+3djwyuz9rPNbwEAACW44OczOzxc6dxGkAaQ4er7aJsIK/Em3kE/IKv7HRJgb20OY///Dx7lCwEA +AAAAAALWi+Mr1pIhAAwTgAAAACBllrVx244bZCDZFhYKewMx2XfpKEpbC16EkyhyPwMAAKhqbvYEO4ferWFmZJXVzqT1EmnSB8rE5aXxLB01uauP7aAPY0Z/Ax5HjwQA +AAAAAALWi+QpzKX/AAwVwAAAACBBDuso9u1wLB1LpHyGR8HwKwlZggrVHyjinu4gvwAAADPxatdXREOYz0R2AwYkKfGII7gxdcJXAwfDfAOaaPluL88QY4iIAR4G3gAA +AAAAAALWi+U/cETtAAwYAAAAACDY1egQRioArrylNqpsnvG2TwEf0pyKISWimDjF4gAAAELcNrl7G2AStFJ/CbdStoeGRGDx6BAXnEKw5/fPpJ8sNRESY34bAh45UAcA +AAAAAALWi+Y522NHAAwaQAAAACAVvPgVkaHKTSYxyaEJUrDQtIQawsVpGXisFLpxTwIAAMMUMk3hAdn91V0nu2SW3LQjMv/gU6dh71gINLpr0Ujjp04TY8dSAh6Y+QMA +AAAAAALWi+cqDUG3AAwcgAAAACDSwQSrvaOrHigwehVyqGHQ3uECg8M9Yp6eRSHGBAAAAHUzeHbrv0Jtr30CRAPiTPGphUMHHBk6nFkGAgxt7WnNDIkUY1fTAR5sDgMA +AAAAAALWi+gX7DaVAAwewAAAACB7PpyOG2oElQQjRBLw7jg4WYINSuipjDwIuujmPQIAAFpRIn7GokCDD0xcfXZg2mutzuc8+8k0uOFFrnxYI/6CbMUVY9icAh7zsgkA +AAAAAALWi+klKY+ZAAwhAAAAACA+1lVzlPXarf6iU/P4jMFrSllcqbEqB3c1mTj6nQEAAIsjf2hS0swjm2ttshTxf/M2PsG1QG2aM1tW3RWtI6w/Bf8WY59jAh78DAcA +AAAAAALWi+oBk6meAAwjQAAAACCl5oetXcUGENOZvHknqp0ij7AwftehwP4zMut3TwEAALAM4Bt4wIeFnNduwpYdvj+WYGrx687fM+7DkveAZf4pc0WTY0fWAR7YiQkA +AAAAAALWi+ryi+byAAwlgAAAACDkspdgi/3wB97T4Rdza1ARSUzugQs6rvRE2LJ3wAAAAKoauhAccRWAn7h1wMvu2L0lwWbhHrowkf9DAWe8P6vhfnmUYwmxAR6+OgAA +AAAAAALWi+wNYN5RAAwnwAAAACAINrHZF81hlHAUDBI1402akdO+x8rNyVtLn5uANQAAAOrpcqUWTHG1UOMdUdD1pz72NFLHbosNYA3Xtu7w9T5K/L2VYzMeAx5VNgUA +AAAAAALWi+0cTHMKAAwqAAAAACCZQKurX8mS2l1prcqt2NTWU/Ni5y2Fqrfhmilg0QEAAHXKdlGj5DB+2GhEV6M6CbylAgX3uQIFCpvYchOcRyHoOAGXY4inAh55TAEA +AAAAAALWi+4MO1ObAAwsQAAAACAo/vw67xAWQ2dcGsUJzZqEaNMIloPdz87kPV3k4gAAACqO4keHlgmuJILsrLEMAemqs/lPiDhDCDAflO7iXeSRqjWYY///Dx46HQMA +AAAAAALWi+7v+lGTAAwugAAAACCQED/N46czStrLrvD6czzE1jIq9+LO09Xc5MlgQQEAAAJ6WFpMBLVwWajyb/Q4y9g68FjYVP9B2iGsq3dCpeuc33iZY8fMAh6P9AYA +AAAAAALWi+/vYnnSAAwwwAAAACAuyYddfsrRjQviyAKQ4Pxu7nLcdc55iNlLqdrb9gAAAD0geqnpQgWrNRP9G9ii7X6F2rDiFU8iFqszkIw5/QF61LKaY3o5Ah5fqwMA +AAAAAALWi/DfVKURAAwzAAAAACB63E1um4PGknOQoRO/yBNPDzt/iPraxX+2EdnPdAEAAJc6SoatdVrLHn6ogpc9PIXYzrMaaN8qvIPY29Y+yjCJ8PWbY5zsAh7jDQEA +AAAAAALWi/H665O6AAw1QAAAACA5Zyx2LdU80eBZipSzNIqy1ftdudmFxhfcsEa1SgEAABlCEy7R6Uod4cR+zJ+AeHpmjd7fqS39bCSrSCDP8K7QNjidY+24AR467gQA +AAAAAALWi/MCYfmNAAw3gAAAACC+1R2rrgUFDZv+MCZNgenNiArRzFqfO/ZYYPxMYQEAAIBtvrI8TQ6ufxMJhjwk5ODSoictjT21sSGRBvrPbojiXXieYwgtAx5l8AcA +AAAAAALWi/P7bUadAAw5wAAAACDKxlkRbpM1uHsAGrhfmcF2WBV4hIq8SBHW6Dc4hgAAAAh895rN14FsAbeKbYXPK20ZQutMBGt3Glp8tFlW4TdYtK+fY+23AR4IlgEA +AAAAAALWi/UElvHHAAw8AAAAACDG6DuED+7uxPIv+x2AYusDVrnCGW0EOnysWkEBFwAAANZiFuVC5jVIfmRjb3Nxd/OkxBQkcy186gSLkjyuF1eTi/igY97lAR6scwMA +AAAAAALWi/aHN9/xAAw+QAAAACA7dMqVV0gWjEkcmW4+U65Um1WXqiLM3ZE+4UZ+KgAAAINRjG3ANiBbJwkLVcBlCm67RI1JzBC9USMUvFEoSuDLOjSiY6U8AR624gkA +AAAAAALWi/iAtZPNAAxAgAAAACCCjDH1+wAu8mp3/EWnn6HuMG5/lABbmtukaawL+gAAAK1yeNXRRc0tzKYS3j969IcfO5NSgG3SNlo68mwms51wMnyjYySuAR7CZQcA +AAAAAALWi/pyNjmNAAxCwAAAACCkxZuKZo5cfziJ4wsGG9OSt6m+Ht+M52W7UCtBSwAAAP+o/QSX7vhaa9WBN8b2zlGfjI1lhCSt4CAX8YTS1ALpTrykY7bdAB7QIwMA +AAAAAALWi/w+FxCyAAxFAAAAACCrLfcj7TLHTQvmCPahoo7BSGdAqJbA9JoAYaUC3AAAAMt/ZOTzjoFYxGU3pnKI/eAnrBvbr5JyAPd9jszfjJTaLPylY+ESAR5Q8gMA +AAAAAALWi/3t38ngAAxHQAAAACBgPA41oan5UvvWB9bD8+ks82psJ2ZKfE2VeWF8fwEAAJSChU5voqX3ys6cnoWUefF7kQe66lfalcKBfTazIvbSezanY0hHAh6IpgYA +AAAAAALWi/7yCz76AAxJgAAAACDkORoN+r036xDJfRhxkugLy4eVky9SM91vPPnx0QAAAP11t7IauBnygSlOw0NaJBAKLgdOosBT+ttlGlnBi5naiHCoY9JEAR6H6gMA +AAAAAALWi//z6RoFAAxLwAAAACAldeoLAEOnKdP0oSam9S4DZhiVdhSHA400c0vmdQAAAGZnhBbaSvFqXFWhPsMZEt/b695kSugNtgPlCWaU4iQ8a6upYwAzAh5z3AUA +AAAAAALWjADx105YAAxOAAAAACD9HzT/3tL+wORPFzfioxa/PsuqpiurdqqqxUujTwEAADGLEVuiyey7A8yMXZG9FRlLA4s+wV5eEaplfSUycl2ctOqqY0H8AR6m/g0A +AAAAAALWjAHxGBEvAAxQQAAAACAfAcgSKNlSOvb6LWV/yLdK9BjyWmx+9jo9YOnAFAAAAMf5ywJ5xePyLiBhpkOHAV2QAiNOxo+zVZO1lVGZTOs/BTGsY9DKAR40xAYA +AAAAAALWjANMY5ktAAxSgAAAACAjS1l6KsXkEljoEacmOxk1XwltfSJtKQKE5NTntAAAAN8y1BDCTdO7lJR2+ugZu6/nO5Dlxa6iEvuyFTupNImoSW6tY68oAR6IvwAA +AAAAAALWjAU6Ay31AAxUwAAAACAkQFOwCrVmbZuSN8Khf6kIzVvD481j675LSHubKgAAAJbAollNV/c/VU5O6P/nZugmycQpI18aK7jVmimH/YprA6quY6hmAR5OrwQA +AAAAAALWjAcNcwXdAAxXAAAAACAIx3uWdf7E3N9lXxkHXq8YNvk72ty7oa/rHC99LwEAAB0MdkxNiM9L9cFTzHu3LQ8AktqB67H8wRrPA5hDex0wR+SvY+OLAR5n7AwA +AAAAAALWjAj2ZmvhAAxZQAAAACC8n1adipGEWKptwffLqif1htZxM2iWPwA1DoLJ9AAAAFI+aowkBOH5CrFV0PQcK++UltNG2hquXm2wnGTsGKpjJh6xYyYEAR7KOgUA +AAAAAALWjArX0zuXAAxbgAAAACDxduZXgIR/IBdepk4foTbhDb5k1UXIEMKD+DFx+AAAAKjzIhP6bt7jjZTlQXjqqBXepdhefC/kQ9bXDcGO/k6s7FuyYx8CAR5tPAQA +AAAAAALWjAy9z0F3AAxdwAAAACD6UwEd6WXTl4DSRHxmDWpWiEB8XAapSFwRKRKSdgAAAGHEsi+4wYJjOuqzEw3VrOcAEr1nVvSKEFnPcWprMGhD8aCzY3MFAR7A1wYA +AAAAAALWjA6VeQ2aAAxgAAAAACCY0bdeJpRfBV9V1//PQGxz/7VcmpmrfWhEcYbr4AAAAL3PYrdsZPmenqyZEZR1vNaAbOxoZyOP8REh1HsFJHqOsdO0YzPzAB6plAsA +AAAAAALWjBBxGEiwAAxiQAAAACBef9gZasuZh7y6X9dL1Os6djab8T+7XwbSqV9BDQAAAGYY4hw6EFGkuRDW8BND28yX0zgZ7DwEwjtpQZdmLMnmEA+2YyBPAR4N/QkA +AAAAAALWjBJ6QO6hAAxkgAAAACC7fZPd60ZhnS19j6v5tXrMGX98RQRLDfK9dBoANwAAAG3Z7PMhjuI+ow1iTakkLTXuo1tlIZIAxxtdayeIRvmKjWG3Y4GPAR5vaAgA +AAAAAALWjBRIKLrHAAxmwAAAACCqVcYdbS4sPmXC1tvOtlFOUgXqXSCPaCH1ooc3OQAAAEZXrPcswCxV8CZrI3r3QDwFYOPT6Ui3jhcIJj1Bns8ECZC4Y+skAR4cAQMA +AAAAAALWjBZG4g7vAAxpAAAAACAAtiLjLpaRK3ZOWT8r7Y9s21a9ThIMI31vBVCvbAAAAFMRv1bzv9wW57dtsbmpw3dSRRELrsV6fTLhL3OcRnjaE8i5Y3zkAB5KFAkA +AAAAAALWjBgNsJsGAAxrQAAAACCY+TNFg/7pLT9bmT3W/PMr/DxgLQEenrDrnYVxwAAAAOgoafe99rOK3ipUK0XM9cQ+VTFicHGorexmMdvOQdIVi/y6YyA2AR6bHA8A +AAAAAALWjBn6g+fzAAxtgAAAACCPt3RcpoR47UJUa4xDeqyjrauriubwUwCHAkSDgwAAANILLDKxFL6UqWqu5duPL7MmqBjdk5CsLrt1O8v8jMuoyji8Y4wIAR4J8wgA +AAAAAALWjBvkVo1oAAxvwAAAACARJc+1VkubKOlX3ns+LVg4N010spKjulbWjUrf/QoAAJgQvibMmMpnGaJW0bIyGgpZ+ZDJ05FnX9yiNZhnXiR7PoC9Y7WsAx4/qgMA +AAAAAALWjB23/ALDAAxyAAAAACB/pjOw3maICZiaPyV991O178TwQ/y7U5/APhryWQAAANg3qRTRVatPMvymf7MExQ8HcrJJKKWAJaPBX2WKtf+Zybq+YxEdAR4zbwcA +AAAAAALWjB+WXj6HAAx0QAAAACB6w+F1qzu54zSbMTGqkQ+VDe04f6F3VaO9x1wBTAAAALh2RQYjRF+Um11sA9AZSvoHNybfbqxKGGMtnFErQXiQGP2/YxmKAR7/pQ0A +AAAAAALWjCGGQEqzAAx2gAAAACCBYfwupbhlCM38uj3hSpb7hurVeTBpY4gL0GN5eAAAAOO+OqB1Mm9A92BUC4O6bLRpbsn0yUThdLd+VNxJMNIAZDTBY68wAR5PzQYA +AAAAAALWjCNeYeqgAAx4wAAAACCimnO2QuS0JREV6fpwKkOEOWUxJDgh6Ji3d4xOiQAAANSMnTVPCUEpVB/frmN4/YD/QCLj7TRkWIWsw6zUqR6mgGvCY1QgAR7QXA0A +AAAAAALWjCVEJ34eAAx7AAAAACAepqTzf4/41Ep0Xe/9UncN4DlHlzcilPCnAYU0OAAAAOpXN6puiIS7sjBL5FCnbYyvWtgQt+hShUraEehJu3cpr63DY6fpAR5+9wcA +AAAAAALWjCco4cFmAAx9QAAAACBoo/sOS3g/Wz3qFTgODWK0LrzmD1bfFpsl7YPAZAAAAJPJL6xDBKfWlXqdeFliKeHqEIlayDc/HdXx5KHZDXH4L+nEY8AcAR5O0QsA +AAAAAALWjCkIjZbsAAx/gAAAACCKv3gtpn8eLuFtXE1U7nJfB3KFBmWvUn/7+21+nAAAAIxQOK757DUny1laFwFD2XYHsah8dPtpqGZyoYOBSIHeeybGY6tHAR4VLQIA +AAAAAALWjCsIiWBtAAyBwAAAACB1JQQ99klZRxZn4DoSbTg+tzrZuj/nqz5sIp60GAAAAAaLcdgcYbAYSHoe4Kwg1AI5ZKoR1MOwjKjuAohcq5LTvmrHY8YAAR7dIAAA +AAAAAALWjCzg8d1UAAyEAAAAACDegJW/t5e2BHIhKwJbk6gjK9P6HDgZp9yYZ6USIQAAAOTLtNAWvZX6Sj+Lch5H3xWsPwmFwY1S9de0Jv92qOkg/KvIYw32AB6qHAsA +AAAAAALWjC62eObFAAyGQAAAACCAkA1wM40qk6ARqi2RcmDAcGcS7XexsYZRBsjHMQAAADIgOACz6uOpSOuBcXszE7nhhv7miX1o3bi1JOC+O3tEsuLJYz71AB6pDAAA +AAAAAALWjDC7MGH4AAyIgAAAACAQ2c6Tqjok14tgGtrssAqcpVsI51+jtY3GiDWvMQAAAAcO3Ox1onfDDZZnkSF/g+2We+W+vgCoi8Dy3qbAyqUV5iXLY9ERAR71owoA +AAAAAALWjDKMa6kwAAyKwAAAACCj09vMYV7NneuGr76GhRIsPhqBrv+eaHP91VcE/wAAAKHjxf9VLM7A/Ywkjsr9d4pw9Hof5NBKKeO9jQEy7aPuA1zMY1BaAR5UHQcA +AAAAAALWjDSNCr51AAyNAAAAACCPe1xpp2aUImk3eclCKVJun/T4jPiP0jMA/rjJ2gAAAN1i2U52micqaOCHERdoTnAK0LwwbhPbhkTfu3ejxCmUK53NYwDxAB61tgcA +AAAAAALWjDZzn9VBAAyPQAAAACCprtY1a/LFtpCqGZGHIUYeXI+j0WkrErcJTWKz9QAAAOovDIbSJI4ystwNtStl7b/E9nOyebxNgGlL/wDQEEDN79TOY8owAR5F2wQA +AAAAAALWjDg81YZMAAyRgAAAACBwjzC6hqR8qusOA2oVGP5CkomDrXbq+6VRqL4UgAEAACdpT/OaqmGk8Td4VEiajxNp8oP0OavCZbMtK07DesSSCw7QY673AR79iAgA +AAAAAALWjDolqJEuAAyTwAAAACCi1dfCEwZaCb/cfVnhwa8LUH8IbqhUyAQi+iU5qwAAAD9uTxPCO8P997hXp9jmhrB6IdS2tT5yukot8jdfFUMlBVbRY7k2Ah7aAwgA +AAAAAALWjDwKm8zXAAyWAAAAACAeK6oVuMW+LJfk8LrKkGyzzCoFGw+jVPREZH83TgAAAGvnfrQZwmQaqqlnKdb/R6elfStc/M7cSohRGcNVEx1MsIzSY3KRAR5HmwIA +AAAAAALWjD3pfZoFAAyYQAAAACAglgU6ObQ0ZkH4ZjXp+rbuf9DX2j8DLQWnzqtBagAAAC+IyzpcGt7tR/WqnA7bk89W8M3Xvar+Nn3wnTMCerOngdTTY+XzAB6Z8Q4A +AAAAAALWjD/hqaUTAAyagAAAACDjR50bKVypLgOeYOv8EftWxQu2N/nCf7jIUeaOcwAAAK7FoYNGzAL5VfM15TtncBVN9yaqD9RMEuE759jI7aoUhBPVY8IBAR7mEgoA +AAAAAALWjEGd8J6rAAycwAAAACB9pPVKhRKtW0/FG0GxTEn8VRDaSSnvysHatFxXPQAAACpie/LKrmocwPX50HGf3xRTpdSBa/q+z6b//9aii74VmUnWYwWAAR58pAIA +AAAAAALWjEPG7bOYAAyfAAAAACBCcDJ85pSsUpQY62qCVE/FbNX3mO5X66xC2FzLfgAAANqCWOULoq/awJme60KCZn2bPJSBS0a/RVDXmEfvRZWQro/XYzTsAB5R5AEA +AAAAAALWjEWhkBZlAAyhQAAAACBdKR3MVNr//UZvdgZn/e24+A49XsHPbi1DZHbzqgAAAITTbb34DiKDEPDVrvdh2VJmZBITmuIXY9ngp49VoIKaxsvYY2wEAR70zAkA +AAAAAALWjEeRdSJ0AAyjgAAAACAiK3EdLrjd+itRRpzd7TG/DSEEHMZPKsf213NmYwEAAOHIXz18K+Enmh295oT6m1vPmji/HC/QlEa4nHeLQJkWJQzaY02UAR7CVgIA +AAAAAALWjEl10ARPAAylwAAAACDzm3FQbvKkgk46nre1Fl4gF1+UQtXTgvBvq4blTwAAAHBvqtBbjCgtJ+Rgh6nZhHvx6eyHOmwy+HZo2MCSe0yKaUDbY3AgAR4VYAgA +AAAAAALWjEtz5/SAAAyoAAAAACCp5YofrigGQbLjKuHBSmCuf3xbNGlgENFV1Ad9iwAAAPW2pqu7U9IP0lJMtqq2xn7TVMBnquNJF/ln33pGZ3QxKYjcY3gQAR65owgA +AAAAAALWjE1HOFd8AAyqQAAAACCDre6f3ufL/rTMolbKIBZYmR/J188+X+lUiE4cgAAAAO62/PtynHMR7Ogpi+R5xzHuvqs6n1YeaORn0HjtmqZ0lcXdYzkNAR6M8AsA +AAAAAALWjE8lhrLdAAysgAAAACB7zw8uY+W8FpEcHLeTFVxvHIqy5pdcLyiIM7OvgQEAAFu3aVIJ7wdpvi7bOzDpkfJfPLV7KpORsSU23yUq/ckD3fjeY2ioAR5ljAQA +AAAAAALWjFEPGhEaAAyuwAAAACBJ7K5fCKeeunD6/32ODlMUM89PhFJsR6EOTY9FrwAAAMGSPhKpAFkJLJIo+Aw0iP0BZFCJQRLavX2ubcYwwUZIHTvgY++LAR7qDAoA +AAAAAALWjFLu0cwZAAyxAAAAACBM/veVCwiQoEbHUbIQwHE5bHIkksm13eJZPZU9zQAAABtQUoCk394e4eW2ktUZAWrqfLjp7u9PPPEvxAVVPTUPOm/hY93VAB7wDgUA +AAAAAALWjFS5r9b4AAyzQAAAACBxJ7KV5oY6MgEFZNgN5zQyjhzJzV9ILHDEsseQFwAAAGDTkIFHaOeRJwGqrzXyxAQRiAET8aDDIs8Y4a/jAs8D/K7iY4ZJAR7+NgEA +AAAAAALWjFa5EhmWAAy1gAAAACAKNv1O+JTMVI08wTQAHTMe9f2GZKNFQ3Z/aJx2FQEAAASKapLvkFKZnMFX0g0e3VTDqdIGLitLKIt8uxZK8dw6O+3jY/VBAR51vQ4A +AAAAAALWjFi9fqehAAy3wAAAACCFSb/b8mH0EKSepHEedE0MclRrlsmu/ODJ4Q17WgAAAGZIzRuemnGA1LFrR+XQWtAFrMZREoFLId8YMWmHkfTCHC7lY00yAR7oHgoA +AAAAAALWjFp034/CAAy6AAAAACBCwt5lhHfAtEOVhNAFAGiEKt1Q5QebMxvz528JKwAAAAhOB2UxyM2pMnygFU1BW0fulPbMz18oo2mmpBJiZMCIrWvmY+vhAB7EigIA +AAAAAALWjFxHDokuAAy8QAAAACA6z/ZWQuA7KfLTWwk07OMmDGaCw8P8rhF9JSuuJAAAAPKZcskeyuakOqe1h6R4E6p74Bn/njzQVt+9tHaiA8hnoKvnYxRxAR6B5gMA +AAAAAALWjF4k/XB5AAy+gAAAACDfxLndBgxLyngKySQrjMP93KoseVIo3avIDN498AAAAJow55o7SiK1TkDzTmb2rHjR3ZbaxV8+qeCpdiUvaToeYeXoY15AAh6w2wIA +AAAAAALWjF/jJHSCAAzAwAAAACBKt7PiW9kg9fgE4tEutG1BusCcwaPZUVmotLa9lwAAAEw6zBcrsysZVqsTvXVvd/Yvgxr8+k9XWvC/gq7lhuUEqxTqY+muAR5CNAIA +AAAAAALWjGHimSurAAzDAAAAACCc9269QbUh2JTfAGnSGU+EtBmd2PcSk1S2IWF5pAAAAC/AVjlk0jJqKruiJMsPjkRuCoIa16EO1B4om6duDztLi1nrY3D4AB7IQgIA +AAAAAALWjGPc4MTfAAzFQAAAACBwZOLrvAMIp7ZHQEc/ODnKSmy3KVQs9oJ2L3SsHgEAADGQKSZhyfKKQaDaJ1x/zZvuDZocW+Vno851QOrluED59p7sYxd8AR7u1gMA +AAAAAALWjGXEry+gAAzHgAAAACAJupG4Dd5CLZX/rSxBnYgPDGJX8rI87emIuPCqFQAAAHtU/kWTlNsS+cJSNLvJITSB7FwZCX4eZ99uDOibVHR1F9ztYxtCAR4WjgcA +AAAAAALWjGewZk/5AAzJwAAAACABatUZDT5N7aZi+j22/U1koggHgI1vii4vARYEagAAAH0DGu50Nd0r9H5Pf1WrnJK7JSyxaW38X//vXmmpRBufIyHvY8qaAR5GHwMA +AAAAAALWjGlvECO4AAzMAAAAACBvvcuUbBkSf5fyALeuQGo3tW/YUq00iM/07+9UTAAAAC2JGRGLZYaz1QYKa6hp0Vvd6eTyr9H3qQLASLhqM5c5cFzwYy0oAR4OrA4A +AAAAAALWjGtkl8giAAzOQAAAACDzfr8Lyc7/JoVM2nk1SJhfmi11l8ZJ2H2Pi/E42AAAAG+zCN7XkhzxrIM4iGeOsfvwcr65EXEr/md6S3DDgIaLkJ7xY5T8AB6/YwMA +AAAAAALWjG0yeXw4AAzQgAAAACBStJmIWVWPYjl9bfPX1XR0SP/aJVzg3p5KsA187wAAALkxmlDpaq1ClSBeJR2a7S4dkd0liTgtvgm2ccgQKWbz4dzyY5t4AR7xWgUA +AAAAAALWjG8UFOqXAAzSwAAAACDK5+mrJHUSEKMfPAlyjgrFkfYtxxTBlPT+vzfTSAEAAOvzSF7NHk7fZTYgO38yjhBwK5sqAS5Zh8EaAGRRVzOJdgv0YxqxAR7bmA0A +AAAAAALWjHClwzWkAAzVAAAAACBGdGfYnSBIevQ/I094iPTjv5CHGnFLS0G7NFsjWQEAADBwjvy7e8vuj18C+Isk2F06CHXGlk9UZ3y/Hg1LIYFEsDf1Y7ioAR7OeAcA +AAAAAALWjHKQLHylAAzXQAAAACBfpWucTDR8OJFsTaI4oRpcgXzpdLFvJ3tiN+yeQgEAAJNCp1Qx/dFoKtfEx3O7VximicVxx7rPX78wL7zugtO4IYL2YxQ4AR70UgEA +AAAAAALWjHSMKBvaAAzZgAAAACCx2qcAJWbJjqwSfar6sdsm4o674tRD8P/4WWk02AAAADoQljmuiC8sVTiZ943/+FVmK1nV2S3EKjeWhU6/zvZATMj3Y5bUAB5SBAIA +AAAAAALWjHZBmgSaAAzbwAAAACDU1xsv5Z+Bdil2j93LVuG27dOASxaUhmpuWiXkuAAAAF++tFRPWH8Z0BtOUVoP+6QzbGlZkf51BMee5UufJTMIpkH5Y4kLAR5g1AIA +AAAAAALWjHf4Z8BCAAzeAAAAACDqPtnmYtG3/u1QPz+sOxwk0BrBQhoc0naxevv95wEAAMFSe4ezqUnerkcV2fbsqDi8sSNzkEWYwylPeyotEy10fXf6Y7FtAR4XBQEA +AAAAAALWjHmjnoweAAzgQAAAACCxiZ+KG2CcZwGy5+LrqcYyMzWaQDgOGdXQkY2mDwEAABkHltkwjSjoUqBagNucAJCJjixDxrw+AHMJUo0MinW+BrL7Y1FwAR4yWgQA +AAAAAALWjHth7Pk1AAzigAAAACANysQxSgrXKVrTC1KwYu5tDujor+yBrpILUk6u7QAAAOl/Ds556lFPW++7GLuUThmfLpxiGD/o9Yqv48R7cPkhN/X8Yx3NAR5HpgkA +AAAAAALWjH0hAmpSAAzkwAAAACCiyQskMPYsx60qKWw2YZMX4r/S0j4u5PmuAREgNQEAAMcBXiIrA/dZftAnKl1r5tv3doNCKtrWA2wVP1EsvIGBbzH+Y2iSAR5zAw8A +AAAAAALWjH7W1ERNAAznAAAAACAs1jiU9bv5YKZ17X39JdLz9PyTq5c+kCutDW86sAAAABGGjqCEKuBd3xW56FzMZk8BrbO4CnBFSVztsxUSNMQtsW7/Y/DgAB7OJwoA +AAAAAALWjICiq7ZxAAzpQAAAACC22MHvZ3df72GgMINaMtazWq9DhAg6OcU7cvJCvAAAAFOqKOGrYC4gllQVLpIFlmRfNqRFvOHxV1GxTZXuAT99b60AZGP7AB7ANQIA +AAAAAALWjIJsG5YPAAzrgAAAACC2bYQK7bC5gjGNWcwdDxEl4qVc4G7q+LxszlI/PQAAABW66GrX3wXEpN/+feRAXN/f0NpSSPPGtQ5jc15E5SBShu4BZNgeAR5yZQMA +AAAAAALWjINtIzLdAAztwAAAACBNJz+hOvlenPRVUO0fYr3qwCat00+klZPaOPSAKwAAAO1pPlousjtCM6ah+Huv/tV3xrHwPBh/1uAI8KmZlMKM+SgDZB63AR79+wYA +AAAAAALWjIR8BF7OAAzwAAAAACCPWNN+TxCMpqKVu9JOlmi1TgMfpX9H1nGSZl6kXgAAAPQNLYSJkzPHYFan0vQf/yL0mVEUiARYtH8dNDYeHRjcMFsEZETxAR66vgkA +AAAAAALWjIZBiIiCAAzyQAABACCdd5oZFyeNhSBdZfdIx7y4k/BZozK/OwVSjLpXKAAAAHoRaupGDAONomEoTfNG6FYrwYo0na4sB3ocPL1PSe1EX5kFZJQVAR62IAcA +AAAAAALWjIbsWwtsAAz0gAAAACBL1O4WYmr5ncFU/x7nP+GfG25hQcXOHKlO0sryhw4AABdRJ5/taM4HkJLO9VmiIrB3iruop5besoxStAE1cVAWo62IZFVVBR7ngAUA +AAAAAALWjIhzqkRXAAz2wAAAACAX6xRAV37aE5V0B/zd38oJhrczJ3i4BBwoUGNgewEAAG24E2sS0OhfmRQYRZDLh/g7WFyEXX/+a0NSD/W+fGDtJeGJZMKJAR7BVg0A +AAAAAALWjIomyAw1AAz5AAAAACAhfPz8L96Aa2ujxWE9hqMG0Z1F1TYErUB7gRYrqAAAAM9QJTtnLad+TO8YXY61jTizPrpLBqZ9FMNhsY2vCgttARuLZCmAAR5e3QMA +AAAAAALWjIwJOHLlAAz7QAAAACCjGCDRYrwyyiDRgjH726BuhUptYx4Bz5hLBfRsgQAAALElkfuCmQSKl/xbfXWtRfkWBoFawvBi6TBB4uwojEx9xVOMZKV2AR5AwgcA +AAAAAALWjI3MxBBPAAz9gAAAACAwRIpzggKZoGFjOwfP8lvMwQwZmZzwTA/bM0y3wQAAAK41/YtdzgE46ZvhqPkg4mbo2mmymrC3bMXylTElr/WrFo2NZPj7AB7GhAMA +AAAAAALWjI9yZcUCAAz/wAAAACAhctaI65fT1j/Jeq9Vo7zlzGR+ha7OSz4Wx9jmYgEAADk7hKFu2UOvnwrnr7ON+j+/BNAAaLd0CbleweNqskO2/caOZIWzAR6ewQIA +AAAAAALWjJFHxZ1WAA0CAAAAACBAvgTGZLmo9roMESgjrFSvIivMb3jwfr46/pvHQgAAALChdcsK2eECDt18rxm92VnPGpyR1tuwo8fMc4kDOSYiXvmPZK3XAB72Zw4A +AAAAAALWjJMf0WaSAA0EQAAAACBLip8xVWvZymQhXjUjGkWli57iW2jojUK32bKaWQMAADfhoW5bcV0A9C7sJhPMJVCfKtji33e6+6heHOBRTjxbIDmRZPK1AR7BSwcA +AAAAAALWjJUBPWvAAA0GgAAAACCfxZlCK6Xrly8dPjSdVa9s0DAlAGkR+OaIEncpcQEAAIKWCaKL8YaUWHwZTJFVpjLf4jQDr/auefaIQlDplGp9KYGSZNh7AR4tFAIA +AAAAAALWjJbiwpK4AA0IwAAAACBZG3RxY+tfvUWWP7ox8jaFd0uksflwz2G3nVicEwIAAI7BxRhl857dKIbtTgn9Cl/ZvHs0SJ0aMtMNHkYY9p4YksWTZK3sAR5jkAgA +AAAAAALWjJiylKKcAA0LAAAAACDistDYW4nzZwhzOL3m4cu1Hy6mS8Iy+tyNnDdEbgAAAGKdxSqJUXijKoo6HnTqeLUiBJl2kiYgsOb7/V7BXDN8PAmVZH6lCR7ZJQkA +AAAAAALWjJqAXmBOAA0NQAAAACD/Vr1E3klg+UtLNqJTOBiN8jWV3nHRQsmB0wpvQwAAAPI0oX5aiE4ZBWXBclU02sI/CylbFI7zJkVZOISuZA4gY0GWZL3VAB7uZQgA +AAAAAALWjJw5WdC2AA0PgAAAACBt5vIyKISZuomtGuSuNnCttF/0HydugiRStY0TAAAAANQ1Pw1USK6CITcIWy/JCruWH0O4ygCBJpoQJ76vryQKo4GXZDAyAR7/7AsA +AAAAAALWjJ4PwuNzAA0RwAAAACCC9MPjSBNEIcWWPVetiz2artwnIsz3Vwc/16nlOwEAAHjgOO/LpANkfbDZwGlMaHW5TiIYh0JvuoI6x/LflujfucyYZLVnAR41hwYA +AAAAAALWjJ+kygcEAA0UAAAAACAB8NeiXrlSwUQVpL2t63agnI6DEhJzFxTCTNlauAAAAGwNgFJyMtNKhHZuJB/esMBnWYWPCKuuMwqtgL1fC9qDif6ZZCj1AB5HOw4A +AAAAAALWjKFvRGXzAA0WQAAAACD44+OM5hhHTJVxMCN93rtjDaxhrN6PckB13AEBqQAAAM6+C/wUjododCkP3l0ehTmGrihB3sLuyp0ZN5s3HFMZaDmbZFLzAB6BZgUA +AAAAAALWjKM+7B+8AA0YgAAAACB1h1Cj2kizJw3ftPBB+/fgvBkBITbJnpXwockEKgAAAEYsV6xLz0vQu1M5xQpgytw9hohBexyBuyrnRTw77r0TpHqcZEtjAR5w+goA +AAAAAALWjKTpzs//AA0awAAAACC3m5kfsnta8sFgBhcMsDD+1OSZD6FLjqWaqYyqKgEAAEiANpPdKsdnhf+/MVYdbDNrR7kmREuyKQ0d08PcyYQY/rKdZBmXAR6HygwA +AAAAAALWjKa6fJCWAA0dAAAAACA28K2SkyimHUvJMx8xyyANpjMlFylnkZc9n2sA6AAAAF7rd90ja1xe+ADYvSJ1cKm2TR0majraYRki10Je6t676ueeZAj4AB622QkA +AAAAAALWjKh11zRUAA0fQAAAACDas0g4dr5Pd25o8CTTsK0gVaofTUrGAiB3vigCPQAAAKnvhCqnslco4OJCzBnjsFnnqZBTCIrKQFZbtLDs5s0LpySgZPxfAR6zLQAA +AAAAAALWjKoe93WpAA0hgAAAACAXjS93mhC5mCihRKrJC80jAL4Q+mWWQ7feHqiulQAAAEm5WMjgwJ19/arA41IsRyeY28bnM5WQX+g7iHL0bgL8TluhZAi+AR7PRAQA +AAAAAALWjKvDPRwyAA0jwAAAACBtHhjqMU+oyH2lXAo1SwVm2MvyS8fxtoY8XUdRlgAAABNyG+HQOwibpyQ9ZlogoJazqgSN/wdq3tYl/hVt5+JzgY2iZGRHAR5xWgMA +AAAAAALWjK1ZodA6AA0mAAAAACDNcwVwL6dBLo8+TW8vACJlZxW2BArUi3MONuNZlQAAAKmyWNerh71MT1PWA2uQuX4YzBOVCEcD/RBjr/LH2Ws0brqjZOOiAR50dQYA +AAAAAALWjK8cEsFFAA0oQAAAACBA8LTrzfAJvbthig/6+P+nlvyT4NoshLUW+XAhdwEAAGQ7lrHSw0qonhxJtN+YrZYvRzksLzoCi/9wlZU06NCzmP+kZN+8AR6znA0A +AAAAAALWjLEHEXmoAA0qgAAAACAIwvMdgfLfPwjbprcmQAlGKIPnx/KdEwRMhzHvngEAAFD9MpIskOMtAisL/Re8T5GRn8qLITtUBfJziHPiiEX/4TumZPbjAR72fwsA +AAAAAALWjLK9wXokAA0swAAAACB38ZebYJaJ092mP7upVpgSZpdAwTN5WX0XmY0HDgEAAEbQIsenq/PF3TZW4UKw88+e0Qz/koNkptO1AizDi5e1M3enZKwjAR4y6AoA +AAAAAALWjLSLXS2aAA0vAAAAACDY1xVMHfqYZu7JCIK4st3HJYSB7tPTZYKwhIDemwAAAEMoi6fm5WqXoY7HXzjaiJwM1Jn4M2XoEEC0V4F2x6YOBMCoZBjECh4SNAkA +AAAAAALWjLZZuk+aAA0xQAAAACAqqFnEMLahMPYT6tIxDqvMg/bHZQLSO3BrLeBKHwAAAHemBUj+JsQxhC3PpIRTNksUZsRM/04nmWgbgDg9M26NPvKpZMTGAB4uOwgA +AAAAAALWjLgXNzoiAA0zgAAAACALibig91g5k9dRDR4LqZ4fVHxXtJVwjj51Alu0lQAAANulOljDgphKtr0RByGc+QbeTf5C2toKgUChnOqfP+GlhSirZCyBAR5S8QUA +AAAAAALWjLnaKZbQAA01wAAAACCCW9tD2YeTi06HcS0mvSsZXrowL4wGlQpizUwdPgEAAH7myV7aGJMGWEfu1r36nsRacIHjicO6fF80wl1XmExTdlusZFp2AR4xpw0A +AAAAAALWjLun+NnfAA04AAAAACDOcSu35aPKiV7G23EP9rIhxvqv87ylyZvSPcZSCQAAAAVrTNSqGv3725YXA75bDtQEqKQzCZg6KeKb01RsuFn1nJCtZAj+AB7cCA4A +AAAAAALWjL1kAq1yAA06QAAAACDoVZMDfo08+JRs1q5fd1XC/P9x4oifcvWwreA1kgAAAAMjtTU46YWCULkkeG4ESPgvyOokrSh/nWb5vtHk55tewsquZPc2AR5BCwAA +AAAAAALWjL8noskNAA08gAAAACBoAeFo4SwmKdxtMUGEKizQxzxOL6BbdrnQskLeAQAAAMMhkCt4XaOVwuopJyvoh6rs1/ubXdD2IYklHU5Rm1drGA2wZPZCAR6hAQAA +AAAAAALWjMDHsT2nAA0+wAAAACDc6RbjMTGvEsNHlAo405awVkQW1DRm9LwGZvYE/gAAAJJVf8nB3oeQyRnaiwzUupSUnwyNo0y8HQJAq/IVOJ+l7UexZJ5VDB4eCwsA +AAAAAALWjMJ8V08WAA1BAAAAACBzvDjlVkNjb+E/eEAHsvkkMUIJSI6dGoEurnz2EAAAANaj+ta7tZMcB66/3Da2x1nWU67Ea9f1mmv0hLYNrpJSL4qyZGM2AR71SwgA +AAAAAALWjMQ0bsVUAA1DQAAAACAd94tOuEe2LMAuxuUTL3YUHiXnsiYWmT/moACC4QAAAMsu81g+wwSreiAzrMur4yng20wSTMvov6vBwnMDSjzJkcizZPTxAB7GrAIA +AAAAAALWjMX1YuHKAA1FgAAAACB/liNU5GGlCeENcwETNh5fB609pD2RnU6YwDUBqwAAAN8WumBwlYhdfJtnfGmRzeQG/yg+Ag0yUwBN0AYQYNSaLwG1ZAsDAR5wCwsA +AAAAAALWjMfjETYxAA1HwAAAACCyDUK/RE6XbZCgOMBwoepCrs3W8iwKiDB89mMPyAAAAFkB9X04WPVjRKn4iaI6jwrZYV2Pc7azXpoAldRKYK0AwEG2ZFjHAB6IQgIA +AAAAAALWjMna70mxAA1KAAAAACC68zzhMDBRveG9tdmXyaj5KxSI+8zs1iOR44LJ2wAAAJwu4M0xthmAc0VXJ+BD646rwLsshIad+FTRKF3KUjei34e3ZGVSAR7s2QsA +AAAAAALWjMt83Xm+AA1MQAAAACAnNuo29kLF7G4P9HW15nydA259cXGvhu2WPyb6uQAAAHtpZQUid/gsb23KWH2A7Quw9tN8m1VjwKQQCqWAV5Gi/bm4ZCDuDx5ldAQA +AAAAAALWjM0igODhAA1OgAAAACDMjKmIwrM7KwXU27oI6D8Xy+8Bw166OTukxeU2qgAAAFtryIRD9n7jIfzqfOXxgcrFYsJucFehMkrXyCvQfNpba+G5ZJYKAR5JBQYA +AAAAAALWjM8CZvXBAA1QwAAAACDqVT8eXKa+S5IaPYnPVqi6Tw/vmR5jYMTKz/5ICgAAAH+p9LbANHMtE+YL1e/4/EYJu7cyowSTmzwpihgFxgECliW7ZFEIAR6jKwUA +AAAAAALWjNDmiYCVAA1TAAAAACBH8kjO0bN0b+NKMv6/LCjcRxSmIQPjrHCX1zG4vAAAAMrxdmvGnHtEWHvjUcKZtIhzxknGtGOBpqZ5Fs4agXLtnmS8ZCw/AR5jRAwA +AAAAAALWjNLGvA6tAA1VQAAAACAlESv9eru2KoD+eL89QTbwnW2eSOFmXOEjbHvLGgAAAHuDsvtQMsU2G+y25sb+rFf31kq2YBg/nH25hqClRWiKJqe9ZLlFAR6cgQMA +AAAAAALWjNR68Y8ZAA1XgAAAACC/yRzM7+HfJ5SYKUEOUNDxI2hFvUUECSoyHwhNbQAAAGmNN4FPjQnmBmLYo36FD8oARmpFz+xjbbi7qZrn2iVH3tq+ZAIeAR5FJwwA +AAAAAALWjNZlj+9HAA1ZwAAAACC2huXEimGgls8R+0x5TYdrWq4noeQsChkvmRWLSgAAABrjD3WZH7pALmsA6Ei5O/RE8XrHxd+r6aYWU2sfN6lb6R3AZEpqAR5XSA0A +AAAAAALWjNhJiwF9AA1cAAAAACBq5/974DNLlQ5ZgNF5gXom5PayrtmP2U06vUxALQEAAE6mnn4wHPAD/GVyT9O+NGDLbEpRt6xGxzYxh7t+Zqyfz1zBZP0iAR5DAQIA +AAAAAALWjNoEkjwCAA1eQAAAACCzPrb5mX7j3pyIjYSMCSOisTO+LHZIEoOJNSl2KQAAADpvivfxM0MZ86R/acRK/3Wi0v0APY1FKj95iNjiJXtKx6LCZFYAAR7kHgkA +AAAAAALWjNvIOkkfAA1ggAAAACDgbEoA2P0JCbT89srT8tHy88Xy9IO2d+9ohfWxWwAAACdhY1ZfmDjkPc8megLAwa8PokdZV9ihngQIR3oqwus6hs7DZMwGAR5LZgwA +AAAAAALWjN1vWlPeAA1iwAAAACBFoPIkyqHdmJty+0Z/gm0JtT400dHKJVfaRUvKtgAAAL1WN2kEGaVZEloidhVslCNX8JNTQReJ258czYuU1ex+Zg3FZEPqAB5WRQkA +AAAAAALWjN8vh9L3AA1lAAAAACCHrwAXDwVfdzrCikPEfBafTobdY/sUzaPux9KTywAAAF+mhNExE9NB5a2IDhl8YWI6xgoUGIioatMNhHZO8qCC1FDGZMnAAR7k+AMA +AAAAAALWjOD0kBw6AA1nQAAAACBR5QQk2mmlafcG7qPv27oIf9p+7Mxel0U6iaig8QAAAPflg8U/0zbcpuIjS7+xBaPr/GWiKuE9ksDHzbQ919vauY/HZEJIAR7RPw0A +AAAAAALWjOLDkV8sAA1pgAAAACCGH7CcJzZhbAs9Nio7WBzAVQPl0bRBiQUwItaGQwAAAFFVAEOKI6gFe8km0KPUU/9o3L6OoVYZ66WmNLF24aAAeNPIZETNAR50rQoA +AAAAAALWjOR9JOFVAA1rwAAAACBh9Pt/XCfEqh4gZkysojTsS518Djm77jqG7cx6lwEAAP0lCGMqnfdDUA/SNySYjqXpKUeX7j3Redqt8LbfObXjnRDKZKK3AR41BQ0A +AAAAAALWjOZVup9uAA1uAAAAACCMXJyRMnrGpkE/qlCJTIXgkutQptIwVx3VHiwNogAAAGDWq4HRbtlF8WGvHH1e/SAtPOjAc8nThqzTRfjSHAuYPVbLZCQ/AR5TUwQA +AAAAAALWjOgYEqGNAA1wQAAAACCfE+DULIP8vJRJwaThPkW6Cdl7DKMy5EINjWithgAAAED/V68UbQgTTrH/kM5dOu7fNuJcklcBksAu9nH+tHP5HpPMZKUTAR7dXwYA +AAAAAALWjOoImvjjAA1ygAAAACCI92BErWSgO2bro58C2LLoJICdOPFOn6oVqfbsIAEAAIMqjrkC4W3EPiEzu9iP3CUey5sSoO3/gvNvNQcUiwDA/+TNZMg/AR5vOwMA +AAAAAALWjOvKsSqfAA10wAAAACBhZtV2oBrys2D/2TFB9nuuYZon+H6zJU7JUfpy9gAAAIjZkAHxsVz0TerWMBZ4E6fXm0USUsfJzDnvwR8hweGDuyvPZMgHAh6Pqw4A +AAAAAALWjO2ODFqRAA13AAAAACCqFsbCnE7vOhJUnHxV4gKGAysaWQDeUWI4HBzGEwAAAKUVr2h1FEshxmQtIpEs3W3SwmBdtiExdptXXznA60zInm3QZFTzAB5PJQwA +AAAAAALWjO9pYQx5AA15QAAAACBaSUUOfhASCA+sPxhjyPzfU8RQ7YzRTyxY819eEwEAAE9SO45gYFrP5iGc6RXJ/Euqb4O5KwEz7g+Ttm5rXGrQnbDRZPEtAR6xAQMA +AAAAAALWjPEe0tT6AA17gAAAACDz5rkBffktox3WsGtsrSwH6l3+d2kX3YDxVjJO5gAAAFZkIzlxPfF+rNqVo95pXGcnKf8WMp8ZFxnzgIza3GgOdunSZJoXAR4bKQkA +AAAAAALWjPLlAagMAA19wAAAACDd8H5nOd/K89HEchFnOsYgqd0FfU5HYvWzFVjDZwEAAJtS4yg/eejICkpsY6xmzdC8Cmr+FRC/+RFenr+4kNfRaCfUZLy5AR6K8gwA +AAAAAALWjPSsV10xAA2AAAAAACAoeyG9GjKoZe1sx57nDu4fgKZ0A9IkIEXZoopdeQAAAGb0rFnQ0goL6ZAcIen08hr6ari8DPngghuNut/65K3tBmPVZIINAR6TtgYA +AAAAAALWjPZZ0NqjAA2CQAAAACAVT4d3uCUfQypOkFfB+cdPV2HSVA39L/45J+LpQgAAAAH4biNUKQ52yW3v+8kfez2lNciFqc3BIT+E9idLh4YkOKLWZAJSAR4YmQwA +AAAAAALWjPgnLZIUAA2EgAAAACBE8PLF7Vo9xobzNb9agLOXRTnraGC2A59ZQdEJdwAAACoeS3LuSmVSuz25C8btAFOO1VLztax93ptcz3P1qHy6n+PXZMsCAR7YKgIA +AAAAAALWjPoYQb95AA2GwAAAACAO4NMeFbGeLk/aTXS7Qcu7Im+aqAmRZg+yAXANuwAAAFiWxZWPSmyxTOlT6rN53BfbrkPf/fTdwj4MECB4pTC1RSPZZDzfAB6eqAcA +AAAAAALWjPv894gkAA2JAAAAACBKXCnafSFKd0bx+sPg9DZhneYpNrOcdbc9V+FIWQAAAOusMZOL+KN1yPf1u34XjVD5LVge/ZvBzWS7ZxiIECktK2DaZDqzAR53UgYA +AAAAAALWjP2k647mAA2LQAAAACAS4myGeooWFRggFTyXLb1/OG7oJfJghVOvf8gx9wAAAKtgfsY7fe4FhQQ2RQmoCGZ4jKmJLGAKQphS/z+W3GPGJo7bZPxUAR6ZEgsA +AAAAAALWjP+Iw4qdAA2NgAAAACBCxWBVTnWgv4TGlov5po028GwNOKHilQHTPqsR0QEAAE/rInCGTiq2i/eKnGUl8QnqrhsmNqug0CyeZLMpbBnH3crcZPi0Ah6mNwgA +AAAAAALWjQFaIIPeAA2PwAAAACB5Bwumu/lr7m5EZir9H/rbGF93hqPVsosTBltgLAAAAKG+8/JsJbAHUulYB1aNmt+onYLn8kDAhP3FJ5DIieYB8QLeZH4wAR5FmAoA +AAAAAALWjQMOrbQGAA2SAAAAACAPtPSDsSOYJ7bxi4ur1rvf9+S79Ehkg3vD+X/BDgAAADCdtqfgznxuFjHRsFRTIUrw9oe/lF6Hl3PFi2zXBaUJT0jfZI3nAR5ucgYA +AAAAAALWjQSz7Lf2AA2UQAAAACCuqnyEE6gyQNubBHGAc2FFOBs31UBvJBbg8tBVkQAAADMe0Hb5EvjqwGFSDOrH4nziTbrR1Q/Xb2ITshnK+FWOyXfgZNfrAB4DjgYA +AAAAAALWjQbVs1PbAA2WgAAAACAkSwPqmvI11IiPNYJdp1CdGQbWF/OhIlVbqmxtJgEAAPX7xPJoeCtl4K6iWaWup46pTah3KkaumYQFJxDlwljdA7PhZNJMAR6wZQEA +AAAAAALWjQiKLtXNAA2YwAAAACAhvXyf+oE8p4BfDOO6nDxzFYYUOrb1SlgpGz6b1wAAAJT9NjstwI7T2nfsEwFAP6VB/r50pBGP7B0K94iDtoax0PLiZAWYAR7pDwgA +AAAAAALWjQpF8CwPAA2bAAAAACCClEEkiMTjRvDsFxOL82LcXJXxK+gQmrk3iS4UewAAAKq1b0Ayj/45Mb6NH0Fw0t8ztIbI1lTTmIvf4R4yo3tUgirkZBwjAR7X8AUA +AAAAAALWjQvwhzu+AA2dQAAAACBD+niNx2Mc1gxYrU4mMD9YbnCXiKtXzMUi8qHTZgAAAJpnloOaGfi3pNEy8EBYe6XMnAJHPUhYJ8frgePH/fcm81/lZHEcAR4DxgoA +AAAAAALWjQ2fDhf7AA2fgAAAACDQw4rxtViCux2Q++uagGWm11n4kdwfgZr/YO6ynwAAAPUp3ZbS/X7xWzWuf/O2aQlUakA/Q7zSSC4Yo/2CYU3F3JzmZCVLAR6ccwEA +AAAAAALWjQ8ZpMRgAA2hwAAAACDujKJ2DyFqCLjgz5RRwSS1X44LY6T6U6mAMV0eDwEAACuuTWDb476GKgHQDi4VC3HV8VM8dr+i1ZL4C0Wo1lxm8CXoZCTUAR4cFwEA +AAAAAALWjRAUs8KOAA2kAAAAACDlyYgtUZ/6po2y16/ykyT/j7S4yVfFIdEMajUkYgEAAI8X9z9sBAKYZqKjsq69OzjtHqpl2BhKzM3FfokWjMy6qV/pZAhDAh6L/QwA +AAAAAALWjRD56OgVAA2mQAAAACBUoKxDYbPQSP4L4wL8ElFkiLb+pdwWBbGUoL0okQAAAATWc6YtRVzACyysAlQRdvDdsU4lj3oxpuC1xThl9WY0nZTqZJK2Ah7GBQgA +AAAAAALWjRHVmNjcAA2ogAAAACDg5nIgTHspR3D9cecohvaKlWC06mGZOkTU3QwhWwMAADvFUi3+sV1vLM+j00IC1zJttkvfQYmAO6wp+FcuOgg3Ms3rZOrfAx6dmQkA +AAAAAALWjRLiZL1nAA2qwAAAACC6oFQ1liCSjsA8edyXsNaR6PcXzYKA90tFcVE+8AAAAAk+N0ioe8JIgipe7EDwEhHppB2DHnXSW9khdw7Ys92g8QjtZHoIAh4tGAMA +AAAAAALWjRPT39nqAA2tAAAAACC5IEsqnrGJfXkiyRwyl19CFCBKTqjiw0RU0481DgIAAMP5h6XMkw+ylO9bllJQ9hh+xWeHYbUanrBgPG2AFgB0OVXuZGEwAx61fAEA +AAAAAALWjRTDwJY2AA2vQAAAACCXou1+WzHAclY4MCFUnDV6cChTd+lLVvqjkINxSgMAAACKf523LxY7uHKvt79kWJBOcXOpeiMH9A/N1+b5QyL5/Z/vZEyMAx4qkgAA +AAAAAALWjRW1xEYHAA2xgAAAACA8vluf/kvPLEptOYBoTMRD1BmN5F7AQL9OsCjAhgEAADcjyhVxRADbEqruIG2hcP405jrmxhP6KtdlJ3AtPAlIs93wZJ73Ah6+LAIA +AAAAAALWjRarsScAAA2zwAAAACA84txnpCpe+QVil8/RpNIIhqDfWJIZCO9EE/guCQEAAE26QvjfnFwQYuaAbNafpj/OxQf32zHeummYEsXJG6qC3hzyZM8cAh52nggA +AAAAAALWjReRmK9BAA22AAAAACCjF8lBGQ+UKqaU/j33WqWeVMsWmak1SuqlibJJswIAABfwDIhjZ5mdr0XhxH4Ak19LanrtWuE+A8SeabynaRPP31vzZF6AAh72TgsA +AAAAAALWjRiT/I80AA24QAAAACCKLSQzdbJb7b4fskXaZcPc96vbFl5jcZs3vlp/BQEAAMYaPP82NcAQdLgfD8BW5OeyqZNMJCNc/ckXVnXq7lH1mp70ZBTcAh46XwAA +AAAAAALWjRmD/qRKAA26gAAAACDphX05ov4lbtWTfaBHeW3anP1nUaSVw/l7IzascwAAAJv+7LxIJuK+fjFCZN6vcG6kk0YxfHLYE7ouECXFjaju/dr1ZAgrAh4tbAAA +AAAAAALWjRt3lgerAA28wAAAACBQ6dLLYdfcWOMtgI3sVq6/D6QIkkqaxugcSB/RqQAAAIWHu0BGMdp4h+XFNtN2JEcOO76MjGIec9z18TL01e+YYjk4ZVYPAR4Q6wEA +AAAAAALWjR1kYhaXAA2/AAAAACC3M4yvM2Q20m1k8mcDf950l7Q1ZmgKyBcAhNnsrgAAAEsjmQs53YHkXeXVRt9CY32B2cmoA/JhhfhV5lEsMrTBnXU5ZWpWAR5ppQoA +AAAAAALWjR5ch7jPAA3BQAAAACBtl49JqCILxBgeZZamGyEfzcRLyc3PyjKPRmV6BQEAAJKHkRnR8lHeZwCmVgOjUbQEoXq/Hy96hp2ctyrohU8Tnr46ZQvBAh7VtwgA +AAAAAALWjR9L8hCzAA3DgAAAACDt2Cnq8X7V2QKD9BCzk7r8JE931JoH6FzWOJNhFgAAADzutziTXr+CavEvUacvAaG3XHA/VdZUvrhUN4UKwTQRx/07ZS2DAh7DgAIA +AAAAAALWjSBEj/fUAA3FwAAAACBmg6Ke4k9dVL9mDvp0O/MVJzR3s7qIwYjEFVc9AAIAAKGO8Eguu1nPo5docBnx0uj+gXRk/OpI8TvH3mBUGyJVrDg9ZbYlAh6gOAcA +AAAAAALWjSEtd9Y+AA3IAAAAACAfTVlkMAyGyev6BSruaL7lNVxOzJDL/hkY9pxdeQAAAJx0m3rY7NdvTfRBy/EeudL0EpVe1BW5r1U8+Q4f/U9yeoc+ZcDlAh4p/AsA +AAAAAALWjSIQmw1gAA3KQAAAACD4MIL7L495p2E1WPIQc1LA9cjQ/Q/9ae9k+ToGQgAAAH6haRus/kOaiE87jlmTvHBQOIuZGlWOObdlMSCwfdWM4cA/ZaoIAh4ufQkA +AAAAAALWjSMSLIQiAA3MgAAAACBhTWHXuH9Tuj19Mg2JrrzhuBav3pvE+2s8XXt4vAAAAK/1hs0XFJQMZC4mPrjeKaMa7FnDH54Mp1P/uxllthnPhAFBZTtmAR4wCQQA +AAAAAALWjSQKd/OhAA3OwAAAACBTLNuMm3Gc5GqghmsEx0CaH0GCZVSHmocVWcjc6gEAALts/ZSMoJVC/Z1nMeNxNkQ7e6W6nIXT0IuHf5izktkK/0ZCZaLIAh4VPw4A +AAAAAALWjST7DZ39AA3RAAAAACDjc7t2BqXaOP02aQZqubVO0D9vM98UWjku10/ZZAAAAJrGxVqb5Bwzy6GTqAc/rHyafyvOBhm0k9dhXrOQgyzXn4dDZUzYAR4aFAkA +AAAAAALWjSX5Z4BoAA3TQAAAACBHxRBUaDOZfx1+YYAJS6ZT1DUUIynMnzgtzTZJPAIAAJAnKEXcQzf6cm1bKWzRRlxn+bbvsRLaU2c6SWSYr+fp0L9EZZSFAh4JOAAA +AAAAAALWjSb6wdf4AA3VgAAAACDc8cykHS4fPdaY3JgOg38iJ75IYdPX9lMUIquceQAAAAea3hG7JLmA5fLjQ3Q/07uD++pQQL02Tl+e14mV9LmVakJGZbBuAR4Plw0A +AAAAAALWjSf4JwbpAA3XwAAAACCQ9K4LcpmB+CYDV++FeAxn3bySeIi5aDdZ74eROgIAAEwHQCi1YpFRBXY1wJC4B+g/Jtwv7TVFOKIU3ppL8y1BY4JHZfXLAh5C8AIA +AAAAAALWjSkEusq0AA3aAAAAACCkHmD5oM2ph5k0d2Xzk8Cvih6YkPuApNEtu9+N0gIAAHG+T7JMLU9TnWU4o56T2+D5okoG607ZBKkPGh+NSrt2/M1IZTSfAx6ATwcA +AAAAAALWjSn4zUhlAA3cQAAAACABtpX5CWJAQsirqmDuLE0jvDSkkwGe2sz7wJb1NQAAAAW23tSmRNPF+MjwBhK7TBwhhth8MEg3lGIet0RMcVSXGgVKZZ8rAh67ZwsA +AAAAAALWjSr0pSguAA3egAAAACC7nEh9h/rNbGgOnyH5BBklfNgS7Mi/Ktj/Ryst5AAAAMDfEdb5E9tyTjIucrJwULoMRZZfF9BZmnDFKYLuQzMrqTlLZba8AR5YfQIA +AAAAAALWjSv5QaAIAA3gwAAAACD6C0ruzrF2bCAoG/HmnQrPqtr1bGW9lhArlqoMvwAAAExXh9m6+BEnPuWyzmUTWPtj/b0Dg7XXWrPtWFY/QhxgtHpMZfn5AR6tZwIA +AAAAAALWjSzfM6fgAA3jAAAAACDV9I1n3TiixAlotYnHd2gTC8gW18gW0EJFzLZmYgEAACkBPaOmVm58X2Y3Wz0ClFkM1v0Cjc+uY1fEv+mXmgtqt65NZX/4AR68BggA +AAAAAALWjS3UvnUdAA3lQAAAACBX/Ob32BPAqTxsxRIXBKoDCZnDYBu+axwuufRmWwAAAH8M+aTRgpENIHj1g9sY0vMesW7DLZjua4NriDqMh8HUC+pOZS1qAR41ogkA +AAAAAALWjS7c+wiFAA3ngAAAACBWCandT0WDxAFmn5eU4FXtgbeiUsCBhtdbRoTp0gAAAFO1iKkK5lolH9hmO9rEahvbI6Dfm6rILUS/B1WdmAJJrChQZZ7uAR6oqgcA +AAAAAALWjS/QofaRAA3pwAAAACDShcFTqriingmirC9Q216uKvP1pNQW0kDsju039wAAAEqL4qQOZ4nMsFK8gzkSOG4uGTdFuhlRBZPCzdF42sjwH2ZRZQHVAR5EMA8A +AAAAAALWjTC6almNAA3sAAAAACANnxu/l/jmoVMyJQvA5+9y9Y30a7cQ+utrqLbnEwIAACJAg3gbsuVS30qpynCbHsqt5m/tipTww+o3TS18Lf1gQalSZcFoAh4hbwcA +AAAAAALWjTGqE9CEAA3uQAAAACBHX2z9rhyOKqlDunt3dGpEqylo2wQIdm65CQj1fgAAAJRj7df27l0ygzKAlgeFI+G7YuiwhLXv9YDP4Hhqz2Hn5eFTZfUuAR7XaAgA +AAAAAALWjTKVi/qJAA3wgAAAACCLHZYujqF1VAIeMw2e72SZnS/5O0xE6idIuhCt+QAAAF6fGpE3uu7zT1CZTn+8xyngjpQlpRfNsh9AjAdqbnO0oh1VZfDvAh5xVQIA +AAAAAALWjTOJTTb8AA3ywAAAACAd7oSb2Fth2luAVe7Hi3j1RYm0Oo1eCT34hlNLwgAAADT0GU2x/PFOXfCufQN6Vq3hOUQSmQ6+YuSfCbq9JNyMMlJWZQtAAh5sPwkA +AAAAAALWjTSMZTSFAA31AAAAACA9gTnErNUfKkFS5lob7Yqut/pRyudUX8xdqr5YNAAAABEBXahqzf4sSg7sWw99wd+wczKkm/QLusNqRO53p9LM1ZVXZQ34AR51AQoA +AAAAAALWjTWKkFPYAA33QAAAACBAV64figxldZqMEbwPGUGq6S0lvUGML5c8XgjviQEAAJzJ26U+py+fVzEzb1rJknvDgOkQhtUfh1YkCEQ6Z2bobNRYZUq0Ah4SdwwA +AAAAAALWjTZ9mcbaAA35gAAAACCeRASdAxjurxbKVSVJrOC6djVcrpu/qArp0MeBEwAAABYjoxeiZRyf4R+ScBVKXMTypJ0bl2gm+/t/xfKmWFrz5h1aZfaSAh64uwsA +AAAAAALWjTdybtBOAA37wAAAACDNMmrIktC7IsCo0CHuQlwwEIFuxQtk4eE1LzHYMwIAADMti9Aklsh5MnAiK6rCkjyW7ctyQY4C/3ldBcvRvkpQF11bZVqdAh6fNQkA +AAAAAALWjTh0TDmsAA3+AAAAACBLZlVdS8btOehLgBf210Ui0mfmo1+B9WiaRabHqgAAAIAIAQdH84kBhWnehfYiqZvM2+G6B6Enj3rbTi14Af4HCpxcZdijAh53bAoA +AAAAAALWjTl5e+ZRAA4AQAAAACA0AtYsuf6o/gtTRAiPI79jz8fBpY5CiYp5MESmUQAAAOBLM5AxnFdPlPx09+IiIePSvmXT4/wHc0kNA3GzBF3939tdZSKwAh5rVwEA +AAAAAALWjTp5YqNvAA4CgAAAACDWrbbNX78rihQsVRGkv62z4zh8WCjhuVZXaz1ltwEAAB+69SniGcICnvLptazMMH8eJWzaVABW8lTpGX94e/VHixZfZeepAh5pHQIA +AAAAAALWjTtt8NajAA4EwAAAACCUEVgRkhl3mBofrxINK0Td62y+eyAoJzqrc7UjNAIAAK9XPTPR0Kz4unxeXnkIfOvF7bonvOb7d5zjHNZFmj137VNgZcHDAh7QEAkA +AAAAAALWjTxbedTKAA4HAAAAACBVhTxEg2napenh0oDf/H8L94twDa0lgpgfRzL9TgIAAPGko+5PijDEyifqz1qvMB+fZ4jgJTeuQXsQ98BXAOBvAJVhZZpzAx6gtgcA +AAAAAALWjT1SZejOAA4JQAAAACB6y9jCNhMTrDRJYt5qMBv6aEqj64SnRBfsG5+ZSAEAAPY8X9otDT1EJW6kkfDYwYYSTbl5Ik0nOApXQqsS0DORVMhiZRIQAh5S1AwA +AAAAAALWjT46OjfTAA4LgAAAACAW3pzlP5rq7Iq8jG9JvfG874wBOjkkKfdV2g2g1QMAAAPQUMKwHDTYuCFAWJ+iO4iwQwGztyQrrL0cAjTKL+0qJQlkZeYaBB6rPA8A +AAAAAALWjT9EhtovAA4NwAAAACAj7/YAWSUitCLW1yEn6mu1+4CPix8qdC3/oVPRWgAAAD8K+WHSfhAYsr+ZUqLOg2BBZDZIGx4Z1BN0sO8rJVoyZUllZeJzAh7tWwQA +AAAAAALWjUA/k/KdAA4QAAAAACDFOT1yMSlLiIuJwq5B0EmHPfceCRVvw9JIL15IAAEAAJsbDT/B256Pfs8YC/aBUiVu86oPJukU6MzI9MXVKf3S5IFmZd1dAh4ACQoA +AAAAAALWjUFEagbRAA4SQAAAACCKjxB0XgkflEe54faXbVwJ90urDY9LdJs3cDs6/wAAAP0h4Uz/8yBL3m82/ufU1vW0AMAhlvfXxpWdlbIqbDtosstnZYpqAx6HagAA +AAAAAALWjUI8Q3h6AA4UgAAAACC4XL0OzbIS8Il5+vba9ooOcJctgyb3yHL4A/CLYAAAALp3FpyCBIFfen4SYqRLGC4+fsj6VIq9Wx6i5CwsiJNdEwRpZfTPAR7kbwMA +AAAAAALWjUM19+xPAA4WwAAAACBvCq4eQXe5zkGsoYwCA7T9F5gRNRIaa9ngL0QqIQEAAII46ppyl+ouJVZb3nv+bbwrtRG5s+1jHf06jTOJqMo+i0pqZex/Ah4mpAAA +AAAAAALWjUQvtOW/AA4ZAAAAACCq2Vns6/3Ys61EFHA8uXgyeNVYP/xXe1FAzzmCaQEAAPP/zFApHejo+8ieMX6ES4uGKfKMnRY5OZgH7ceT5+nxr4xrZUUQAh7+CgYA +AAAAAALWjUzH3OHuAA4bQAAAACCRXjNcccC2zjIryw1tS8nLTtW3acxuzzlaZB+axgAAAOQyZ9s6XRHfAb2BLhwyqGDprEf4Eo1Vs7e/qgDnL0KM965sZZ2wAR6EMAQA +AAAAAALWjU2+oHQLAA4dgAAAACA0RhoZqrXer578in6bSaVUpVlNkP2wPCufsz3ksgEAALoIbgXD4C6I/A0xPYD8UJ/56HVwyJ/HRnoXA9zdgs28huhtZUemAh6KXQ4A +AAAAAALWjU6q8wJ0AA4fwAAAACAs/cHhcmh3er9LrJ9E7wxCXL6y/IaxAje/gjS7NwEAAIDJE2wwUXCvp+j2jeWOsh0CV8Z4pkmbjz7M+s4IwFmWuB1vZZdZAh7BOwYA +AAAAAALWjU+azw9QAA4iAAAAACBPh/FIMMLUuz3jgepmI52jGrd0rosk0G7alifPTgAAAPH8QtmzBfqnVHeiHxeVj1eoNjjzAxVISRHzTQxU7PJHoFxwZbVOAh67XAcA +AAAAAALWjVCPW7PAAA4kQAAAACAiLnCqoLx7d67Sk6hjWihsStqAceBnYCUZDfnQhAAAAIOSFdHVVNHuh1sQhLQiSvZeS2JK7TRgjaEuRT5LEzV9/5VxZeihAR4qgAoA +AAAAAALWjVFubwgKAA4mgAAAACCXswtP/Bu46KpD78pHmCt0Jc5XVOs0QC4UTMXxXAAAAMpXjNm9YUwyX7k9HLljC5I5FzAkeqVkttG+i3/zTO7i/cxyZYv7AR45twQA +AAAAAALWjVJ1FMd9AA4owAAAACA/v/brOIEGwTo+dnT1Rk0gWKMF0m0KHUh93sjhKgMAAPiT/VfX5MmjAS53grjs7csJZg67rDa8ZCHp4Zu82i/oThB0ZdFfAx7rTwEA +AAAAAALWjVQ6v+68AA4rAAAAACBSiUtpNT6wBjf4fV0yeTR8G0I2OT2Q2pu2rY4rswEAAC2n81zmDIAUkzUZqO6Nw8qBUw/7NAVkTHdVeKPN3Oy7klJ1ZQO8AR4MfQwA +AAAAAALWjVXtKhLAAA4tQAAAACAr4j15ikd7A0eVaPXLeAJCBp/ttzvWGaQQ0ZL2QwEAAMk3WR2R4YGTdr6KkmZPjrYgHRLHzKkRs4DX68adLuheGIR2ZUtoAR43JQQA +AAAAAALWjVfXRdVhAA4vgAAAACDz2rJyGNS6EtaItwdTNRpN2Y/+DZhULUPF2V6YogAAAH1tuCLw9jUKXv/MeZqXOFZb7tyFjtNDEEu5Fc5MKe8lUcZ3ZY3BAB5YpgEA +AAAAAALWjVmXi9JiAA4xwAAAACDWCMdliQ30c5O4vJ2qgOiWt3f2GnYjlDNIHsJjkAEAAHXN6vKQoGh/5P4iePmB6c21AHsbuKAqCCiM3apgQgQChwh5ZWLAAR5kuAcA +AAAAAALWjVtnUJ5jAA40AAAAACBqOgPCbofWO+vrgmCJ6619NJYHvm2sTeasQGTEOQAAABAwKu12W0vDrfmLIRF6bpn4UNaWzuicR7fJzdDkECP/OkZ6ZUAjAR5utwwA +AAAAAALWjV0zxYFcAA42QAAAACBMZJd+CAkU9rWseYidDuG8bMvzG14IxJ+DiNuwVAAAAKGQ+wBJmtAMrdh/bPbhMczCUZTdtfJP+PHQ8kk4YMROGIl7ZXHZAB6oGwwA +AAAAAALWjV8CLRySAA44gAAAACBeHNydpymGm+9E1niiy14VkZtm8Ao8yS4t1lvavwAAALBwZkYrvzJzhYCwNhZnPs8jpxuYKCxKXUPaRC72MkJoA8x8ZUV7AR72WAoA +AAAAAALWjWDQtX5gAA46wAAAACCzRdRl/w8PeFdtX2JkzqLh8GfCJEn75US4hceQOgEAADntdwPpsxMk4DuV1Oj8/UretG3tXP8RmL7aXA5gC8I8mwh+ZU18AR6arg4A +AAAAAALWjWKMr0lFAA49AAAAACB4NLSw8EdKB4ZB/8XCl2wd2utcuJfDM10M5WjagQAAABoLdko+TausXW0yR8S0LQRY1nr1OzTgd5v1C5O519MjTEZ/ZYvTAR41VgkA +AAAAAALWjWQ7DXyqAA4/QAAAACBCgb3X1li3eUu4SRa+uO3mxSwdYTzteG7T1SbESAAAAEGBxJ1oYMqPYMCDepJAw+Syv1JjBrcittY+mUIc0VQKqnWAZRjLAB6wEw8A +AAAAAALWjWYPQsNAAA5BgAAAACDH+w9nhMlVmgNKJ5M/1juQiHSAoQ+LKyhSWOaPagAAAMF6XA6CyogyP/SEp19JBiyoIYaTizSvqQ90BAydnwVSl7SBZfEzAR4IqgcA +AAAAAALWjWfQrwJ3AA5DwAAAACBL2w8m0C7dBFBbROsaV8d9NREu/wt1Rw3Tb3E99gAAAIm784XvgG5xWAtx2S/RYn38TEUeeTnz9Jp9YXckEb0U8+SCZYQfAR5xHAAA +AAAAAALWjWnXZ60zAA5GAAAAACB4z5c9laKcM/gXr0aGr5YGqMifH6mXmFRnO6agcAAAAE4W0K4uv16oFoWngCxmS6j+k/zxPXAI80iLYMLzWMywXieEZUTIAB66rQgA +AAAAAALWjWu6pyZQAA5IQAAAACCdYAlqaq27ZJgdPDbi5z354Zqz79sDJKzX/UKmNgEAAANUmpRRkYC348sTNu16AThc3DIlGDj02W3bd3p1zrjTUmmFZZAeAR4SAgEA +AAAAAALWjW2H0odGAA5KgAAAACDke6gCGqhJpeZ8zkuApZaOdMYwTwYbNjqVLOTZ+wAAAJOioKkIrqGwCurQhl1zdg54HcuZhn99XaA/mRv2oXDHB56GZS9XAR7bfAkA +AAAAAALWjW92gQnzAA5MwAAAACBtYIpyLkMEiU7IpcnL9iULNdzHIrF9Cnf0U7e5cwAAAC/Mn6J14hn1k1zARXz5rZja04X4Jp20s4N4DMtXCSmqvOqHZaU1AR5UqAIA +AAAAAALWjXEwjL0IAA5PAAAAACBbU0NOnc0mRBWXQtyfDxEPZERqxT3TdfNNOe+oYwAAAJNMw3U+TAhIKvZMSU/JZci9a36pmlQewQVPbKYCpzKUjyOJZVLJAB7ZZgYA +AAAAAALWjXYODpOsAA5RQAAAACDt/ifpM/Mnl7gxrZcZVHG+FFo7owD+3YYj+tT0KwEAAOtjQUe0zFpwj6MM80J6l6MurtCD/mbSpawFnqDGz+QwslWKZZknAR5ggw4A +AAAAAALWjXfOfnHbAA5TgAAAACC3Rzs0fatkwbcrLRoXwPGyAueZVOguJ2UUH9h6PwAAALtwWwKe1KoLNY0cc5v2kyMdHMLK8cAMrJeibhp2DSd/XpKLZQBhAR7m4Q4A +AAAAAALWjXmlL8pFAA5VwAAAACAKaLi+GL0bV3tYne8KfjZbZOcVthpjPn2qVxtQHwAAAEstbQo89IExnSMseiiDZSfUHRBM5L639wpjEN2aJdBmqNCMZeLmAB5Xew0A +AAAAAALWjXthdxWoAA5YAAAAACBu93TuRbI0Plx7TtnQ8Rk9dxDp14trcpFukoYnJAEAAJjtK89t5t+MOT5hrwTq/biftpHpImB5xRUMK5wib4YPageOZYCZAR5DCwwA +AAAAAALWjX0y/kvOAA5aQAAAACAOYnpkneoaaUCGfglzmEIzmJ3Dq3LPbmnhbCxaHAEAAET37z/PfQpfYcOejy2I9O2JseDttNGicScdvJA05D7rLUOPZTg/AR6/ywYA +AAAAAALWjX8jAP4rAA5cgAAAACCxwPoDBwpIwPxWLDMQ43n9xBM6iIZMf2nV49QXYQAAAB059kx/c6fhlfs5TpkiVCPnARl2W8DZw3RCWDS8ZNvlwYCQZUDsAB5nIQIA +AAAAAALWjYDqUsjbAA5ewAAAACCIBsk5wZiBrKZR/cZCGvOHip3mhS75+Kjitv5C7wAAAJEkBarCGs6sSCdmQL7w9l5lvLgXysESF1Z1PnyU2efvdb+RZfQOAR6GJwAA +AAAAAALWjYLaJN4lAA5hAAAAACAaTkFNm0DTE0A+P48gBVV01XdLTV+WwW9pbN/yMwAAAOx/J/DZ25Sihj+Q1TBRS4NhiDaLh/3dinBx/Vi4gKdFIAaTZcXQAB5I5AUA +AAAAAALWjYSy7nfnAA5jQAAAACDNxFsSO7ywWhsBcvG8y/tBfnbEGdVvBI9yNSJcEQAAAPWfGVjTh3pJzPRwbtqC4GDoivY6tTVUiVvrEu2Zox+gGU2UZbklAR6EywYA +AAAAAALWjYaV5mSBAA5lgAAAACChdf9X9V6LwoNWEkUkRK64gN1pnC/+eYPJwHfh5wAAAFYpDsqC2KJ6uwZiCYvHRYBvTjzrSV0MCwmSf+s3abvp7omVZVdhAR5V6gIA +AAAAAALWjYh1/k3wAA5nwAAAACC37aU4HpUnpzSmFS5o4vbqJMKnhRYGuMVxxISUgQAAAJs8xdcWI5e8XLUkLy+SAv8y6ESnB7bIPs47eIGXGu85ucGWZb3aAB7SFwkA +AAAAAALWjYpnkgbPAA5qAAAAACCUQc0ph+L2t0ANzaXZ01UzF7W1v/FhGNTM8M0mJAEAABBlPB5PbqWsWgszwoCdyxhG9ItSAogw1EuBt69djizSRQWYZeuOAR7yaAwA +AAAAAALWjYw8Tl1/AA5sQAAAACD+4ANChXF9HdcsjImPlLAt6KoFjVXRmsB8X2WFwQAAAJ9PH9n1zyFHblE/BF1jZGSi/3k6fdD10ZL+gepa12wXR0iZZeQMAR5VsQgA +AAAAAALWjY4Bxz0qAA5ugAAAACBEh3uool3KOnjhd7GidlW+rTdMq/cn6r50L7bZAQEAAPoskodU5Rjg0wKhSd/s/NctOT4gXqp2h20J9RQPtfK5WIKaZWgNAR6gjw4A +AAAAAALWjY/UH7GyAA5wwAAAACByzuIgS85HJBf1Hh/nePI3hptstR5iB5tLPIr0KwAAAISTzj62Ul8rzXJGQgLrUDlrvHx+oiXQkwCE6iy4h8XcrbubZa4rAR48EAIA +AAAAAALWjZGh6mg0AA5zAAAAACDmO5x5F5EU5XNEAFvQfa2mExWAySSsVRcD934sSAAAAD0DbnHvV6HgSlFk/khnreeNSQGkKOSK4Q88/7vlUbih3PucZTgrAR6plAAA +AAAAAALWjZN/MdQPAA51QAAAACDaJzAVun/ipZlc3+SxXOnzv5XV8LI5ZBN4jqDr6gAAAE40l/Y5MXEMS2SPs98VhhHejJrCl3TgI0LYWzEPzvJmTTaeZcuKAR5mmgMA +AAAAAALWjZVE7g36AA53gAAAACCXPIJRDO71qiYzEkpySkqFmzXJNef/ZZAcrSYfxgAAAMrtwEoAIuICyKg/OGDh+PRu0eXUauF1+LZfTrqGqlp0kHOfZVPwAB68swkA +AAAAAALWjZcg5HYVAA55wAAAACAmAzWv5AKiccMmAk8fL7L20oQkroV4rD6Sl0HDqAAAANnPwTCih+vJXrvmiR7fFyRcdxZk1UrRcHGBj5gqP+di7KygZSTAAB6TEQ4A +AAAAAALWjZjUzGtxAA58AAAAACCTmB9A+G9w0HUT9/jrQ7oSjiSaQBysON/ORln2HgEAAC/GhxXHgzFlv2816+2FwexqdBTAIoyZpOh75ZBwCJB2gOehZfjEAR5KvAEA +AAAAAALWjZqVr6O5AA5+QAAAACBDTZ42KrnbZBJPEZJ7Y7SD/HSchIEFnr6YHYupSQAAALHJEVNVZyB/j9ZODxjj58JP8LvTReeBljR9B5CuIO7dpB2jZVDsAB659QAA +AAAAAALWjZx7nx1EAA6AgAAAACBvHeo4oO3FvOYkj2MvbDYCgG/AJH1BOhomF5+DaQAAABqG/gu3M/f/G93CKoVavAJsEJNgA3uogeT77/5s5d6uO2mkZZbmAB5KSQoA +AAAAAALWjZ5T6lAlAA6CwAAAACDXkxufU5YXL2Hg6ff6sTTge6iDAPnjmAYyafdbkQAAAKDtMB/JC1T+SAvpyt03+XenD8mrhprTeeGU7sQhtDYw0aqlZYcAAR6UewUA +AAAAAALWjaBS0//NAA6FAAAAACDxEIq7KnWX0DRCzQNDpBu5jaEc3uCS1G6SBii/pQAAAA1iONVXJqA+ZjkbljtXD5yjCxub00O315olRoLDe3kfUemmZRU+Ah7JmAwA +AAAAAALX0WpP5P7HAA6HQAAAACAe5v0Nc5jgAwvY8sGNoyt49pQHruIHjEkgSFS2ZQAAAB/4uDKLLxeXieXJVUtpb5aAXMu3pB1hr7BpJARyT6eZJNSnZcS2AR4vRgAA +AAAAAALX0WwhvlieAA6JgAAAACCn420QQqX5PnXQj7LnnUpe+WD+mGSL8u++NF2VJQAAAFH14npvFtwO/j/w/8HoG4wKbuNXJaFA/0r1QTToehBbEgepZbCNAR53fQEA +AAAAAALX0W4RT74aAA6LwAAAACAKkzc/LIznWFPuYfVOYQH5VrO/jfRtxJraJ+3oGAsAAPN6DF0A0FbosfPLX/+V4XEWE1tRZuWcjOceLUJZxKNfwEmqZbvBAR6xvAIA +AAAAAALX0W+l55oPAA6OAAAAACBhs9pLndoxARs1EFUfy1UJPJ/CJ6iF+LZggtj3bgAAABnQzW5LmuO5oDSIGqc0F8aZcPtxRFS6vg5iMYDDhTYUyHqrZQUbAR4rCwwA +AAAAAALX0XGtf0uvAA6QQAAAACA+T/RM1u9yM3i5IgmNShrq02SL71vYwzqyX/eEFQEAAJjrUYougMjqZSwcuC61JepC7OJ07TJQfDzB/JT7pGbJ0bysZd1DAR5GrQcA +AAAAAALX0XOKQc4CAA6SgAAAACCsvQ/+YN+klJ88tHr2f7gph808QjxCLZeIDaeL1QAAAA+6QCA/s3HGgDQLqmJ9aezkNbXHje0djaS8jwP2JzkrQwCuZTN5AR4+XgsA +AAAAAALX0XVfi4rvAA6UwAAAACCo8cly/Cm2MK2Tfvym2H3ercL4xn7JIP0rRJmRtQAAAKqtO73MZLwcOW43F3FSsBzEY4XjN5v9borhQfRvQeTGrTWvZQI6AR5Z/QcA +AAAAAALeT2/umyALAA6XAAAAACBgT/YZB6C3oUqWGwOxwQninDI512soO3zUZC+YmQEAAEZ7XK0uVOeg4fj5X7/CRUfOGmOw9c0dWIeSElQJvFkCqhqwZYe+AR4KAAgA +AAAAAALpzNdkR4g7AA6ZQAAAACA+Oh+s7N2ABF7S/vMjMDu9NFfIVxdgG2myLgQAAAAAAMIcWqKsqVRr2LTPN2dzMP8D4RfmsXI8aDQnvzu54lNok8WwZdYXDBt7ZyU2 +AAAAAALs1sZdItf2AA6bgAAAACAcOIvI3P9yeP4vZcGIxtI16KyxqWkqWPG7S6BvAAEAAClvjRZ6k6DRxWZeqd63gI6ue18vq6dBOtT4XGmiUSviGwiyZaMcAR7EMAkA +AAAAAALs1si9y931AA6dwAAAACCJW4ODNZGcTEUkD8/+YEIr17rZr3TekpUUjwxH3QAAAHTJMKxFaoe5AG5pX8pCTI1x0ScDdYU01KE/RgFrL9S/dD6zZQBAAR59MgQA +AAAAAALs1sqVTVdvAA6gAAAAACBq0k8+6/rG3wS+MEmqViIPvGflxIxtBoN8AQc1vwAAAGOHKoOvKoxGGXENLjaz9QVzmkVUiwXgG9TwbuPdbcgnV4G0ZXgNAR4aAggA +AAAAAALs1sx8ihmIAA6iQAAAACAtR2eiJjNPfmHCZtpJhngO4/AkY9pf6vsWz5DfIgAAADFP8q0BhTNb0WQuvXFqLL6i03noAEbingJzMiv/nvA8pcO1ZVghAR5VggcA +AAAAAALs1s4r46tdAA6kgAAAACA2Byx+RIjwKxnVfRzhwxlALl3wtcCGZ5Sm6FJQTgAAAB8XwOcTZkKL0Pai1mNRPSzuZwFeHZkVVp+EkW+Z8igDfvy2ZdffAB7kFAcA +AAAAAALs1s/y0oiBAA6mwAAAACDbVF8+vAT/HH9SaorTkIO503toF2TaD+l6RPpcPgAAAFzYUBYE0IlTxtTbOZaJ09YnYNAIoxnOxmLd0o/w+kidID24ZRoKCR5RGwIA +AAAAAALs1tHEvNngAA6pAAAAACBFJneBHs81CNDcSV7RB7bpSizdc2jIFKLmB1VcmwAAAIse9HFEzbOZWAgZGnPaSlSEZLgVv+ZsQuxfv8PXaKHyzWy5ZdJ7AR4asQEA +AAAAAALs1tNuApjrAA6rQAAAACD9n64IxloPOJXPujaOwhE7rOui3jXtRVesUES3OQEAAOYgf0p59wwb0qhUang4ZDEgNlxyvCQGsbU1dVfvU02Kx6W6ZbmhAR6MFwAA +AAAAAALs1tVSowCeAA6tgAAAACCfHZdfJUwbqbrPSS8NH0h6QCSpmS40i6nmYq2AOgAAAOGSHtScHk0aiEuWTiyz0e8u2GO7f2wvpAmVwaGSWk5Yhuu7ZQYLDR5VKgAA +AAAAAALs1tdBw5YsAA6vwAAAACDzzoENxA0GBwkid1NloRblYvQumIDVt68N7dVgtQEAANBz019GgxTNXyX6V5s4/SBYl87b+TBmLAPp866DbNZpEC+9ZfuzAR5XQgkA +AAAAAALs1tj0hXinAA6yAAAAACBOBeg8Xnz3krYktzv8BjuDL5hlnT0C45gFLzf4IwAAAK7C/HrXsdGdHQnAVOnmbSRvzV9P6EB2lS0ueCu0l8btWWq+ZTa1AR5d+woA +AAAAAALs1tqtrDlXAA60QAAAACAvghrNwS6pwsRMjyrnoFBOLTX337hxOu4Tph5GngAAAC6a+R+8zYF6sgx0W6ALv7MiCF6Q9edgplBN0iOll+bz6py/ZZFmAR5L5wwA +AAAAAALtbB7JKdq0AA62gAAAACAMvTizwqO1CgipK3L1AC++zKdvCN5srtzHMquDHgAAAKHMXQ5wrE5LSkIAmDk0F2P0csOvEH/OLuVViPyFvw85NZzAZQUNAR5d9AEA +AAAAAALtbCDNTv9eAA64wAAAACCXJcBtMEuYzkmCbcwcD3XKF3ZdQYc31MNIRzyBwQAAAOt3Ph5wQYwOCUM0YM1FhS5VABBA4gt8Y2agWBmRAGm+SdDBZeN/AR6MjAIA +AAAAAALtbCJ96R14AA67AAAAACCJ7CxQbTB4AHo9HquNK+3DvOvNleNZjNeFU82QWQAAAGUyeJFMKAw6z1jw+FbX9nlGd0EwMORoEdAH33Kbr0FMowPDZfQnAR41wgAA +AAAAAALuMBZaAO0aAA69QAAAACCRqr5cX/k+480t+Wk5Horzeu344PxTtKyiSktGqQAAAMpYo3/fITknEtQyrFQFB+3yJb6YLj3MejghoQuwmeY8n+zDZU4CAR6vpgQA +AAAAAALuMBhME7rSAA6/gAAAACC7acYN2R/LgyvGS9/cheRQ2Y4lIuwLkurzkrv4egAAAFOf1lVVRRretvWUdujSc9LEVwKzUyQ1B8WyClNzUVYavjHFZU5AAR6usAMA +AAAAAALuMBnhOKV+AA7BwAAAACBJDQsQ4CO2KODojA8IIqD4tud9nKJePxl5fJpmnAkAAASp2HXlIuYCH+5ohiihGs5P3QUOL5SAJnxx2iX89EBJnWrGZaXjAR6lhQUA +AAAAAALuMBuvDGaaAA7EAAAAACB6EYaxBESnL/P/IlAsP3rHmSt7VdjKyaNcopkuYgAAAHDl5Ze24NKqulDxfo8wJxf6iq2WICB6sGWRYD7bNai0U6rHZYNUAR6aRAAA +AAAAAALuMB1xKWm3AA7GQAAAACAdCYOhIa2xruz7MKQqfGx1/r2twQVGAMVNzUDqmgEAAL5cl41w89pkjFJ4zv3WUvy1o3rcfb0EvrRM/Ic+mok5EuLIZfdVAh5h0AsA +AAAAAALuMB8vIEaZAA7IgAAAACA6EDdc9OwB+7eTa/jvL5DzkSVELbLaZdEonqYAbQAAAHwUlqoKbjhhiDBrl1LfVV4A7JcarANnmp697CdnFUM2JhPKZT8gAR7eAgYA +AAAAAALuMCDPqqukAA7KwAAAACDsBXXA6cQvnWnslLrghij9mry7+X0BvRS+zG1RBwAAAOCaFAtku3RnzFellbsGa58SYYuWcwMEU1osGCK39ctJbkbLZXitAR41Zw0A +AAAAAALuMCKsva0DAA7NAAAAACAiWArHshy7KiL9VelvZLYu/17vPV1vdZoJrRZNaAAAAKRiZhcomqf8owPvKStNo42VGvjesyHhlKWPXOIXDPMvFYHMZYUAAR53NwUA +AAAAAALuMCSabe4KAA7PQAAAACA67Esw6H3aNV/g6JoNVQ/q/JQw2lCse5spurc4ogAAALOPHlOl7ZFqrgbnxuOfEF52ZZTlnRbaxmcyRvMRIrWXmbzNZeTUAB5EEgUA +AAAAAALuMCaCHIHZAA7RgAAAACDCzgNjPk95sdec8tXFktMbKQf7qAIBueh29UkdhgAAAMwgUEVTTR9Wv5IKdezgv/y3hbku30gfnm6VsWc/w3kwQf3OZS5dAR4yOw8A +AAAAAALuMChZ92/qAA7TwAAAACCturmFdtUgDpHCktV3XcU7SNvpLnkGXc6oEZYj6QAAAHhy0S8EkZGVCba9eZrOexUOOom5J4yxsbWXyuNRhwHIK0HQZU18AR4IFA4A +AAAAAALuMCoW2D6vAA7WAAAAACDN9FrSnr0zGSn7gbSYhEgu3+/tgbmZz0oZfBWuCAEAAIRpc5gXVoox+Bizc7Bh0rh3RVBPWPX1igjFo5W7UIR69XfRZapBAR71vAkA +AAAAAALuMCvW5HUrAA7YQAAAACBIJ+1ch8Rwl6H3OXZ184MFzB9fA4LWhlHbNWZ2GwAAAI8e94wCyM6L7e1YtV2vJZRmbikrwe6NRvkEjFE3stO1la3SZSXTAB7GRAwA +AAAAAALuMC2Zs9GjAA7agAAAACDoNL7bSyWkAqIdGiyC44qY7Ua7+GOr4Zs8Li0qRQEAAFEeVdYJyWJJt6l6w+OQifyYalX0MSpw6Bvm0YfeYGsDy+bTZSOQAR5rFQsA +AAAAAALuMC9ujH7bAA7cwAAAACDwyLMCVcsV+ODHqUKw+74Kdp7s3XHCbQZKGB22FAEAAErHWcCwCUQM/H71DxyAJ17yGEnEz34G06MpfF2rfoT7BifVZQZnAR4y3gUA +AAAAAALuMDEwNVXuAA7fAAAAACAg7Fva4XaOXXIj6cxOO+JANVLJJ7z4wK22nARHwQAAAPk1WLAN7J50H37X29qhB5gkvjoKb9qObOH9GJcejz8EvGDWZQ7uAB7TWg0A +AAAAAALuMDMGdahtAA7hQAAAACAh+EQ6rw3h9BhwQixmlxMhfeB+PTnxw0z2RJR9PwAAAMbhcual9uCp/vxaJhjAzGlA05JgP0R1oZ8njYL30bfZN5/XZRdqAR4aCwIA +AAAAAALuMDTEDu/YAA7jgAAAACCGMwJFW4moQ8ZUimSTj8+nfAMyu7y00H1dkfx6egAAAK44qcn5VDqlosgK/SEh+0rR/tqp4FTEUKh9lluuYBq6dNLYZTeAAR7IAAoA +AAAAAALuMDaFLXhuAA7lwAAAACDZ183A93ynLQwjRQx5BKo7mzyxG6pkqETpv15jRgAAAIVtG2lce0pv+xZv1wdsfGFIlppPLYqqR2yH+lC/ExbqtgraZXXBAR7JjAwA +AAAAAALuMDh/NHLLAA7oAAAAACBv/1wjgBjsB9I5nNwRfOtEzyEu/zxSDIcue4QfNwEAAOwxV3Tugofkl3IQOfnLbL6acPnVVCTPL/wBWUnwL9shMU7bZeiDAR6NUg4A +AAAAAALuMDp3W2gRAA7qQAAAACAt4D/vx6DoGi4bjKC7ogrwHfdHquLNdS/FA7R6eQAAAPWJvIgdTgx4YS4Otlo/+CQgEnYXTpcyTCpwz/Pe7eO0UIfcZbyVAR43mwAA +AAAAAALuMDwtQE8tAA7sgAAAACBzC/Ia3Gr/vHCo9FNO/1PCtMPA42sUcweubS4NbQAAAD5DNLp3sH0KE/9Xq5GBh2f64QC0NvtAvIu74bs91zUvycHdZcEPAR5qnQ0A +AAAAAALuMD3OhZsyAA7uwAAAACBTBkDepVfJeFWppXIfhhdxbwrtWwI2jDrIM8A6oAAAADTqxufeQW/tSC+mhph0c39sco9OuCp6nfT7/QxfnJp+RgHfZSotAR69zAkA +AAAAAALuMD+99rEWAA7xAAAAACCV57WrpwkXiUVXeTIIP9WyP15xEAp4BQKwzUgWggEAAMUERrK+3mqoQSTECXJwOwF4+GJkFxgM3W6uc9fJqYPjZEjgZWiJAR5oFgUA +AAAAAALuMEGXS53BAA7zQAAAACCPk8p1Hq1GVV5WJRTymUloP5ekmKJdxkohe/+JzQAAAKAf80Bf9oWEv/huLrgDNMFQdp+Gl7oNlvF1ZWGT0ZzMforhZXMGAR5wUgAA +AAAAAALuMENhwwK4AA71gAAAACC2j6GrEWdHHDshk/ElujTnqKG99BDT/WMfQeqWkwAAAFCZyb+gpOlYzWaXhaMq5LFbVJAiYbypttgVE1IMM1Vzs8XiZdFmAR7erQcA +AAAAAALuMEUf8rxjAA73wAAAACC3tVfhPDm46lfFo0i0sNsOhOj0t2T+4l2wiGOG4wAAABj8FjRD13jF4HHJ26YrnBHmqE6I17OSwlJ8xWPTIehuLPzjZUQ4AR4xtgwA +AAAAAALuMEbWjFu7AA76AAAAACC2BQEwmifefrRAiI4MptGPPTudFHzOazs4axt9OAAAALUlFwrTEFQc5Ayt4LawRAox99YQF0Z7E/w84rGq5RfNHC/lZX+7AB4n4AcA +AAAAAALuMEjKBKUFAA78QAAAACBFGlm5PHvs4zI9GHJ1C3wlMAh0tOdA+3LhFqX3LwAAAMSyzaC+h26qvLHplb3IHBHb9R4vfjFPzZ1zlawGtpfv0HfmZWYGAR4k+QYA +AAAAAALuMErQ4Gg3AA7+gAAAACCum3RP4qik85MQhzEPk1FOD/YrHxqEvNOYhm2mTgAAAMSsefoO/0UhBpwVDe02qUiH9G8IW+uC2q+0BaUIdRlMWsDnZbvEAB5OQg4A +AAAAAALuMEyLeXhaAA8AwAAAACAb+vNO+RdNtYCs6HEhhwA3+z9kzRuWLuFwbCtFswAAAAjh8/41ApJpmmaB37ZcUp3GuGJRbTdwPAYxv8rEm6BQ++7oZU/NAB643w4A +AAAAAALudWfzqU9ZAA8DAAAAACCABL0vyF3iMkHcJCzNhsRIF7p+sw47SnExf0cYHAAAAIch5BEhWvTVg7NMS69X5ColpXLN5YerEBji+PjjQGJHuPLpZfwpAR4YIwkA +AAAAAALudWnG0Gj4AA8FQAAAACAcP6AulsqEzjv8byGvhiX7Qjm1+agurr2UUgW6yQAAAIywxQ8EhIfZ+6IowoqISL7xygoNaimBZ3y+MYCCWDSEyjDrZUrqAB6gOQkA +AAAAAALudWub1zAlAA8HgAAAACC/pxs05X0dTbZa0wS/qjlTysQMa+nWZ/alkH90zwAAAFOkD8H102+jsGuwb11zcMWBLJ/gRNUikS+FDdBZVWYCR3XsZZAYAh6/3AgA +AAAAAALudW2E7nttAA8JwAAAACDB/3scQmv2pfmg/vIQPeTeIPpyglo8jPShkS3NWQAAAKz3ybRxJFklYRozcAKe06Kx43baKL9xwWRkb477KQxMFrbtZRL6AB6NGA0A +AAAAAALudW88PpcbAA8MAAAAACBXfF79T7eeAsGq4RMPivF5LNWNBxA/ff12oklvtgAAAFxOMC+6c4+cwVup590NZr9n612ARXKRl7CiCeJvM3uM9e3uZaT8AB7SKAwA +AAAAAALudXEqaQgtAA8OQAAAACA1I/SZ1Iz4wGgHY0OdQfLH/3CGUd/yfds804aU1QAAABT1Q0G0p3biqc/irXmswBbqF9SLqJ2k3omVBhjEfy34BDPwZQoUAR7M7ggA +AAAAAALudXL5fuRIAA8QgAAAACDpfg5UangCm6kXoZCbY2B29Pu8XMAo7VZMhYtRaAAAAH1R/WKer1aJzEe4lO8L5HQeGnEodndWO64GH6ZK6wmOAnTxZS0mAR5CqgYA +AAAAAALudXTHz8mSAA8SwAAAACBTBhvl6xJnTud9QDqaoJE5m1pTxEvhmEHrCuL3HgAAAEtPLiWyZRUPV4d1Of9LNLPDPwv9XnXkiV8RR+T9bq/kRavyZS8jAR6lJAMA +AAAAAALudXaSk5EVAA8VAAAAACCq4D5/ZHjKw1+BQcJ9wMn7JhLOmu2Um2EFX9L5CwEAAMufJhiYA6CNKyzx5dkjeqb1IJyZvupXG83gRPRKKQRof+/zZT9LAR4ONgYA +AAAAAALudXhX1/qiAA8XQAAAACAUetjydlRLpRgWYvjdXyj0fTFmKfUHw0RozMdBbQEAAK1BahdkIbFgZgLlUQxUMTZgl6WKhTuSLM/VS6LJvALB6iP1ZfF/AR5Y2QMA +AAAAAALudXpRpcFxAA8ZgAAAACCqsnCIjFOl8X7ohjUho/DEMdsRdTuKqZ4xSepAMAEAAJnSQHFpG0FTvip9CB/yj/8wia7ZrUr4cSObr39saa/pwHD2ZZFbAR4UVg0A +AAAAAALudXwxxvRbAA8bwAAAACCUT/VQeCRnfysTrBHll0/zkSUzguvCqMi627bBtQAAADzAlimM5YoLDOgBD+jC+AA4EqaiC7YhEspzsB1ieQn6nbz3ZRiJAR7UdgYA +AAAAAALudX3h/bMtAA8eAAAAACClEgsUeFvSer5k0t17Nk+6SPmMvlHZc5zyE3ZohgAAAIR2aYTesm2FqPlTEaou1MI72E65vA69dbFJ35/sK72QiPT4ZQLfAR5ZhgYA +AAAAAALudX/LnSyTAA8gQAAAACDI/ThOgb5tHQqpTErZEyhAwqkOb7SCljudwsuNEQAAAOsMpDkp1Bpa1J36nqEq/T7N5/tddcZZcR485h5XFwT6wDL6ZdsaAR4rMAsA +AAAAAALudYG4gSd8AA8igAAAACDjQG9N1LXJdweVVlvKgoKh5MQJC3jHOVF/LIcx0gAAAEks5zBizDNUxg8Kx+pXjsabbe7aEZ5IU/Bx0uAVMpMNTnP7ZeTmAB7jCAsA +AAAAAALudYOn+OhmAA8kwAAAACBT037ZBu8MeLPGvW3T9e8uByO5mbE2xm45I0Hk8QAAAKXui44CV9/TTpKw9tJ3kioA7+baxmX6SmyBOO+WA7djGLX8Zaz2AB7tsg0A +AAAAAALudYVxq4VYAA8nAAAAACDHGuwvLQK2vjjd1oA2+px3d5t5mzBLf5lltAIzEAAAAOACUFABhSRtdXLn7/fLuHFmzvJknkE27x6Pr/QKGYTouvT9ZYUUAR5YQgMA +AAAAAALudYcw4xz4AA8pQAAAACCe4yFlZVnE1B4cQSz+BcUU8E4LgObnFZsWM2wLDAEAAJBDczUQsdD/+Rahtxd+Q6ZQb0WhWJJUsuAxXThA8Yd1pir/ZWQ8AR4meQgA +AAAAAALudYkcgBQrAA8rgAAAACD3s5w7xt62XPbLvuNIYlMoUCLfKsRsal6HJT19FAAAAJDFb3ZNev6jqWzFYxAg0Sk5THFQTJCORisZRniVHCzZg3EAZlyQAR4NlQwA +AAAAAALudYruraNFAA8twAAAACCYD95OoHi9WItC1KrcqSD60slbp/D907vxheIqnQAAAEQEln7cA2ZWrgXEsxmpE3dc0NNoaH0EjcKWiSm6vr/gYbEBZhAGAR7BwgsA +AAAAAALudZ3FRgHfAA8wAAAAACCa8aHb6B2nvTQ/BlkUCiwc2CQ2PwfVOSSbaypYWgAAAGDccR3C9q5muiIKj1LsecrEoa/DxCu4Cgn9te2ppM4xxdMCZtIxAR5eSgAA +AAAAAALudZ+9yFJlAA8yQAAAACAAq6EG14jNjhyaH7yH6eZdW8TJrMNyZH7RgQK6wAAAAGJnc9NwOCKMtyAd6sc7jjGJgNdYyLDmyMEKhF1A5fiVNAQEZsEYAR4PXgsA +AAAAAALuda4LDCJMAA80gAAAACCaJVxVvKLyPKIZN5G9b+pEwlun5ApPX2XvnFom6gEAACDv454CMU27CKRbtMarQGfQ1ll7MFnJN81j8qXRVKokgDEFZtspAh7bgQkA +AAAAAALuda/mIj0XAA82wAAAACA2ppZhaRODqMBicgNK7JhuejlrWbAd/xkiP/6qcQAAABRwKcG+vnfk1NgmRkkCcwuUy+vyWf5nPr4KvIdU9OgQNV8GZrh8AR5yJgcA +AAAAAALudbHZkdqxAA85AAAAACCi0ZipQjsL5GU1MEeBuyjL74Pqsdj0no2uXwl9GAEAAMZevQfvcJkvvbG53M6Wv4CGW24SfrPkfMeeGSxgVOp49KMHZlxzAR4wYwAA +AAAAAALudbPqbf2SAA87QAAAACCNbWsvoT/wldqBtQzrjbdR6fc1lpu8WZcsET7MZQAAAPGO30dhNIUGukgtkQDJigQzOkK04S70eHyvCw8b+oxjgdcIZsDMAB708gwA +AAAAAALud0EZPjlcAA89gAAAACCRy3JMV5WPwGUaeqoUWg8rP2MtT6doYVdF9O0IAAAAALTzEtcFmQcBQwntDGwW2rx+xhKsOJjE9wzZ1QXqwUGsXtYJZm+VCByQYcL+ +AAAAAAL7sxIGorNAAA8/wAAAACCtd86YigfNhlLPXdxQFLMTvTcKEp5cpwjqCT/wWAAAAKAljU6qcEDf1eWakXu6pDPJHV9vDIbxPvx2PXwUaMkeKPoKZooJAR5uMw8A +AAAAAAL7sxOxGuL/AA9CAAAAACBGQOe/zAZIMLEFpArJidXUu+E0ZFHa76sSbshTZwAAAAWq/+nzG6+XEwS9JIgysPOfNpox7mHXB8SJjeABhtJmjjUMZgzmAB6csQEA +AAAAAAL7sxVv0MFwAA9EQAAAACAqSOEA9YA3sXJs4kG+SK++n+XHX1fjfTlgQgxIPwEAAB4yfddCtAFJQK0ncD65RvUniJJdFhqDj01ZDdETp29sEXQNZg0mAR4JAAEA +AAAAAAL7sxcreVpsAA9GgAAAACB0QJ/3klulLpeIZpBYST0BrG9YrxY89Ghbh8+orgAAABl4ydxSDtbCt2NsH0rUCf0m6hTky3YMrGZVbSF8V9+d9LkOZkT0AB6OWgMA +AAAAAAL7sxkJy6G/AA9IwAAAACCbfmEvLznw+fn3B8SSdQi+D25nSYs9g7WTpuvqVgAAAFDqvlFVfCvErRxXjAJi2K7vqKNEXQD8GpzTTl5nep6W6wMQZrv4AB5OlwAA +AAAAAAL7sxrNhOpbAA9LAAAAACD8g7OgvIhnWSnHa7bhdUZPTLF2lvBPdcDgfEKsMAAAAALWJL36kpLxs6shQmMkN2euDs6baWoCEHIzcOFLf/pGdz0RZmNIAR7D2gsA +AAAAAAL7sxx4I0YPAA9NQAAAACAvF+HIRdVETIyv7VdatkOkMZE4+Xgsx/j/nYtCOgEAAC1Ed2KUrGQfdNGGbkDZRzpbQualteA6KO4yQgrMYJ0BOG4SZrw2AR7b9AsA +AAAAAAL7sx44d8kkAA9PgAAAACBSSq7mhoex23T1f5Tih15COM2udQkdpBNUmomyDgAAAAAw1m1B/CzqIwecB6MHwBoAHpqUj12lSzuKxduEw9yv3qoTZkfOAR7iGwQA +AAAAAAL7syABhYP9AA9RwAAAACBHUnA3lx+tFYXHpWADzWdLxvT+WPIjYqQYG9U2LQEAAAiIDzAfh2Orfq+o9cSGegi26uT85omlWBR9sXtK2PQNqOUUZiuSAR6nzAUA +AAAAAAL7syHNG3xTAA9UAAAAACBe7DICZsEP9AsjtLkkTOhq8JPf9+i1oCuOpEXGgwAAAGNtnmVfySlDJL23ULut95P0oyNr4RTaPvvU6c2Iwi5u1R0WZvnHAR4cvQgA +AAAAAAMJKJykR21tAA9WQAAAACAReY5d/qQ8y2HeDpH0G04CrpRXkn7rVHe3d6s0GAAAALFg1K4T99mw5aVvJ/h5L9E4ITJfD/apDmSbMacL6QxdUAYXZqJHAR4W4AIA +AAAAAAMJKJ5t1GMfAA9YgAAAACDzMFzi49uifE1SinWsXkaxjVSjPtk7HXYhVuI4EwAAABQDVV/lhb0cSrY/k89lvcHULpDx3f/LikJ3N3SXf8Wfn0QYZhkwAR4H8w4A +AAAAAAMJKKAdXiXiAA9awAAAACCWjPnLYKj1egtRqsFHIuI4y668Z7CQ5xGoHDg8igAAANxCcVMMAW5Uq9++r2gFJJzAGXbMkG5sYqKwvkoZ1PhgBHEZZjPSAR7aHgkA +AAAAAAMJKKHz4jptAA9dAAAAACDmpYv12tIZDZtF6Qv8gr74akziPCUnEuDq6DRpQAAAALOIHOJIUVWpFA4oFHgcNMuTENWpACxsMQXjcGdZquW4L6caZrXpAB5D6AYA +AAAAAAMJKKPppc/KAA9fQAAAACDDElOk52xysxOl0Pe9JgnY/IJ/XWHEA//VKKWFGAAAABIMgt9N++ER67TjCU+0v2DKFuBkwmILsk3hfphdQWlWkOkbZvUCAR5yGwsA +AAAAAAMJKKW+vzcaAA9hgAAAACAFKACt0Ffwf9Ga9HvLH72rMO5hsFmUJeE3v5wIRAEAAGkzeyqKufAwHXXFt6SSRh6MDU7crBvGZ9vKFWl1JkPWxysdZiZOAR5k7ggA +AAAAAAMJKKeYuXfcAA9jwAAAACB+vKgNRPNO3dO5/TzwZsT9ahHCcWCOmyOstabjgQAAAG4h9z18knt2awepfA4fLvaN6qs5LEKYdJX7mfPfCHLZjmQeZnlbAR7uEw0A +AAAAAAMJKKlPk3q5AA9mAAAAACBGfRNikqEdaHWtHtEAgJNKaouuUafmmJi7a10hXwAAAC++7QNBced/DK8yE1nNzQvPh47kQgZDVYP+TW7lDyJSgpcfZvT3AB6v8A0A +AAAAAAMJKKsRyaNKAA9oQAAAACADyna8sVMAc+nSSstCBGoDQ/nwSwCeY/KONJE9wAAAABwB2EDmyZD462JPjbT+HkI2WbSWQXwk2dIPf2XVTo/eweEgZvp+AR5hhQoA +AAAAAAMJKKzweq8MAA9qgAAAACDD/7Dh3GCGmJR20if0uQuIYVFoEeNkfWbGxzh5MwAAAMFiC2Hc5hXBcEPXvoAy/ZrO6fvkQzDAQk8xTmGzdl4CLyQiZvHXAR7/4gAA +AAAAAAMJKK6rj9SyAA9swAAAACBp1TRq/iXrEdXZQNTCEZn+WETz9yJCKQba9pstGAAAAJs+4bLG5UvjMbIWAJJyn4x55AgDJy8mSkzRCmw6LPYH6U8jZvvgAB6AWgIA +AAAAAAMJKLCKmZZ9AA9vAAAAACA1EPnWPIfzOKHkpOHU+2Dio9xilJHGA5txwN9U1gAAAGDZne0mfIdwfyVgSmXFjPNLDBI1gvAURKgO1cddrBcl3I4kZv0HAR4aIgwA +AAAAAAMJKLJY1bo8AA9xQAAAACC7yYaXL0hXRa1vlxEzdCsF79OX8jbl1W3L//3p8gAAAIGv6/4xItFSXSJMHZ2qs4d6TXD85VbqaigU3evILZLMdcklZtYZAR7sEQAA +AAAAAAMJKLQZf8GTAA9zgAAAACBCPsbaUGPpqpyJ3zFjuL4W2VcYQigTEiofv/BDyAAAALBARD3jzHKGPUvY+nX+NI3ID4zR3FC6YZVbhYLNU2EXMP8mZkP6AB5CUwYA +AAAAAAMJKLXzSLH5AA91wAAAACAZ2+HF48Zznxz3eiHhrOJRuS3SIyKf58274voOLwAAAJqgtV731UEr9n0pZTyFBHLq3/r36sI6xb57RTk/Waa3xzUoZjI7AR6+fAYA +AAAAAAMJKLfiT722AA94AAAAACDiL5eCHWLHA1IQG8cxXhZE9sbuwdm8fFS6LG6uGwAAANuMYYJ7HQM3J7Odv45eql2SGEP2XDW5il6NzR/kuhLZO3YpZtkwAR7rvgIA +AAAAAAMJKLnIOZQ1AA96QAAAACA68k7MwiOqMBmSQ10MvSiDkJfSLbAyxFAUklPOeQAAAOPMUtj++5d2mqI9GghKuM+K72LiVF0RGkvFRvf5VltJ+bYqZo9aAR6DsgUA +AAAAAAMJKLuYS5N3AA98gAAAACAVQmRQcGvxzUnCzAwtWri/cceYtoW3P6DJ9v+EfwAAANLNCWBqYyyo3vu6uSJg5S0nKHlFrMpTG+3pSxI6T7pyW+4rZskRAR6ptgAA +AAAAAAMJKL1/JQwsAA9+wAAAACDjtSReoqgGczUhL+1eC2BSP/SAaucJDLAdm1t+3QAAAOBy/Gy7yPUIm0yzvGQXwqZUzMBK8Orv1uj+NUqE/+ABfyYtZlgPAR5uKQ8A +AAAAAAMJKL9ifSgkAA+BAAAAACCgCxWPyZA/6CoXpxR00L14odj3ZTSuE30htALvAwAAAO67QCJGp3hU5oLBG54lNMOpNyK3C6+b4sNnLzoSQij/SGwuZpeSAR7XGw4A +AAAAAAMJKMEt8QzXAA+DQAAAACDv2tb2pEpNZKPJlSEBCDj34d7TpDHFLMgAT3TdeQAAAIAsb8dGysf3s44A1hW3pGFJWMGa5uPkvaxCI0fwusb1j6MvZu3GAB5lcwkA +AAAAAAMJKML0ZJM7AA+FgAAAACClfd5VKUUKQAN005rbtIFIeHm9flpERrAlUo1QCgAAAI1OEBGTDxTkx2+AOsK2OUlNt/VxtRsM+Zo8YYdYACHiAugwZsb0AB6JnAgA +AAAAAAMJKMTc/CP2AA+HwAAAACCPZZRBwOXDnLQbRHeUOPKA/ksEKpkTo5lHfuPKAAAAAHMy5zmqjVtKfZW2ZpVP9Sk9qXixmbfBBFhHWOOb79RwSysyZpZ7AR60swAA +AAAAAAMJKMaaVfmWAA+KAAAAACDOKHJIpHMdGDbuo5trtKO3/09/Y25254RmvThY9QEAAKhfDd/GcjI23iTfxT4gcC4CT9kcsRJX4jizJCCIrVlpUmUzZrwZAh6fAAUA +AAAAAAMJKMiaZWoRAA+MQAAAACBpkJzlNBAD6bQiE8GAtMBSbAEc7tdX5mnHMBQTYwAAAPHknAtnRkvQZCX7KCqDcqwU/HghgkZRc/Xb2XgTEO5/daA0Zj/4AB5+Eg4A +AAAAAAMJKMprrtuHAA+OgAAAACARDhWlrZaKfJ2hacMahKhqk6LeKLSgVZZMbLi8/gAAADFA7tR3McODjPmrxXU9AV1Ua4J64vsg88GeS9cRh4UxR+I1ZgQMAR7S2QAA +AAAAAAMJKMxSzdVAAA+QwAAAACAlJW2+BMYSzyxI+2n7LjsaopwdvNe5mHoE8A6ETQAAALgu58sWD1YZRdOa7BXuLOUJkTkRG3MV/gMLlaD6TtmmBiI3ZrVEAR6sVg4A +AAAAAAMJKM4V9Lj3AA+TAAAAACBbpBS90k2AZF0rmgP0eLkZEx0OO0lFJ9IEI64+CQAAAGAY0Gl7gtmhe7PCmw+PtxnlB1AGuYC4YQVogkNIyWxxaFo4ZnV/AR7Umw0A +AAAAAAMJKNAapsipAA+VQAAAACBSyCKr5itQ4QpP1FG/QMx84R4+a/wimh8oSndvqAAAAIihZRSnJLJph0e9Yz3/a/XH90DyVvCtVXyRLr6aCDJ3+Zo5ZssEAR7DaAkA +AAAAAAMJKNH9LgexAA+XgAAAACAJJD8Xus3Yz2NZLEG8+xJ8A+iEf1PrdzU7uE7ZMQAAAJb6UNs5cmvnyDbW1EEaNdpfpDhrj6/hvkRXgw80BnV/gtQ6ZjUhAR54XQcA +AAAAAAMJKNOo4yzEAA+ZwAAAACD8XAXnVgIFuwkvDo4tFqSGRhTDV3D6lXebplfGPAEAAKoW0WH70vLHfrTqkoRuQduoJhkRf2yoJUrSWnCsy7aMCAc8ZkoSAh442wMA +AAAAAAMJKNWYbucPAA+cAAAAACB3ByKVRHaPQoGisw87W7LR23qrPMHblpRoekYM/gAAAO8NxCuYkgBhs9x2e4VH3BD8J8dGTYtw8MwUE+1YM6tE8UM9ZusRAR76WwcA +AAAAAAMJKNdhAR0KAA+eQAAAACARtve4FFmzOUvCDFClVL4iU3vkB1Tshl/5vosgYQEAAIpmUCNKdrVR7K5EEbJ/vj6l4H6/dqOP2Wl7pD2oQYFTZ3Y+ZpiGAR4x/A4A +AAAAAAMJKNk5thxWAA+ggAAAACBwdeyLdQq1d/5Sj6Ajovg9D2YPAHl1YiHnP9bphAAAAFl39Q7gJN6rbo2W2COe8rk2rkKaBtSpSuYPFIZxKpRpvK0/ZkFPAR4VVQAA +AAAAAAMJKNsI8cmyAA+iwAAAACBG++0MDYXBFLOXV0YzCner3j2b83STWoKEKObuGgEAAILkQzONJEDYACQCNFPzrFYypExsSB1kLNZg/y9YBpcbyOZAZgVEAR6Klg4A +AAAAAAMJKNz6e/GMAA+lAAAAACA794M8Jr3xoaz6t3V8JnK3G/PdJAi3zaQyFgaC2wAAAN0uAow/7s6w2hxc4t+jJNUco6q2OLcIJy/SlenxVRym9CRCZmxLAR7COQsA +AAAAAAMJKN61lI/vAA+nQAAAACA5f67fH55QnOfvRu8h7ud2isN8zugKwwB7iAHHHwAAABIAqwvfwzWtvftpBd63rdpr422XeD4ZoaKkpYo1JZ++t1ZDZuMBAR58SwgA +AAAAAAMJKOCHSuhpAA+pgAAAACBKEADfWWJbdjPfuJBmZr6A2a0QTjZbOXjZDDCvGgAAAL/2lZ4afF7fQxzos7Oae01jFuRMmdhBGdXt6CIJ1Qh5DJhEZoADAR5lDQ0A +AAAAAAMJKOJJzSzYAA+rwAAAACDSbuICuIM14fQnXrvkfl2ctHeLct5juuJO1T6JwwAAALsLoFAGXhBo6sCOoFBSq9wBiK17D18d4OrORpWqLwGG19VFZjgvAR6gugkA +AAAAAAMJKOQaviA1AA+uAAAAACDJJv9kXMXW+6H8bu8/WgbDJO/+EqQnO3IaXYiyXQAAABZwEmgYJvsQAKQ6u3IzAvRR3L/JaowsdAQjIIzuuJHG/QxHZr+HAR5L3gEA +AAAAAAMJKOW98EHJAA+wQAAAACACmN2ElIzcywxg/Z0OGKBcv8x/SIZ6DvfKmduk5QAAAABXEePBpJNBoCRkkVyoZx8LkdlMfsPUf4mk2ytl48Wg0UBIZggSAR7pXAMA +AAAAAAMJKOdyk89gAA+ygAAAACDIoxSVEajm7yC5w1IRJjo9+ucXA2KLwJQyZQa4SwAAAJtsTr1xbrHdKTTbZkO1jedWfouLGPYpQYLAhvuci0deaHFJZjsQAR60zAcA +AAAAAAMJKOk9KWzEAA+0wAAAACC9ZRUK+MM95KvUhwxO/vOmBluLM50pKAmZtTKUZAAAAMVgi0iqT0Mfglzah1w8ucaSfnGq6CaYIT00v5fsZGlr865KZrdLAR6xjgkA +AAAAAAMJKOr8vKpMAA+3AAAAACD42wO2fzDb+71Un17SsKtoBkJQtSx7sE3shKyV4gAAAIknoF8w1iiKttguzNmeaa/fHLp0GSDRelu6lQb1bgmDzuJLZmnKAR7N1wsA +AAAAAAMJKOzpEfOJAA+5QAAAACArtIhy2EG5P3h1UBNAVC8m6N4xuoPDcxItg4vAOQEAAHm6Y+ndCwlNdZvpua81Au6OkvrKNy7OeB2T/M79XpPdHSRNZi5MAR7n4Q4A +AAAAAAMJKO6U09P2AA+7gAAAACAjSJDfjxtxSfFrOmperWx2B1WwfUY7ZlPpoYi+xwAAAKHyLvQXqnEuqzYU2A9imnPhWnQgMgsSkNq+UtbVipfFcVxOZnohAR5H1QsA +AAAAAAMJKPkpUvnuAA+9wAAAACCfDCsvPCyi88ftBTzZZRSdxrlY1WvQPeOLEKv7AAAAAOri+KiqtEwqTb7K+ESgUXEc+tdztivG16o8QK5qEYF+VX9PZiAgeR0GTKM1 +AAAAAAMTAgLqUcFiAA/AAAAAACCdermEhrpo5VaU7zd/a2+0FbdypMbTgreIagAAAAAAABgJKElRtz1i2uklIPhM9RRHPEQJ1dn19jUMomYBiSTJ2UFQZo4jAhsyLMw8 +AAAAAAMXeNtBSTPMAA/CQAAAACCdbEwcJNnUUVbMVL70+ftXR7k5/ANyTgFsbv5c6wAAAOSpkTT+aDnF7O4DjzLiClY7ghXyE4u02tvLtAHMYynC2B9RZvAHAR5P/wsA +AAAAAAMXeN0+99pxAA/EgAAAACDhOOLSz00Z8gD58tazI1+Q00OgQnoXYSezYt6MaQAAAHe2L0juAZp10Q8IZ8Qc+3ELU7onRUg7LHt+q5WuAVn8cV9SZn6GAR5t1gsA +AAAAAAMXeN9QDksDAA/GwAAAACDwctsJKzAQs526jR9nGJbcQ7Jj4l3l2oITQ23+AgAAADaErvt4EjIqHoI3pQUzo9tHrfJl7jYMvxLinqeaIwWB0ZJTZnvEAR6NBwwA From f2dd6866df5c89dc35671dd893fe24ce39ccd68d Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 5 Jul 2024 19:30:33 -0700 Subject: [PATCH 02/31] refactor: move merkle root calculation to MerkleRoot Signed-off-by: HashEngineering --- .../evolution/SimplifiedMasternodeList.java | 64 +------------- .../quorums/SimplifiedQuorumList.java | 72 ++------------- .../java/org/bitcoinj/utils/MerkleRoot.java | 87 +++++++++++++++++++ 3 files changed, 95 insertions(+), 128 deletions(-) create mode 100644 core/src/main/java/org/bitcoinj/utils/MerkleRoot.java diff --git a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java index 48f1b39249..73b8042684 100644 --- a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java +++ b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java @@ -4,6 +4,7 @@ import com.google.common.collect.Lists; import org.bitcoinj.core.*; import org.bitcoinj.quorums.LLMQUtils; +import org.bitcoinj.utils.MerkleRoot; import org.bitcoinj.utils.Pair; import org.bitcoinj.utils.Threading; import org.slf4j.Logger; @@ -285,7 +286,7 @@ public int compare(Sha256Hash o1, Sha256Hash o2) { if (smnlHashes.size() == 0) return true; - if (!cbtx.getMerkleRootMasternodeList().equals(calculateMerkleRoot(smnlHashes))) + if (!cbtx.getMerkleRootMasternodeList().equals(MerkleRoot.calculateMerkleRoot(smnlHashes))) throw new MasternodeListDiffException("MerkleRoot of masternode list does not match coinbaseTx", true, false, false, true); return true; } finally { @@ -314,71 +315,12 @@ public int compare(Sha256Hash o1, Sha256Hash o2) { smnlHashes.add(entry.getValue().getHash()); } - return calculateMerkleRoot(smnlHashes); + return MerkleRoot.calculateMerkleRoot(smnlHashes); } finally { lock.unlock(); } } - private Sha256Hash calculateMerkleRoot(List hashes) { - List tree = buildMerkleTree(hashes); - return Sha256Hash.wrap(tree.get(tree.size() - 1)); - } - - private List buildMerkleTree(List hashes) { - // The Merkle root is based on a tree of hashes calculated from the masternode list proRegHash: - // - // root - // / \ - // A B - // / \ / \ - // t1 t2 t3 t4 - // - // The tree is represented as a list: t1,t2,t3,t4,A,B,root where each - // entry is a hash. - // - // The hashing algorithm is double SHA-256. The leaves are a hash of the serialized contents of the transaction. - // The interior nodes are hashes of the concenation of the two child hashes. - // - // This structure allows the creation of proof that a transaction was included into a block without having to - // provide the full block contents. Instead, you can provide only a Merkle branch. For example to prove tx2 was - // in a block you can just provide tx2, the hash(tx1) and B. Now the other party has everything they need to - // derive the root, which can be checked against the block header. These proofs aren't used right now but - // will be helpful later when we want to download partial block contents. - // - // Note that if the number of transactions is not even the last tx is repeated to make it so (see - // tx3 above). A tree with 5 transactions would look like this: - // - // root - // / \ - // 1 5 - // / \ / \ - // 2 3 4 4 - // / \ / \ / \ - // t1 t2 t3 t4 t5 t5 - ArrayList tree = new ArrayList(); - // Start by adding all the hashes of the transactions as leaves of the tree. - for (Sha256Hash hash : hashes) { - tree.add(hash.getBytes()); - } - int levelOffset = 0; // Offset in the list where the currently processed level starts. - // Step through each level, stopping when we reach the root (levelSize == 1). - for (int levelSize = hashes.size(); levelSize > 1; levelSize = (levelSize + 1) / 2) { - // For each pair of nodes on that level: - for (int left = 0; left < levelSize; left += 2) { - // The right hand node can be the same as the left hand, in the case where we don't have enough - // transactions. - int right = min(left + 1, levelSize - 1); - byte[] leftBytes = Utils.reverseBytes(tree.get(levelOffset + left)); - byte[] rightBytes = Utils.reverseBytes(tree.get(levelOffset + right)); - tree.add(Utils.reverseBytes(hashTwice(leftBytes, 0, 32, rightBytes, 0, 32))); - } - // Move to the next level. - levelOffset += levelSize; - } - return tree; - } - public boolean containsMN(Sha256Hash proTxHash) { for (Map.Entry entry : mnMap.entrySet()) { if (entry.getValue().getProTxHash().equals(proTxHash)) { diff --git a/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java b/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java index 43997e1eb6..afb722a808 100644 --- a/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java +++ b/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java @@ -299,7 +299,7 @@ public int compare(Sha256Hash o1, Sha256Hash o2) { if (!cbtx.getMerkleRootQuorums().isZero() && !commitmentHashes.isEmpty() && - !cbtx.getMerkleRootQuorums().equals(calculateMerkleRoot(commitmentHashes))) + !cbtx.getMerkleRootQuorums().equals(MerkleRoot.calculateMerkleRoot(commitmentHashes))) throw new MasternodeListDiffException("MerkleRoot of quorum list does not match coinbaseTx - " + commitmentHashes.size(), true, false, false, true); return true; @@ -323,12 +323,9 @@ public int compare(Sha256Hash o1, Sha256Hash o2) { } }); - if (!merkleRootQuorums.isZero() && - !commitmentHashes.isEmpty() && - !merkleRootQuorums.equals(calculateMerkleRoot(commitmentHashes))) - return false; - - return true; + return merkleRootQuorums.isZero() || + commitmentHashes.isEmpty() || + merkleRootQuorums.equals(MerkleRoot.calculateMerkleRoot(commitmentHashes)); } public Sha256Hash calculateMerkleRoot() { @@ -347,71 +344,12 @@ public int compare(Sha256Hash o1, Sha256Hash o2) { } }); - return calculateMerkleRoot(commitmentHashes); + return MerkleRoot.calculateMerkleRoot(commitmentHashes); } finally { lock.unlock(); } } - public static Sha256Hash calculateMerkleRoot(List hashes) { - List tree = buildMerkleTree(hashes); - return Sha256Hash.wrap(tree.get(tree.size() - 1)); - } - - private static List buildMerkleTree(List hashes) { - // The Merkle root is based on a tree of hashes calculated from the masternode list proRegHash: - // - // root - // / \ - // A B - // / \ / \ - // t1 t2 t3 t4 - // - // The tree is represented as a list: t1,t2,t3,t4,A,B,root where each - // entry is a hash. - // - // The hashing algorithm is double SHA-256. The leaves are a hash of the serialized contents of the transaction. - // The interior nodes are hashes of the concenation of the two child hashes. - // - // This structure allows the creation of proof that a transaction was included into a block without having to - // provide the full block contents. Instead, you can provide only a Merkle branch. For example to prove tx2 was - // in a block you can just provide tx2, the hash(tx1) and B. Now the other party has everything they need to - // derive the root, which can be checked against the block header. These proofs aren't used right now but - // will be helpful later when we want to download partial block contents. - // - // Note that if the number of transactions is not even the last tx is repeated to make it so (see - // tx3 above). A tree with 5 transactions would look like this: - // - // root - // / \ - // 1 5 - // / \ / \ - // 2 3 4 4 - // / \ / \ / \ - // t1 t2 t3 t4 t5 t5 - ArrayList tree = new ArrayList(); - // Start by adding all the hashes of the transactions as leaves of the tree. - for (Sha256Hash hash : hashes) { - tree.add(hash.getBytes()); - } - int levelOffset = 0; // Offset in the list where the currently processed level starts. - // Step through each level, stopping when we reach the root (levelSize == 1). - for (int levelSize = hashes.size(); levelSize > 1; levelSize = (levelSize + 1) / 2) { - // For each pair of nodes on that level: - for (int left = 0; left < levelSize; left += 2) { - // The right hand node can be the same as the left hand, in the case where we don't have enough - // transactions. - int right = Math.min(left + 1, levelSize - 1); - byte[] leftBytes = Utils.reverseBytes(tree.get(levelOffset + left)); - byte[] rightBytes = Utils.reverseBytes(tree.get(levelOffset + right)); - tree.add(Utils.reverseBytes(hashTwice(leftBytes, 0, 32, rightBytes, 0, 32))); - } - // Move to the next level. - levelOffset += levelSize; - } - return tree; - } - public void addQuorum(Quorum quorum) { addCommitment(quorum.getCommitment()); } diff --git a/core/src/main/java/org/bitcoinj/utils/MerkleRoot.java b/core/src/main/java/org/bitcoinj/utils/MerkleRoot.java new file mode 100644 index 0000000000..6fcdb6e1eb --- /dev/null +++ b/core/src/main/java/org/bitcoinj/utils/MerkleRoot.java @@ -0,0 +1,87 @@ +/* + * Copyright 2024 Dash Core Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bitcoinj.utils; + +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Utils; + +import java.util.ArrayList; +import java.util.List; + +import static org.bitcoinj.core.Sha256Hash.hashTwice; + +public final class MerkleRoot { + private MerkleRoot() {} + public static Sha256Hash calculateMerkleRoot(List hashes) { + List tree = buildMerkleTree(hashes); + return Sha256Hash.wrap(tree.get(tree.size() - 1)); + } + + private static List buildMerkleTree(List hashes) { + // The Merkle root is based on a tree of hashes calculated from the masternode list proRegHash: + // + // root + // / \ + // A B + // / \ / \ + // t1 t2 t3 t4 + // + // The tree is represented as a list: t1,t2,t3,t4,A,B,root where each + // entry is a hash. + // + // The hashing algorithm is double SHA-256. The leaves are a hash of the serialized contents of the transaction. + // The interior nodes are hashes of the concenation of the two child hashes. + // + // This structure allows the creation of proof that a transaction was included into a block without having to + // provide the full block contents. Instead, you can provide only a Merkle branch. For example to prove tx2 was + // in a block you can just provide tx2, the hash(tx1) and B. Now the other party has everything they need to + // derive the root, which can be checked against the block header. These proofs aren't used right now but + // will be helpful later when we want to download partial block contents. + // + // Note that if the number of transactions is not even the last tx is repeated to make it so (see + // tx3 above). A tree with 5 transactions would look like this: + // + // root + // / \ + // 1 5 + // / \ / \ + // 2 3 4 4 + // / \ / \ / \ + // t1 t2 t3 t4 t5 t5 + ArrayList tree = new ArrayList(); + // Start by adding all the hashes of the transactions as leaves of the tree. + for (Sha256Hash hash : hashes) { + tree.add(hash.getBytes()); + } + int levelOffset = 0; // Offset in the list where the currently processed level starts. + // Step through each level, stopping when we reach the root (levelSize == 1). + for (int levelSize = hashes.size(); levelSize > 1; levelSize = (levelSize + 1) / 2) { + // For each pair of nodes on that level: + for (int left = 0; left < levelSize; left += 2) { + // The right hand node can be the same as the left hand, in the case where we don't have enough + // transactions. + int right = Math.min(left + 1, levelSize - 1); + byte[] leftBytes = Utils.reverseBytes(tree.get(levelOffset + left)); + byte[] rightBytes = Utils.reverseBytes(tree.get(levelOffset + right)); + tree.add(Utils.reverseBytes(hashTwice(leftBytes, 0, 32, rightBytes, 0, 32))); + } + // Move to the next level. + levelOffset += levelSize; + } + return tree; + } +} From 3d4a98ed748ec7afd0465f07052c7329a89fc485 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 5 Jul 2024 19:31:48 -0700 Subject: [PATCH 03/31] refactor: add and modify getters for SimplifiedMasternodeList Signed-off-by: HashEngineering --- .../SimplifiedMasternodeListDiff.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiff.java b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiff.java index 38c472c916..0de4dbc89a 100644 --- a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiff.java +++ b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiff.java @@ -24,8 +24,8 @@ public class SimplifiedMasternodeListDiff extends AbstractDiffMessage { public static final short BASIC_BLS_VERSION = 2; public static final short CURRENT_VERSION = 1; private short version; - public Sha256Hash prevBlockHash; - public Sha256Hash blockHash; + private Sha256Hash prevBlockHash; + private Sha256Hash blockHash; PartialMerkleTree cbTxMerkleTree; Transaction coinBaseTx; protected HashSet deletedMNs; @@ -188,6 +188,14 @@ protected void bitcoinSerializeToStream(OutputStream stream) throws IOException } } + public Sha256Hash getPrevBlockHash() { + return prevBlockHash; + } + + public Sha256Hash getBlockHash() { + return blockHash; + } + public boolean hasChanges() { return !mnList.isEmpty() || !deletedMNs.isEmpty() || hasQuorumChanges(); } @@ -197,10 +205,14 @@ boolean verify() { return true; } + private String getAddRemovedString() { + return String.format("adding %d and removing %d masternodes%s", mnList.size(), deletedMNs.size(), + (coinBaseTx.getExtraPayloadObject().getVersion() >= 2 ? (String.format(" while adding %d and removing %d quorums", newQuorums.size(), deletedQuorums.size())) : "")); + } + @Override public String toString() { - return "Simplified MNList Diff: adding " + mnList.size() + " and removing " + deletedMNs.size() + " masternodes" + - (coinBaseTx.getExtraPayloadObject().getVersion() >= 2 ? (" while adding " + newQuorums.size() + " and removing " + deletedQuorums.size() + " quorums") : ""); + return String.format("Simplified MNList Diff{ %s }", getAddRemovedString()); } public String toString(DualBlockChain blockChain) { @@ -212,28 +224,22 @@ public String toString(DualBlockChain blockChain) { } catch (Exception x) { // swallow } - StringBuilder builder = new StringBuilder(); - builder.append("Simplified MNList Diff: ").append(prevHeight).append(" -> ").append(height).append("/").append(getHeight()) - .append(" [adding ").append(mnList.size()).append(" and removing ").append(deletedMNs.size()).append(" masternodes") - .append(coinBaseTx.getExtraPayloadObject().getVersion() >= 2 ? (" while adding " + newQuorums.size() + " and removing " + deletedQuorums.size() + " quorums") : "") - .append("]"); - - return builder.toString(); + return String.format("Simplified MNList Diff{ %d -> %d/%d; %s }", prevHeight, height, getHeight(), getAddRemovedString()); } public Transaction getCoinBaseTx() { return coinBaseTx; } - public ArrayList getMnList() { + public List getMnList() { return mnList; } - public ArrayList> getDeletedQuorums() { + public List> getDeletedQuorums() { return deletedQuorums; } - public ArrayList getNewQuorums() { + public List getNewQuorums() { return newQuorums; } From a12d1c3eadbdfeea3bd46f9a2ce82e572467610a Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 5 Jul 2024 19:36:17 -0700 Subject: [PATCH 04/31] refactor: use new getters from SimplifiedMasternodeList Signed-off-by: HashEngineering --- .../evolution/QuorumRotationState.java | 62 +++++++++---------- .../org/bitcoinj/evolution/QuorumState.java | 22 +++---- .../evolution/SimplifiedMasternodeList.java | 4 +- .../quorums/SimplifiedQuorumList.java | 4 +- .../SimplifiedMasternodeListDiffTest.java | 8 +-- 5 files changed, 50 insertions(+), 50 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java b/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java index 3f709c3753..834a2f3df4 100644 --- a/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java +++ b/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java @@ -174,13 +174,13 @@ public void applyDiff(Peer peer, DualBlockChain blockChain, isLoadingBootStrap ? "bootstrap" : "requested", mnListAtH.getHeight(), newHeight, quorumRotationInfo.toString(blockChain), peer); - blockAtTip = blockChain.getBlock(quorumRotationInfo.getMnListDiffTip().blockHash); - blockAtH = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtH().blockHash); - blockMinusC = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtHMinusC().blockHash); - blockMinus2C = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtHMinus2C().blockHash); - blockMinus3C = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtHMinus3C().blockHash); + blockAtTip = blockChain.getBlock(quorumRotationInfo.getMnListDiffTip().getBlockHash()); + blockAtH = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtH().getBlockHash()); + blockMinusC = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtHMinusC().getBlockHash()); + blockMinus2C = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtHMinus2C().getBlockHash()); + blockMinus3C = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtHMinus3C().getBlockHash()); if (quorumRotationInfo.hasExtraShare()) { - blockMinus4C = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtHMinus4C().blockHash); + blockMinus4C = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtHMinus4C().getBlockHash()); } // TODO: this may not be needed @@ -190,23 +190,23 @@ public void applyDiff(Peer peer, DualBlockChain blockChain, if (peer != null && isSyncingHeadersFirst) peer.queueMasternodeListDownloadedListeners(MasternodeListDownloadedListener.Stage.Processing, quorumRotationInfo.getMnListDiffTip()); - if (!mnListAtH.getBlockHash().equals(quorumRotationInfo.getMnListDiffAtH().blockHash)) { + if (!mnListAtH.getBlockHash().equals(quorumRotationInfo.getMnListDiffAtH().getBlockHash())) { SimplifiedMasternodeList baseMNList; SimplifiedMasternodeList newMNListAtHMinus4C = null; if (quorumRotationInfo.hasExtraShare()) { - baseMNList = mnListsCache.get(quorumRotationInfo.getMnListDiffAtHMinus4C().prevBlockHash); + baseMNList = mnListsCache.get(quorumRotationInfo.getMnListDiffAtHMinus4C().getPrevBlockHash()); newMNListAtHMinus4C = baseMNList.applyDiff(quorumRotationInfo.getMnListDiffAtHMinus4C()); mnListsCache.put(newMNListAtHMinus4C.getBlockHash(), newMNListAtHMinus4C); } - baseMNList = mnListsCache.get(quorumRotationInfo.getMnListDiffAtHMinus3C().prevBlockHash); + baseMNList = mnListsCache.get(quorumRotationInfo.getMnListDiffAtHMinus3C().getPrevBlockHash()); if (baseMNList == null) throw new MasternodeListDiffException("does not connect to previous lists", true, false, false, false); SimplifiedMasternodeList newMNListAtHMinus3C = baseMNList.applyDiff(quorumRotationInfo.getMnListDiffAtHMinus3C()); mnListsCache.put(newMNListAtHMinus3C.getBlockHash(), newMNListAtHMinus3C); - baseMNList = mnListsCache.get(quorumRotationInfo.getMnListDiffAtHMinus2C().prevBlockHash); + baseMNList = mnListsCache.get(quorumRotationInfo.getMnListDiffAtHMinus2C().getPrevBlockHash()); if (baseMNList == null) throw new MasternodeListDiffException("does not connect to previous lists", true, false, false, false); SimplifiedMasternodeList newMNListAtHMinus2C = baseMNList.applyDiff(quorumRotationInfo.getMnListDiffAtHMinus2C()); @@ -215,19 +215,19 @@ public void applyDiff(Peer peer, DualBlockChain blockChain, // TODO: do we actually need to keep track of the blockchain tip mnlist? // SimplifiedMasternodeList newMNListTip = mnListTip.applyDiff(quorumRotationInfo.getMnListDiffTip()); SimplifiedMasternodeList newMNListAtH = mnListAtH.applyDiff(quorumRotationInfo.getMnListDiffAtH()); - baseMNList = mnListsCache.get(quorumRotationInfo.getMnListDiffAtHMinusC().prevBlockHash); + baseMNList = mnListsCache.get(quorumRotationInfo.getMnListDiffAtHMinusC().getPrevBlockHash()); if (baseMNList == null) throw new MasternodeListDiffException("does not connect to previous lists", true, false, false, false); - StoredBlock prevBlockHMinusC = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtHMinusC().prevBlockHash); + StoredBlock prevBlockHMinusC = blockChain.getBlock(quorumRotationInfo.getMnListDiffAtHMinusC().getPrevBlockHash()); if (baseMNList == null) { - log.info("mnList missing for " + quorumRotationInfo.getMnListDiffAtHMinusC().prevBlockHash + " " + (prevBlockHMinusC != null ? prevBlockHMinusC.getHeight() : -1)); + log.info("mnList missing for " + quorumRotationInfo.getMnListDiffAtHMinusC().getPrevBlockHash() + " " + (prevBlockHMinusC != null ? prevBlockHMinusC.getHeight() : -1)); for (Sha256Hash hash : mnListsCache.keySet()) { StoredBlock block = blockChain.getBlock(hash); log.info("--> {}: {}: {}", hash, block == null ? -1 : block.getHeight(), mnListsCache.get(hash).getBlockHash()); } } - checkNotNull(baseMNList, "mnList missing for " + quorumRotationInfo.getMnListDiffAtHMinusC().prevBlockHash + " " + (prevBlockHMinusC != null ? prevBlockHMinusC.getHeight() : -1)); + checkNotNull(baseMNList, "mnList missing for " + quorumRotationInfo.getMnListDiffAtHMinusC().getPrevBlockHash() + " " + (prevBlockHMinusC != null ? prevBlockHMinusC.getHeight() : -1)); SimplifiedMasternodeList newMNListAtHMinusC = baseMNList.applyDiff(quorumRotationInfo.getMnListDiffAtHMinusC()); mnListsCache.put(newMNListAtHMinusC.getBlockHash(), newMNListAtHMinusC); @@ -252,12 +252,12 @@ public void applyDiff(Peer peer, DualBlockChain blockChain, // TODO: do we actually need to keep track of the blockchain tip mnlist? // newMNListTip.setBlock(blockAtTip, blockAtTip != null && blockAtTip.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffTip().prevBlockHash)); - newMNListAtH.setBlock(blockAtH, blockAtH != null && blockAtH.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffAtHMinusC().prevBlockHash)); - newMNListAtHMinusC.setBlock(blockMinusC, blockMinusC != null && blockMinusC.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffAtHMinusC().prevBlockHash)); - newMNListAtHMinus2C.setBlock(blockMinus2C, blockMinus2C != null && blockMinus2C.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffAtHMinus2C().prevBlockHash)); - newMNListAtHMinus3C.setBlock(blockMinus3C, blockMinus3C != null && blockMinus3C.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffAtHMinus3C().prevBlockHash)); + newMNListAtH.setBlock(blockAtH, blockAtH != null && blockAtH.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffAtHMinusC().getPrevBlockHash())); + newMNListAtHMinusC.setBlock(blockMinusC, blockMinusC != null && blockMinusC.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffAtHMinusC().getPrevBlockHash())); + newMNListAtHMinus2C.setBlock(blockMinus2C, blockMinus2C != null && blockMinus2C.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffAtHMinus2C().getPrevBlockHash())); + newMNListAtHMinus3C.setBlock(blockMinus3C, blockMinus3C != null && blockMinus3C.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffAtHMinus3C().getPrevBlockHash())); if (quorumRotationInfo.hasExtraShare()) { - newMNListAtHMinus4C.setBlock(blockMinus4C, blockMinus4C != null && blockMinus4C.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffAtHMinus4C().prevBlockHash)); + newMNListAtHMinus4C.setBlock(blockMinus4C, blockMinus4C != null && blockMinus4C.getHeader().getPrevBlockHash().equals(quorumRotationInfo.getMnListDiffAtHMinus4C().getPrevBlockHash())); } mnListsCache.clear(); @@ -276,8 +276,8 @@ public void applyDiff(Peer peer, DualBlockChain blockChain, for (int i = 0; i < oldDiffs.size(); ++i) { SimplifiedMasternodeList oldList = new SimplifiedMasternodeList(params); oldList = oldList.applyDiff(oldDiffs.get(i)); - mnListsCache.put(oldDiffs.get(i).blockHash, oldList); - quorumSnapshotCache.put(oldDiffs.get(i).blockHash, oldSnapshots.get(i)); + mnListsCache.put(oldDiffs.get(i).getBlockHash(), oldList); + quorumSnapshotCache.put(oldDiffs.get(i).getBlockHash(), oldSnapshots.get(i)); } // TODO: do we actually need to keep track of the blockchain tip mnlist? @@ -306,21 +306,21 @@ public void applyDiff(Peer peer, DualBlockChain blockChain, SimplifiedQuorumList newQuorumListAtHMinus4C = new SimplifiedQuorumList(params); if (quorumRotationInfo.hasExtraShare()) { - baseQuorumList = quorumsCache.get(quorumRotationInfo.getMnListDiffAtHMinus4C().prevBlockHash); + baseQuorumList = quorumsCache.get(quorumRotationInfo.getMnListDiffAtHMinus4C().getPrevBlockHash()); if (baseQuorumList != null) newQuorumListAtHMinus4C = baseQuorumList.applyDiff(quorumRotationInfo.getMnListDiffAtHMinus4C(), isLoadingBootStrap, blockChain, true, false); } - baseQuorumList = quorumsCache.get(quorumRotationInfo.getMnListDiffAtHMinus3C().prevBlockHash); + baseQuorumList = quorumsCache.get(quorumRotationInfo.getMnListDiffAtHMinus3C().getPrevBlockHash()); SimplifiedQuorumList newQuorumListAtHMinus3C = baseQuorumList.applyDiff(quorumRotationInfo.getMnListDiffAtHMinus3C(), isLoadingBootStrap, blockChain, true, false); - baseQuorumList = quorumsCache.get(quorumRotationInfo.getMnListDiffAtHMinus2C().prevBlockHash); + baseQuorumList = quorumsCache.get(quorumRotationInfo.getMnListDiffAtHMinus2C().getPrevBlockHash()); SimplifiedQuorumList newQuorumListAtHMinus2C = baseQuorumList.applyDiff(quorumRotationInfo.getMnListDiffAtHMinus2C(), isLoadingBootStrap, blockChain, true, false); - baseQuorumList = quorumsCache.get(quorumRotationInfo.getMnListDiffAtHMinusC().prevBlockHash); + baseQuorumList = quorumsCache.get(quorumRotationInfo.getMnListDiffAtHMinusC().getPrevBlockHash()); SimplifiedQuorumList newQuorumListAtHMinusC = baseQuorumList.applyDiff(quorumRotationInfo.getMnListDiffAtHMinusC(), isLoadingBootStrap, blockChain, true, false); - baseQuorumList = quorumsCache.get(quorumRotationInfo.getMnListDiffAtH().prevBlockHash); + baseQuorumList = quorumsCache.get(quorumRotationInfo.getMnListDiffAtH().getPrevBlockHash()); SimplifiedQuorumList newQuorumListAtH = baseQuorumList.applyDiff(quorumRotationInfo.getMnListDiffAtH(), isLoadingBootStrap, blockChain, true, false); if (context.masternodeSync.hasVerifyFlag(MasternodeSync.VERIFY_FLAGS.MNLISTDIFF_QUORUM)) { @@ -1268,10 +1268,10 @@ public void processDiff(@Nullable Peer peer, QuorumRotationInfo quorumRotationIn log.info(toString()); } catch (MasternodeListDiffException x) { //we already have this qrinfo or doesn't match our current tipBlockHash - if (mnListAtH.getBlockHash().equals(quorumRotationInfo.getMnListDiffAtH().blockHash)) { + if (mnListAtH.getBlockHash().equals(quorumRotationInfo.getMnListDiffAtH().getBlockHash())) { log.info("heights are the same: " + x.getMessage(), x); - log.info("mnList = {} vs qrinfo {}", mnListTip.getBlockHash(), quorumRotationInfo.getMnListDiffTip().prevBlockHash); - log.info("mnlistdiff {} -> {}", quorumRotationInfo.getMnListDiffTip().prevBlockHash, quorumRotationInfo.getMnListDiffTip().blockHash); + log.info("mnList = {} vs qrinfo {}", mnListTip.getBlockHash(), quorumRotationInfo.getMnListDiffTip().getPrevBlockHash()); + log.info("mnlistdiff {} -> {}", quorumRotationInfo.getMnListDiffTip().getPrevBlockHash(), quorumRotationInfo.getMnListDiffTip().getBlockHash()); log.info("lastRequest: {} -> {}", lastRequest.request.getBaseBlockHashes(), lastRequest.request.getBlockRequestHash()); // remove this block from the list if (!pendingBlocks.isEmpty()) { @@ -1281,8 +1281,8 @@ public void processDiff(@Nullable Peer peer, QuorumRotationInfo quorumRotationIn log.info("heights are different", x); log.info("qrinfo height = {}; mnListAtTip: {}; mnListAtH: {}; quorumListAtH: {}", newHeight, getMnListTip().getHeight(), getMnListAtH().getHeight(), getQuorumListAtTip().getHeight()); - log.info("mnList = {} vs qrinfo = {}", mnListTip.getBlockHash(), quorumRotationInfo.getMnListDiffTip().prevBlockHash); - log.info("qrinfo {} -> {}", quorumRotationInfo.getMnListDiffTip().prevBlockHash, quorumRotationInfo.getMnListDiffTip().blockHash); + log.info("mnList = {} vs qrinfo = {}", mnListTip.getBlockHash(), quorumRotationInfo.getMnListDiffTip().getPrevBlockHash()); + log.info("qrinfo {} -> {}", quorumRotationInfo.getMnListDiffTip().getPrevBlockHash(), quorumRotationInfo.getMnListDiffTip().getBlockHash()); log.info("lastRequest: {} -> {}", lastRequest.request.getBaseBlockHashes(), lastRequest.request.getBlockRequestHash()); log.info("requires reset {}", x.isRequiringReset()); log.info("requires new peer {}", x.isRequiringNewPeer()); diff --git a/core/src/main/java/org/bitcoinj/evolution/QuorumState.java b/core/src/main/java/org/bitcoinj/evolution/QuorumState.java index b3d0bcd43b..645e344f5e 100644 --- a/core/src/main/java/org/bitcoinj/evolution/QuorumState.java +++ b/core/src/main/java/org/bitcoinj/evolution/QuorumState.java @@ -123,7 +123,7 @@ public void applyDiff(Peer peer, DualBlockChain blockChain, boolean isSyncingHeadersFirst = context.peerGroup.getSyncStage() == PeerGroup.SyncStage.MNLIST; long newHeight = ((CoinbaseTx) mnlistdiff.coinBaseTx.getExtraPayloadObject()).getHeight(); - block = blockChain.getBlock(mnlistdiff.blockHash); + block = blockChain.getBlock(mnlistdiff.getBlockHash()); if(!isLoadingBootStrap && block.getHeight() != newHeight) throw new ProtocolException("mnlistdiff blockhash (height="+block.getHeight()+" doesn't match coinbase blockheight: " + newHeight); @@ -134,7 +134,7 @@ public void applyDiff(Peer peer, DualBlockChain blockChain, if(context.masternodeSync.hasVerifyFlag(MasternodeSync.VERIFY_FLAGS.MNLISTDIFF_MNLIST)) newMNList.verify(mnlistdiff.coinBaseTx, mnlistdiff, mnList); if (peer != null && isSyncingHeadersFirst) peer.queueMasternodeListDownloadedListeners(MasternodeListDownloadedListener.Stage.ProcessedMasternodes, mnlistdiff); - newMNList.setBlock(block, block != null && block.getHeader().getPrevBlockHash().equals(mnlistdiff.prevBlockHash)); + newMNList.setBlock(block, block != null && block.getHeader().getPrevBlockHash().equals(mnlistdiff.getPrevBlockHash())); SimplifiedQuorumList newQuorumList = quorumList; if (mnlistdiff.coinBaseTx.getExtraPayloadObject().getVersion() >= SimplifiedMasternodeListManager.LLMQ_FORMAT_VERSION) { @@ -293,24 +293,24 @@ public void processDiff(@Nullable Peer peer, SimplifiedMasternodeListDiff mnlist finishDiff(isLoadingBootStrap); } catch(MasternodeListDiffException x) { // we already have this mnlistdiff or doesn't match our current tipBlockHash - if(getMnList().getBlockHash().equals(mnlistdiff.blockHash)) { - log.info("heights are the same: " + x.getMessage()); - log.info("mnList = {} vs mnlistdiff {}", getMnList().getBlockHash(), mnlistdiff.prevBlockHash); - log.info("mnlistdiff {} -> {}", mnlistdiff.prevBlockHash, mnlistdiff.blockHash); + if(getMnList().getBlockHash().equals(mnlistdiff.getBlockHash())) { + log.info("heights are the same: {}", x.getMessage()); + log.info("mnList = {} vs mnlistdiff {}", getMnList().getBlockHash(), mnlistdiff.getPrevBlockHash()); + log.info("mnlistdiff {} -> {}", mnlistdiff.getPrevBlockHash(), mnlistdiff.getBlockHash()); log.info("lastRequest {} -> {}", lastRequest.request.baseBlockHash, lastRequest.request.blockHash); // remove this block from the list if(!pendingBlocks.isEmpty()) { StoredBlock thisBlock = pendingBlocks.peek(); - if(thisBlock.getHeader().getPrevBlockHash().equals(mnlistdiff.prevBlockHash) && - thisBlock.getHeader().getHash().equals(mnlistdiff.prevBlockHash)) { + if(thisBlock.getHeader().getPrevBlockHash().equals(mnlistdiff.getPrevBlockHash()) && + thisBlock.getHeader().getHash().equals(mnlistdiff.getPrevBlockHash())) { pendingBlocks.pop(); } } } else { - log.info("heights are different " + x.getMessage()); + log.info("heights are different {}", x.getMessage()); log.info("mnlistdiff height = {}; mnList: {}; quorumList: {}", newHeight, getMnList().getHeight(), quorumList.getHeight()); - log.info("mnList = {} vs mnlistdiff = {}", getMnList().getBlockHash(), mnlistdiff.prevBlockHash); - log.info("mnlistdiff {} -> {}", mnlistdiff.prevBlockHash, mnlistdiff.blockHash); + log.info("mnList = {} vs mnlistdiff = {}", getMnList().getBlockHash(), mnlistdiff.getPrevBlockHash()); + log.info("mnlistdiff {} -> {}", mnlistdiff.getPrevBlockHash(), mnlistdiff.getBlockHash()); log.info("lastRequest {} -> {}", lastRequest.request.baseBlockHash, lastRequest.request.blockHash); log.info("requires reset {}", x.isRequiringReset()); log.info("requires new peer {}", x.isRequiringNewPeer()); diff --git a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java index 73b8042684..6f51e8f762 100644 --- a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java +++ b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java @@ -152,7 +152,7 @@ public String toString() { public SimplifiedMasternodeList applyDiff(SimplifiedMasternodeListDiff diff) throws MasternodeListDiffException { CoinbaseTx cbtx = (CoinbaseTx)diff.coinBaseTx.getExtraPayloadObject(); - if(!diff.prevBlockHash.equals(blockHash) && !(diff.prevBlockHash.isZero() && blockHash.equals(params.getGenesisBlock().getHash()))) + if(!diff.getPrevBlockHash().equals(blockHash) && !(diff.getPrevBlockHash().isZero() && blockHash.equals(params.getGenesisBlock().getHash()))) throw new MasternodeListDiffException("The mnlistdiff does not connect to this list. height: " + height + " -> " + cbtx.getHeight(), false, false, height == cbtx.getHeight(), false); @@ -160,7 +160,7 @@ public SimplifiedMasternodeList applyDiff(SimplifiedMasternodeListDiff diff) thr try { SimplifiedMasternodeList result = new SimplifiedMasternodeList(this, diff.getVersion()); - result.blockHash = diff.blockHash; + result.blockHash = diff.getBlockHash(); result.height = cbtx.getHeight(); result.coinbaseTxPayload = cbtx; diff --git a/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java b/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java index afb722a808..4bd62264bd 100644 --- a/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java +++ b/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java @@ -126,12 +126,12 @@ public SimplifiedQuorumList applyDiff(SimplifiedMasternodeListDiff diff, boolean lock.lock(); try { CoinbaseTx cbtx = (CoinbaseTx) diff.getCoinBaseTx().getExtraPayloadObject(); - if(!diff.prevBlockHash.equals(blockHash)) + if(!diff.getPrevBlockHash().equals(blockHash)) throw new MasternodeListDiffException("The mnlistdiff does not connect to this quorum. height: " + height + " vs " + cbtx.getHeight(), false, false, height == cbtx.getHeight(), false); SimplifiedQuorumList result = new SimplifiedQuorumList(this); - result.blockHash = diff.blockHash; + result.blockHash = diff.getBlockHash(); result.height = cbtx.getHeight(); result.coinbaseTxPayload = cbtx; diff --git a/core/src/test/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiffTest.java b/core/src/test/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiffTest.java index afd8c92425..275545b509 100644 --- a/core/src/test/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiffTest.java +++ b/core/src/test/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiffTest.java @@ -59,7 +59,7 @@ public void mnlistdiff_70227() throws IOException { assertArrayEquals(payloadOne, mnlistdiff.bitcoinSerialize()); assertTrue(mnlistdiff.hasChanges()); - assertEquals(Sha256Hash.wrap("0000000000000011a67470334158a97a99867e7969343ee871b6ea1c7733e510"), mnlistdiff.blockHash); + assertEquals(Sha256Hash.wrap("0000000000000011a67470334158a97a99867e7969343ee871b6ea1c7733e510"), mnlistdiff.getBlockHash()); assertFalse(mnlistdiff.hasBasicSchemeKeys()); @@ -74,7 +74,7 @@ public void mnlistdiff_70228_beforeActivation() throws IOException { assertArrayEquals(payloadOne, mnlistdiff.bitcoinSerialize()); assertTrue(mnlistdiff.hasChanges()); - assertEquals(Sha256Hash.wrap("0000001e0b53d7e4e2dea97b0cb8b705fd8b4a6e6d51470f13b56d6588a61f77"), mnlistdiff.blockHash); + assertEquals(Sha256Hash.wrap("0000001e0b53d7e4e2dea97b0cb8b705fd8b4a6e6d51470f13b56d6588a61f77"), mnlistdiff.getBlockHash()); assertEquals(1, mnlistdiff.getVersion()); assertFalse(mnlistdiff.hasBasicSchemeKeys()); @@ -89,7 +89,7 @@ public void mnlistdiff_70228_afterActivation() throws IOException { assertArrayEquals(payloadOne, mnlistdiff.bitcoinSerialize()); assertTrue(mnlistdiff.hasChanges()); - assertEquals(Sha256Hash.wrap("000001a505e030a10fa15b0f1abfe3314886ab8080f5c777321f55749457c7a6"), mnlistdiff.blockHash); + assertEquals(Sha256Hash.wrap("000001a505e030a10fa15b0f1abfe3314886ab8080f5c777321f55749457c7a6"), mnlistdiff.getBlockHash()); assertEquals(1, mnlistdiff.getVersion()); assertTrue(mnlistdiff.hasBasicSchemeKeys()); @@ -104,7 +104,7 @@ public void mnlistdiff_70230() throws IOException { assertArrayEquals(payloadOne, mnlistdiff.bitcoinSerialize()); assertTrue(mnlistdiff.hasChanges()); - assertEquals(Sha256Hash.wrap("000000000000000f78a0addf3f9a4c65a4d0f2ca8e63d5893f8227e1585ef3d8"), mnlistdiff.blockHash); + assertEquals(Sha256Hash.wrap("000000000000000f78a0addf3f9a4c65a4d0f2ca8e63d5893f8227e1585ef3d8"), mnlistdiff.getBlockHash()); assertEquals(1, mnlistdiff.getVersion()); assertTrue(mnlistdiff.hasBasicSchemeKeys()); From f3209d3195eedcc9b5a8cae15cdccdcf617302f0 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 9 Jul 2024 11:58:42 -0700 Subject: [PATCH 05/31] chore: update seed lists for mainnet and testnet from Core 21 Signed-off-by: HashEngineering --- .../org/bitcoinj/params/MainNetParams.java | 369 +++++++++--------- .../org/bitcoinj/params/TestNet3Params.java | 2 +- tools/src/main/python/nodes_main.txt | 369 +++++++++--------- tools/src/main/python/nodes_test.txt | 2 +- 4 files changed, 366 insertions(+), 376 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/params/MainNetParams.java b/core/src/main/java/org/bitcoinj/params/MainNetParams.java index 80a5015122..6a0e2b2377 100644 --- a/core/src/main/java/org/bitcoinj/params/MainNetParams.java +++ b/core/src/main/java/org/bitcoinj/params/MainNetParams.java @@ -111,194 +111,189 @@ public MainNetParams() { // updated with Dash Core 20.0.0 seed list addrSeeds = new int[] { - 0xddd53802, - 0xa947d303, - 0xf22be403, - 0xbe430205, - 0x22ed0905, - 0x13672305, - 0x19672305, - 0x21484e05, - 0xf36d4f05, - 0xe12c6505, - 0x4f6ea105, - 0x077ea105, - 0x12cab505, - 0x5091bd05, - 0xc06aff05, - 0x788ceb0f, - 0x09f48b12, - 0x94819d12, - 0x2af65117, - 0x0a855317, - 0xc4855317, - 0xcb00a317, - 0x24610a1f, - 0x6863941f, - 0x15e36125, - 0xd663f02c, - 0x91f8082d, - 0x9afa082d, - 0x40b60b2d, - 0x1818212d, - 0xf93d212d, - 0x40383a2d, - 0xdd383a2d, - 0x5a6b3f2d, - 0x3a9e472d, - 0x6c9e472d, - 0x689f472d, - 0x5b534c2d, - 0x33574c2d, - 0x7a7a532d, - 0x2d75552d, - 0xca75552d, - 0x2aa3562d, - 0xd95e5b2d, - 0x7fa2042e, - 0xbff10a2e, - 0xd5bd1e2e, - 0xd6bd1e2e, - 0xfbbd1e2e, - 0xf228242e, - 0x091f482e, - 0x79e7942e, - 0x04f1fe2e, - 0x06f1fe2e, - 0x16f1fe2e, - 0x1cf1fe2e, - 0xa66d6d2f, - 0xc538f32f, - 0xb70e7432, - 0xce600f33, - 0x2a750f33, - 0x409b4433, - 0xeda99e33, - 0xac092134, - 0x3c8dca34, - 0x60b9a436, - 0xa6e06e3a, - 0x45f3f442, - 0x46f3f442, - 0xd76b3d45, - 0x8307244b, - 0x8407244b, - 0x8507244b, - 0x0463df4d, - 0x0013534e, - 0x3b1f624f, - 0x5f1d8f4f, - 0xc2b85a50, - 0xaaead150, - 0x8acfd350, - 0x8bddd350, - 0xe784f050, - 0x76f00251, - 0x33fae351, - 0x53e6ca52, - 0x1715d352, - 0xb315d352, - 0x6919d352, - 0xc119d352, - 0x2863ef53, - 0x11320954, - 0x34743454, - 0xccb3f254, - 0x5bf81155, - 0x23f1d155, - 0x47f1d155, - 0xbcf1d155, - 0xbef1d155, - 0xca6bd755, - 0x56fd6257, - 0x712cf957, - 0xf3684959, - 0x1f0b895b, - 0xb94c155d, - 0x658cbe5d, - 0x6ac0f95e, - 0x8d33b75f, - 0x6234b75f, - 0x2c35b75f, - 0x20c4d35f, - 0x22c4d35f, - 0xdb5fa067, - 0xe15fa067, - 0xf95fa067, - 0xe89fe168, - 0x5ed8e168, - 0x7223ee68, - 0x7423ee68, - 0x1609376a, - 0x5a18a16b, - 0x5f41eb6d, - 0x7241eb6d, - 0xaa45eb6d, - 0x8546eb6d, - 0xa640c17b, - 0x9fb5c780, - 0xbae9a282, - 0x40e51285, - 0x081c448a, - 0x22d2ee8c, - 0x5fcdca8e, - 0xa67f5b90, - 0xa78e7e90, - 0xb014ef91, - 0x06309e96, - 0x8b487397, - 0x4aa2659e, - 0x1ca8659e, - 0x3e4f56a7, - 0x045077a8, - 0xf155eba8, - 0xbe68eba8, - 0x7a15f9ad, - 0xcbe922ae, - 0xcce922ae, - 0xcee922ae, - 0xcfe922ae, - 0x914166b0, - 0x75eb3eb2, - 0x81793fb2, - 0x7e5b9db2, - 0xb05b9db2, - 0xb35b9db2, - 0x0c029fb2, - 0xd557d0b2, - 0xe257d0b2, - 0xaa973eb9, - 0xae973eb9, - 0x90d48eb9, - 0x22639bb9, - 0x55a3a4b9, - 0xdaa3a4b9, - 0x75aba5b9, - 0x289eafb9, - 0xc108c9b9, - 0x2218d5b9, - 0x9c53e4b9, - 0xdb73f3b9, - 0x5edf44bc, - 0x28e67fbc, - 0xf3ed7fbc, - 0x844fe1bc, - 0x8c5340c0, - 0x5706a9c0, - 0x595ab8c0, - 0x603b1dc1, - 0x185287c2, - 0xd25f62c3, - 0x40d3b5c3, - 0xcb1205ca, - 0x806e18d4, - 0xd20034d4, - 0xaef9a8d5, - 0x3ed96bd8, - 0x089abdd8, 0x3461fad8, - 0x133c12d9, - 0xf00f45d9 + 0x2e4beed8, + 0x7de8e6d8, + 0x089abdd8, + 0x3ed96bd8, + 0xaef9a8d5, + 0xd20034d4, + 0x806e18d4, + 0xdf6b18d4, + 0x28f7f4cf, + 0x6dd5a8ce, + 0xe2d4a8ce, + 0xb2d4a8ce, + 0x90d4a8ce, + 0xcb1205ca, + 0x40d3b5c3, + 0xd25f62c3, + 0xe4479ec2, + 0xd65187c2, + 0xd69d05c2, + 0x3295a4c1, + 0x603b1dc1, + 0x15391dc1, + 0x5706a9c0, + 0x8c5340c0, + 0xb7c4d0bc, + 0xf3ed7fbc, + 0x28e67fbc, + 0x5edf44bc, + 0xdb73f3b9, + 0x9c53e4b9, + 0x8b7fd9b9, + 0x760dd8b9, + 0x2218d5b9, + 0xf928b9b9, + 0x75aba5b9, + 0xdaa3a4b9, + 0x55a3a4b9, + 0x22639bb9, + 0x90d48eb9, + 0xc85087b9, + 0x078467b9, + 0x3d9557b9, + 0x91651cb9, + 0xe257d0b2, + 0xd557d0b2, + 0x0c029fb2, + 0xb35b9db2, + 0xb05b9db2, + 0xccfe80b2, + 0x81793fb2, + 0x75eb3eb2, + 0x107f7eb0, + 0x0f7f7eb0, + 0x914166b0, + 0xcfe922ae, + 0xcee922ae, + 0xcce922ae, + 0xcbe922ae, + 0x7a15f9ad, + 0x151569ac, + 0x045077a8, + 0x10a958a7, + 0x87ea16a5, + 0xdd17859b, + 0x06309e96, + 0xb014ef91, + 0x5fcdca8e, + 0x54e41285, + 0xbae9a282, + 0xfc783d82, + 0x9fb5c780, + 0xa640c17b, + 0x6446eb6d, + 0xaa45eb6d, + 0x5f41eb6d, + 0x1609376a, + 0x7423ee68, + 0x7223ee68, + 0xf95fa067, + 0xe15fa067, + 0xdb5fa067, + 0x2ec4d35f, + 0x20c4d35f, + 0x08c4d35f, + 0x2c35b75f, + 0x6234b75f, + 0x8d33b75f, + 0xb94c155d, + 0x1f0b895b, + 0x6049b359, + 0x0a137559, + 0xc6694959, + 0x57042859, + 0x56fd6257, + 0xca6bd755, + 0xbef1d155, + 0xbcf1d155, + 0x47f1d155, + 0x23f1d155, + 0xccb3f254, + 0x11320954, + 0xc119d352, + 0x6919d352, + 0xb315d352, + 0x1715d352, + 0x53e6ca52, + 0x33fae351, + 0xaaead150, + 0x5f1d8f4f, + 0x0013534e, + 0x5984e84d, + 0x0484e84d, + 0x0463df4d, + 0xf76b3d45, + 0xd76b3d45, + 0x46f3f442, + 0x45f3f442, + 0xa6e06e3a, + 0x79ea2536, + 0xac092134, + 0x52c49f33, + 0xeda99e33, + 0x409b4433, + 0x2a750f33, + 0xce600f33, + 0xc538f32f, + 0xa66d6d2f, + 0x1cf1fe2e, + 0x15f1fe2e, + 0x06f1fe2e, + 0x04f1fe2e, + 0x20f9fa2e, + 0x091f482e, + 0xf228242e, + 0xfbbd1e2e, + 0xd6bd1e2e, + 0xd5bd1e2e, + 0xbff10a2e, + 0x7fa2042e, + 0xc9138c2d, + 0xd95e5b2d, + 0x2aa3562d, + 0x7a7a532d, + 0xcd284f2d, + 0xcfa94d2d, + 0x5b534c2d, + 0x689f472d, + 0x6c9e472d, + 0x3a9e472d, + 0x5a6b3f2d, + 0xdd383a2d, + 0x1818212d, + 0x40b60b2d, + 0x9afa082d, + 0x91f8082d, + 0xd663f02c, + 0x2e4de52b, + 0xa6684d25, + 0x6863941f, + 0x24610a1f, + 0xcb00a317, + 0x09f48b12, + 0xc06aff05, + 0x1815fc05, + 0x34efbd05, + 0x5091bd05, + 0x2ccab505, + 0x10cab505, + 0x4f6ea105, + 0xf36d4f05, + 0x764a4e05, + 0x6f672305, + 0x4a672305, + 0x40672305, + 0x3a672305, + 0x22ed0905, + 0xbe430205, + 0x39f15203, + 0x41e02303, + 0x2378e902, + 0xddd53802, + 0xdcd53802 }; strSporkAddress = "Xgtyuk76vhuFW2iT7UAiHgNdWXCf3J34wh"; diff --git a/core/src/main/java/org/bitcoinj/params/TestNet3Params.java b/core/src/main/java/org/bitcoinj/params/TestNet3Params.java index 102429bea6..835c7d3bf2 100644 --- a/core/src/main/java/org/bitcoinj/params/TestNet3Params.java +++ b/core/src/main/java/org/bitcoinj/params/TestNet3Params.java @@ -80,7 +80,7 @@ public TestNet3Params() { checkpoints.put(850100, Sha256Hash.wrap("000004728b8ff2a16b9d4eebb0fd61eeffadc9c7fe4b0ec0b5a739869401ab5b")); checkpoints.put(899760, Sha256Hash.wrap("000007b169cbf75796ee1147b24f6cdf627c189490d7472187408e0902413a68")); - // updated with Dash Core 20.0.0 seed list + // updated with Dash Core 21.0.0 seed list addrSeeds = new int[] { 0x2e4de52b, 0xf7a74d2d, diff --git a/tools/src/main/python/nodes_main.txt b/tools/src/main/python/nodes_main.txt index 740779536e..36aa63efa0 100644 --- a/tools/src/main/python/nodes_main.txt +++ b/tools/src/main/python/nodes_main.txt @@ -1,188 +1,183 @@ -2.56.213.221:9999 -3.211.71.169:9999 -3.228.43.242:9999 -5.2.67.190:9999 -5.9.237.34:9999 -5.35.103.19:9999 -5.35.103.25:9999 -5.78.72.33:9999 -5.79.109.243:9999 -5.101.44.225:9999 -5.161.110.79:9999 -5.161.126.7:9999 -5.181.202.18:9999 -5.189.145.80:9999 -5.255.106.192:9999 -15.235.140.120:9999 -18.139.244.9:9999 -18.157.129.148:9999 -23.81.246.42:9999 -23.83.133.10:9999 -23.83.133.196:9999 -23.163.0.203:9999 -31.10.97.36:9999 -31.148.99.104:9999 -37.97.227.21:9999 -44.240.99.214:9999 -45.8.248.145:9999 -45.8.250.154:9999 -45.11.182.64:9999 -45.33.24.24:9999 -45.33.61.249:9999 -45.58.56.64:9999 -45.58.56.221:9999 -45.63.107.90:9999 -45.71.158.58:9999 -45.71.158.108:9999 -45.71.159.104:9999 -45.76.83.91:9999 -45.76.87.51:9999 -45.83.122.122:9999 -45.85.117.45:9999 -45.85.117.202:9999 -45.86.163.42:9999 -45.91.94.217:9999 -46.4.162.127:9999 -46.10.241.191:9999 -46.30.189.213:9999 -46.30.189.214:9999 -46.30.189.251:9999 -46.36.40.242:9999 -46.72.31.9:9999 -46.148.231.121:9999 -46.254.241.4:9999 -46.254.241.6:9999 -46.254.241.22:9999 -46.254.241.28:9999 -47.109.109.166:9999 -47.243.56.197:9999 -50.116.14.183:9999 -51.15.96.206:9999 -51.15.117.42:9999 -51.68.155.64:9999 -51.158.169.237:9999 -52.33.9.172:9999 -52.202.141.60:9999 -54.164.185.96:9999 -58.110.224.166:9999 -66.244.243.69:9999 -66.244.243.70:9999 -69.61.107.215:9999 -75.36.7.131:9999 -75.36.7.132:9999 -75.36.7.133:9999 -77.223.99.4:9999 -78.83.19.0:9999 -79.98.31.59:9999 -79.143.29.95:9999 -80.90.184.194:9999 -80.209.234.170:9999 -80.211.207.138:9999 -80.211.221.139:9999 -80.240.132.231:9999 -81.2.240.118:9999 -81.227.250.51:9999 -82.202.230.83:9999 -82.211.21.23:9999 -82.211.21.179:9999 -82.211.25.105:9999 -82.211.25.193:9999 -83.239.99.40:9999 -84.9.50.17:9999 -84.52.116.52:9999 -84.242.179.204:9999 -85.17.248.91:9999 -85.209.241.35:9999 -85.209.241.71:9999 -85.209.241.188:9999 -85.209.241.190:9999 -85.215.107.202:9999 -87.98.253.86:9999 -87.249.44.113:9999 -89.73.104.243:9999 -91.137.11.31:9999 -93.21.76.185:9999 -93.190.140.101:9999 -94.249.192.106:9999 -95.183.51.141:9999 -95.183.52.98:9999 -95.183.53.44:9999 -95.211.196.32:9999 -95.211.196.34:9999 -103.160.95.219:9999 -103.160.95.225:9999 -103.160.95.249:9999 -104.225.159.232:9999 -104.225.216.94:9999 -104.238.35.114:9999 -104.238.35.116:9999 -106.55.9.22:9999 -107.161.24.90:9999 -109.235.65.95:9999 -109.235.65.114:9999 -109.235.69.170:9999 -109.235.70.133:9999 -123.193.64.166:9999 -128.199.181.159:9999 -130.162.233.186:9999 -133.18.229.64:9999 -138.68.28.8:9999 -140.238.210.34:9999 -142.202.205.95:9999 -144.91.127.166:9999 -144.126.142.167:9999 -145.239.20.176:9999 -150.158.48.6:9999 -151.115.72.139:9999 -158.101.162.74:9999 -158.101.168.28:9999 -167.86.79.62:9999 -168.119.80.4:9999 -168.235.85.241:9999 -168.235.104.190:9999 -173.249.21.122:9999 -174.34.233.203:9999 -174.34.233.204:9999 -174.34.233.206:9999 -174.34.233.207:9999 -176.102.65.145:9999 -178.62.235.117:9999 -178.63.121.129:9999 -178.157.91.126:9999 -178.157.91.176:9999 -178.157.91.179:9999 -178.159.2.12:9999 -178.208.87.213:9999 -178.208.87.226:9999 -185.62.151.170:9999 -185.62.151.174:9999 -185.142.212.144:9999 -185.155.99.34:9999 -185.164.163.85:9999 -185.164.163.218:9999 -185.165.171.117:9999 -185.175.158.40:9999 -185.201.8.193:9999 -185.213.24.34:9999 -185.228.83.156:9999 -185.243.115.219:9999 -188.68.223.94:9999 -188.127.230.40:9999 -188.127.237.243:9999 -188.225.79.132:9999 -192.64.83.140:9999 -192.169.6.87:9999 -192.184.90.89:9999 -193.29.59.96:9999 -194.135.82.24:9999 -195.98.95.210:9999 -195.181.211.64:9999 -202.5.18.203:9999 -212.24.110.128:9999 -212.52.0.210:9999 -213.168.249.174:9999 -216.107.217.62:9999 -216.189.154.8:9999 216.250.97.52:9999 -217.18.60.19:9999 -217.69.15.240:9999 \ No newline at end of file +216.238.75.46:9999 +216.230.232.125:9999 +216.189.154.8:9999 +216.107.217.62:9999 +213.168.249.174:9999 +212.52.0.210:9999 +212.24.110.128:9999 +212.24.107.223:9999 +207.244.247.40:9999 +206.168.213.109:9999 +206.168.212.226:9999 +206.168.212.178:9999 +206.168.212.144:9999 +202.5.18.203:9999 +195.181.211.64:9999 +195.98.95.210:9999 +194.158.71.228:9999 +194.135.81.214:9999 +194.5.157.214:9999 +193.164.149.50:9999 +193.29.59.96:9999 +193.29.57.21:9999 +192.169.6.87:9999 +192.64.83.140:9999 +188.208.196.183:9999 +188.127.237.243:9999 +188.127.230.40:9999 +188.68.223.94:9999 +185.243.115.219:9999 +185.228.83.156:9999 +185.217.127.139:9999 +185.216.13.118:9999 +185.213.24.34:9999 +185.185.40.249:9999 +185.165.171.117:9999 +185.164.163.218:9999 +185.164.163.85:9999 +185.155.99.34:9999 +185.142.212.144:9999 +185.135.80.200:9999 +185.103.132.7:9999 +185.87.149.61:9999 +185.28.101.145:9999 +178.208.87.226:9999 +178.208.87.213:9999 +178.159.2.12:9999 +178.157.91.179:9999 +178.157.91.176:9999 +178.128.254.204:9999 +178.63.121.129:9999 +178.62.235.117:9999 +176.126.127.16:9999 +176.126.127.15:9999 +176.102.65.145:9999 +174.34.233.207:9999 +174.34.233.206:9999 +174.34.233.204:9999 +174.34.233.203:9999 +173.249.21.122:9999 +172.105.21.21:9999 +168.119.80.4:9999 +167.88.169.16:9999 +165.22.234.135:9999 +155.133.23.221:9999 +150.158.48.6:9999 +145.239.20.176:9999 +142.202.205.95:9999 +133.18.228.84:9999 +130.162.233.186:9999 +130.61.120.252:9999 +128.199.181.159:9999 +123.193.64.166:9999 +109.235.70.100:9999 +109.235.69.170:9999 +109.235.65.95:9999 +106.55.9.22:9999 +104.238.35.116:9999 +104.238.35.114:9999 +103.160.95.249:9999 +103.160.95.225:9999 +103.160.95.219:9999 +95.211.196.46:9999 +95.211.196.32:9999 +95.211.196.8:9999 +95.183.53.44:9999 +95.183.52.98:9999 +95.183.51.141:9999 +93.21.76.185:9999 +91.137.11.31:9999 +89.179.73.96:9999 +89.117.19.10:9999 +89.73.105.198:9999 +89.40.4.87:9999 +87.98.253.86:9999 +85.215.107.202:9999 +85.209.241.190:9999 +85.209.241.188:9999 +85.209.241.71:9999 +85.209.241.35:9999 +84.242.179.204:9999 +84.9.50.17:9999 +82.211.25.193:9999 +82.211.25.105:9999 +82.211.21.179:9999 +82.211.21.23:9999 +82.202.230.83:9999 +81.227.250.51:9999 +80.209.234.170:9999 +79.143.29.95:9999 +78.83.19.0:9999 +77.232.132.89:9999 +77.232.132.4:9999 +77.223.99.4:9999 +69.61.107.247:9999 +69.61.107.215:9999 +66.244.243.70:9999 +66.244.243.69:9999 +58.110.224.166:9999 +54.37.234.121:9999 +52.33.9.172:9999 +51.159.196.82:9999 +51.158.169.237:9999 +51.68.155.64:9999 +51.15.117.42:9999 +51.15.96.206:9999 +47.243.56.197:9999 +47.109.109.166:9999 +46.254.241.28:9999 +46.254.241.21:9999 +46.254.241.6:9999 +46.254.241.4:9999 +46.250.249.32:9999 +46.72.31.9:9999 +46.36.40.242:9999 +46.30.189.251:9999 +46.30.189.214:9999 +46.30.189.213:9999 +46.10.241.191:9999 +46.4.162.127:9999 +45.140.19.201:9999 +45.91.94.217:9999 +45.86.163.42:9999 +45.83.122.122:9999 +45.79.40.205:9999 +45.77.169.207:9999 +45.76.83.91:9999 +45.71.159.104:9999 +45.71.158.108:9999 +45.71.158.58:9999 +45.63.107.90:9999 +45.58.56.221:9999 +45.33.24.24:9999 +45.11.182.64:9999 +45.8.250.154:9999 +45.8.248.145:9999 +44.240.99.214:9999 +43.229.77.46:9999 +37.77.104.166:9999 +31.148.99.104:9999 +31.10.97.36:9999 +23.163.0.203:9999 +18.139.244.9:9999 +5.255.106.192:9999 +5.252.21.24:9999 +5.189.239.52:9999 +5.189.145.80:9999 +5.181.202.44:9999 +5.181.202.16:9999 +5.161.110.79:9999 +5.79.109.243:9999 +5.78.74.118:9999 +5.35.103.111:9999 +5.35.103.74:9999 +5.35.103.64:9999 +5.35.103.58:9999 +5.9.237.34:9999 +5.2.67.190:9999 +3.82.241.57:9999 +3.35.224.65:9999 +2.233.120.35:9999 +2.56.213.221:9999 +2.56.213.220:9999 diff --git a/tools/src/main/python/nodes_test.txt b/tools/src/main/python/nodes_test.txt index 4727001d27..519e04919b 100644 --- a/tools/src/main/python/nodes_test.txt +++ b/tools/src/main/python/nodes_test.txt @@ -1,3 +1,3 @@ 43.229.77.46:19999 45.77.167.247:19999 -178.62.203.249:19999 \ No newline at end of file +178.62.203.249:19999 From 18a97125d395fe40ad018e3c6b36125105a4693e Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:41:55 -0700 Subject: [PATCH 06/31] refactor: improve the BLS Lazy classes Signed-off-by: HashEngineering --- .../crypto/BLSAbstractLazyObject.java | 9 ++++++- .../org/bitcoinj/crypto/BLSLazyPublicKey.java | 6 ++++- .../org/bitcoinj/crypto/BLSLazySignature.java | 27 ++++++++++++++++--- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/crypto/BLSAbstractLazyObject.java b/core/src/main/java/org/bitcoinj/crypto/BLSAbstractLazyObject.java index 2b1a31d696..36580ccf7c 100644 --- a/core/src/main/java/org/bitcoinj/crypto/BLSAbstractLazyObject.java +++ b/core/src/main/java/org/bitcoinj/crypto/BLSAbstractLazyObject.java @@ -21,7 +21,7 @@ import java.io.IOException; import java.io.OutputStream; - +import java.util.Arrays; public abstract class BLSAbstractLazyObject extends Message { @@ -59,4 +59,11 @@ public void bitcoinSerialize(OutputStream stream, boolean legacy) throws IOExcep public boolean isInitialized() { return initialized; } + + public abstract byte[] getBuffer(); + + @Override + public int hashCode() { + return Arrays.hashCode(getBuffer()); + } } diff --git a/core/src/main/java/org/bitcoinj/crypto/BLSLazyPublicKey.java b/core/src/main/java/org/bitcoinj/crypto/BLSLazyPublicKey.java index 7c228b9b50..4a34ca06e6 100644 --- a/core/src/main/java/org/bitcoinj/crypto/BLSLazyPublicKey.java +++ b/core/src/main/java/org/bitcoinj/crypto/BLSLazyPublicKey.java @@ -136,7 +136,7 @@ public boolean equals(Object o) { if (that.isInitialized()) return Objects.equals(publicKey, that.publicKey); else - return Objects.equals(publicKey.getBuffer(), that.buffer); + return Arrays.equals(publicKey.getBuffer(), that.buffer); } else { if (that.isInitialized()) return Arrays.equals(buffer, that.publicKey.getBuffer()); @@ -157,4 +157,8 @@ public boolean isValid() { return buffer != null; } } + + public byte [] getBuffer() { + return buffer != null ? buffer : publicKey.getBuffer(); + } } diff --git a/core/src/main/java/org/bitcoinj/crypto/BLSLazySignature.java b/core/src/main/java/org/bitcoinj/crypto/BLSLazySignature.java index dbbbbc95a1..37ae8c3909 100644 --- a/core/src/main/java/org/bitcoinj/crypto/BLSLazySignature.java +++ b/core/src/main/java/org/bitcoinj/crypto/BLSLazySignature.java @@ -10,12 +10,13 @@ import java.io.IOException; import java.io.OutputStream; +import java.util.Arrays; import java.util.concurrent.locks.ReentrantLock; public class BLSLazySignature extends BLSAbstractLazyObject { ReentrantLock lock = Threading.lock("BLSLazySignature"); - Logger log = LoggerFactory.getLogger(BLSLazySignature.class); - BLSSignature signature; + private static final Logger log = LoggerFactory.getLogger(BLSLazySignature.class); + private BLSSignature signature; @Deprecated public BLSLazySignature() { @@ -80,6 +81,7 @@ protected void bitcoinSerializeToStream(OutputStream stream, boolean legacy) thr } } + @Deprecated public BLSLazySignature assign(BLSLazySignature blsLazySignature) { lock.lock(); try { @@ -135,7 +137,11 @@ public BLSSignature getSignature() { @Override public String toString() { - return initialized ? signature.toString() : (buffer == null ? invalidSignature.toString() : Utils.HEX.encode(buffer)); + if (initialized) { + return signature.toString(); + } else { + return buffer == null ? invalidSignature.toString() : Utils.HEX.encode(buffer); + } } public boolean isValid() { @@ -145,4 +151,19 @@ public boolean isValid() { return buffer != null; } } + + public byte [] getBuffer() { + return buffer != null ? buffer : signature.getBuffer(); + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BLSLazySignature)) return false; + + BLSLazySignature that = (BLSLazySignature) o; + byte[] thisBuffer = getBuffer(); + byte[] thatBuffer = that.getBuffer(); + return legacy == that.legacy && Arrays.equals(thisBuffer, thatBuffer); + } } From 59f3ef1c400d02bae69c18ed1fd8f3ac0dcc8b30 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:42:39 -0700 Subject: [PATCH 07/31] refactor: improve the AbstractDiffMessage class * use auto close on a stream Signed-off-by: HashEngineering --- .../org/bitcoinj/evolution/AbstractDiffMessage.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/AbstractDiffMessage.java b/core/src/main/java/org/bitcoinj/evolution/AbstractDiffMessage.java index 65a5103b4a..00f11e524f 100644 --- a/core/src/main/java/org/bitcoinj/evolution/AbstractDiffMessage.java +++ b/core/src/main/java/org/bitcoinj/evolution/AbstractDiffMessage.java @@ -39,11 +39,11 @@ public abstract class AbstractDiffMessage extends Message { private static final Logger log = LoggerFactory.getLogger(AbstractDiffMessage.class); - public AbstractDiffMessage(NetworkParameters params) { + protected AbstractDiffMessage(NetworkParameters params) { super(params); } - public AbstractDiffMessage(NetworkParameters params, byte [] payload, int offset, int protocolVersion) { + protected AbstractDiffMessage(NetworkParameters params, byte [] payload, int offset, int protocolVersion) { super(params, payload, offset, protocolVersion); } @@ -51,11 +51,9 @@ public AbstractDiffMessage(NetworkParameters params, byte [] payload, int offset public void dump(long startHeight, long endHeight) { if (!Utils.isAndroidRuntime() && Context.get().isDebugMode()) { - try { - File dumpFile = new File(getShortName() + "-" + params.getNetworkName() + "-" + startHeight + "-" + endHeight + ".dat"); - OutputStream stream = new FileOutputStream(dumpFile); + File dumpFile = new File(getShortName() + "-" + params.getNetworkName() + "-" + startHeight + "-" + endHeight + ".dat"); + try (OutputStream stream = new FileOutputStream(dumpFile)) { stream.write(bitcoinSerialize()); - stream.close(); log.info("dump successful"); } catch (FileNotFoundException x) { log.warn("could not dump {} - file not found.", getShortName(), x); From b21d9130742126dc781dab31ec388b008ddddd8b Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:43:05 -0700 Subject: [PATCH 08/31] refactor: make all constructors abstract in AbstractQuorumRequest Signed-off-by: HashEngineering --- .../java/org/bitcoinj/evolution/AbstractQuorumRequest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumRequest.java b/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumRequest.java index 6947d583d5..29eb6c0ad9 100644 --- a/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumRequest.java +++ b/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumRequest.java @@ -28,15 +28,15 @@ public abstract class AbstractQuorumRequest extends Message { - public AbstractQuorumRequest() { + protected AbstractQuorumRequest() { super(); } - public AbstractQuorumRequest(NetworkParameters params) { + protected AbstractQuorumRequest(NetworkParameters params) { super(params); } - public AbstractQuorumRequest(NetworkParameters params, byte [] payload, int offset) { + protected AbstractQuorumRequest(NetworkParameters params, byte [] payload, int offset) { super(params, payload, offset); } From 36b3b593bae9f9c76ca00b360a0a29c3303f09ba Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:43:59 -0700 Subject: [PATCH 09/31] refactor: remove unused fields in ChainLocksHandler Signed-off-by: HashEngineering --- .../bitcoinj/quorums/ChainLocksHandler.java | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java b/core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java index 86656b9714..b963d66b70 100644 --- a/core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java +++ b/core/src/main/java/org/bitcoinj/quorums/ChainLocksHandler.java @@ -15,6 +15,7 @@ */ package org.bitcoinj.quorums; +import com.google.common.annotations.VisibleForTesting; import org.bitcoinj.core.*; import org.bitcoinj.core.listeners.NewBestBlockListener; import org.bitcoinj.crypto.BLSSecretKey; @@ -47,11 +48,10 @@ */ public class ChainLocksHandler extends AbstractManager implements RecoveredSignatureListener { - static final long CLEANUP_INTERVAL = 1000 * 30; - static final long CLEANUP_SEEN_TIMEOUT = 24 * 60 * 60 * 1000; + static final long CLEANUP_INTERVAL = 1000L * 30; + static final long CLEANUP_SEEN_TIMEOUT = 24 * 60 * 60 * 1000L; private SigningManager quorumSigningManager; - private InstantSendManager quorumInstantSendManager; private static final Logger log = LoggerFactory.getLogger(ChainLocksHandler.class); private final ReentrantLock lock = Threading.lock("ChainLocksHandler"); @@ -99,9 +99,9 @@ public void setBlockChain(AbstractBlockChain blockChain, AbstractBlockChain head this.headerChain = headerChain; this.blockChain.addNewBestBlockListener(this.newBestBlockListener); this.quorumSigningManager = context.signingManager; - this.quorumInstantSendManager = context.instantSendManager; } + @Override public void close() { if (blockChain != null) { blockChain.removeNewBestBlockListener(this.newBestBlockListener); @@ -119,14 +119,15 @@ public void onNewRecoveredSignature(RecoveredSignature recoveredSig) { //do nothing. In Dash Core, this handles signing CLSIG's } - public void start() - { + @VisibleForTesting + public void start() { quorumSigningManager.addRecoveredSignatureListener(this); // TODO: start the scheduler here: // processChainLock(); scheduledExecutorService = Executors.newScheduledThreadPool(1); } + @VisibleForTesting public void stop() { try { quorumSigningManager.removeRecoveredSignatureListener(this); @@ -150,8 +151,7 @@ public boolean alreadyHave(InventoryItem inv) return seenChainLocks.containsKey(inv.hash); } - public ChainLockSignature getChainLockByHash(Sha256Hash hash) - { + public ChainLockSignature getChainLockByHash(Sha256Hash hash) { lock.lock(); try { if (hash != bestChainLockHash) { @@ -273,7 +273,7 @@ void acceptedBlockHeader(StoredBlock newBlock) { try { if (newBlock.getHeader().getHash().equals(bestChainLock.blockHash)) { - log.info("block header {} came in late, updating and enforcing", newBlock.getHeader().getHash().toString()); + log.info("block header {} came in late, updating and enforcing", newBlock.getHeader().getHash()); if (bestChainLock.height != newBlock.getHeight()) { // Should not happen, same as the conflict check from ProcessNewChainLock. @@ -415,10 +415,7 @@ boolean internalHasChainLock(long height, Sha256Hash blockHash) { return blockHash == bestChainLockBlock.getHeader().getHash(); } - StoredBlock cursor = bestChainLockBlock; - while(cursor != null) { - cursor = cursor.getPrev(blockChain.getBlockStore()); - } + StoredBlock cursor = bestChainLockBlock.getAncestor(blockChain.getBlockStore(), (int) height); return cursor != null && cursor.getHeader().getHash().equals(blockHash); } catch (BlockStoreException x) { return false; @@ -452,11 +449,8 @@ boolean internalHasConflictingChainLock(long height, Sha256Hash blockHash) { return blockHash != bestChainLockBlock.getHeader().getHash(); } - StoredBlock cursor = bestChainLockBlock; - while(cursor != null) { - cursor = cursor.getPrev(blockChain.getBlockStore()); - } - return cursor != null && !cursor.getHeader().getHash().equals(blockHash); + StoredBlock cursor = bestChainLockBlock.getAncestor(blockChain.getBlockStore(), (int) height); + return cursor != null && cursor.getHeader().getHash().equals(blockHash); } catch (BlockStoreException x) { return false; } @@ -485,7 +479,7 @@ void cleanup() { private final NewBestBlockListener newBestBlockListener = block -> updatedBlockTip(block, null); - private final transient CopyOnWriteArrayList> chainLockListeners; + private final CopyOnWriteArrayList> chainLockListeners; /** * Adds an event listener object. Methods on this object are called when something interesting happens, @@ -542,12 +536,12 @@ public int calculateMessageSizeInBytes() { @Override public void checkAndRemove() { - + // there is nothing to check in this class } @Override public void clear() { - + // there is nothing to clear in this class } @Override From a8c9fa3fc7d0ae59667315b7bc54026bbc648858 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:44:32 -0700 Subject: [PATCH 10/31] refactor: add @Override and license to ChainLockSignature Signed-off-by: HashEngineering --- .../bitcoinj/quorums/ChainLockSignature.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/quorums/ChainLockSignature.java b/core/src/main/java/org/bitcoinj/quorums/ChainLockSignature.java index 9b32729fbd..e2cb6d5c61 100644 --- a/core/src/main/java/org/bitcoinj/quorums/ChainLockSignature.java +++ b/core/src/main/java/org/bitcoinj/quorums/ChainLockSignature.java @@ -1,7 +1,22 @@ +/* + * Copyright 2019 Dash Core Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.bitcoinj.quorums; import org.bitcoinj.core.*; -import org.bitcoinj.crypto.BLSScheme; import org.bitcoinj.crypto.BLSSignature; import java.io.ByteArrayOutputStream; @@ -47,6 +62,7 @@ protected void bitcoinSerializeToStream(OutputStream stream) throws IOException signature.bitcoinSerialize(stream); } + @Override public Sha256Hash getHash() { try { UnsafeByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(getMessageSize()); From d7953b688d2b701c0c9f7b0ab655c9867c49dbc9 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:45:12 -0700 Subject: [PATCH 11/31] refactor: use diamond <> for implied template parameters Signed-off-by: HashEngineering --- .../evolution/DeterministicMasternodeList.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/DeterministicMasternodeList.java b/core/src/main/java/org/bitcoinj/evolution/DeterministicMasternodeList.java index a0a2522be6..02919f9d52 100644 --- a/core/src/main/java/org/bitcoinj/evolution/DeterministicMasternodeList.java +++ b/core/src/main/java/org/bitcoinj/evolution/DeterministicMasternodeList.java @@ -24,7 +24,7 @@ public class DeterministicMasternodeList extends Message { this.blockHash = other.blockHash; this.height = other.height; - mnMap = new HashMap(other.mnMap.size()); + mnMap = new HashMap<>(other.mnMap.size()); for(Map.Entry entry : mnMap.entrySet()) { mnMap.put(entry.getKey(), new DeterministicMasternode(entry.getValue())); } @@ -35,7 +35,7 @@ protected void parse() throws ProtocolException { blockHash = readHash(); height = (int)readUint32(); int size = (int)readVarInt(); - mnMap = new HashMap(size); + mnMap = new HashMap<>(size); for(int i = 0; i < size; ++i) { Sha256Hash hash = readHash(); @@ -45,13 +45,13 @@ protected void parse() throws ProtocolException { } size = (int)readVarInt(); - mnUniquePropertyMap = new HashMap>(size); + mnUniquePropertyMap = new HashMap<>(size); for(long i = 0; i < size; ++i) { Sha256Hash hash = readHash(); Sha256Hash first = readHash(); int second = (int)readUint32(); - mnUniquePropertyMap.put(hash, new Pair(first, second)); + mnUniquePropertyMap.put(hash, new Pair<>(first, second)); } } @@ -158,7 +158,7 @@ void deleteUniqueProperty(DeterministicMasternode dmn, T oldValue) if (p.getSecond() == 1) { mnUniquePropertyMap.remove(oldHash); } else { - mnUniquePropertyMap.put(oldHash, new Pair(dmn.proRegTxHash, p.getSecond() - 1)); + mnUniquePropertyMap.put(oldHash, new Pair<>(dmn.proRegTxHash, p.getSecond() - 1)); } } @Deprecated From 2726331658a6bc80344893b94dfa83bc9ab8e08b Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:46:22 -0700 Subject: [PATCH 12/31] refactor: remove deprecated methods in InstantSendManager Signed-off-by: HashEngineering --- .../src/main/java/org/bitcoinj/core/Peer.java | 2 +- .../bitcoinj/quorums/InstantSendManager.java | 58 +++++++------------ 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/Peer.java b/core/src/main/java/org/bitcoinj/core/Peer.java index 997707fe3a..1c44948f92 100644 --- a/core/src/main/java/org/bitcoinj/core/Peer.java +++ b/core/src/main/java/org/bitcoinj/core/Peer.java @@ -1501,7 +1501,7 @@ protected void processInv(InventoryMessage inv) { } // The New InstantSendLock (ISLOCK) - if(context.instantSendManager != null && context.instantSendManager.isNewInstantSendEnabled() && + if(context.instantSendManager != null && context.instantSendManager.isInstantSendEnabled() && context.masternodeSync != null && context.masternodeSync.hasSyncFlag(MasternodeSync.SYNC_FLAGS.SYNC_INSTANTSENDLOCKS)) { it = instantSendLocks.iterator(); while (it.hasNext()) { diff --git a/core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java b/core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java index d1e4d314e1..a70897a4d7 100644 --- a/core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java +++ b/core/src/main/java/org/bitcoinj/quorums/InstantSendManager.java @@ -37,7 +37,7 @@ public class InstantSendManager implements RecoveredSignatureListener { ReentrantLock lock = Threading.lock("InstantSendManager"); InstantSendDatabase db; Thread workThread; - public boolean runWithoutThread; + private boolean runWithoutThread = true; AbstractBlockChain blockChain; //Keep track of when the ISLOCK arrived @@ -50,8 +50,8 @@ public InstantSendManager(Context context, InstantSendDatabase db) { this.context = context; this.db = db; this.quorumSigningManager = context.signingManager; - pendingInstantSendLocks = new HashMap>(); - invalidInstantSendLocks = new HashMap(); + pendingInstantSendLocks = new HashMap<>(); + invalidInstantSendLocks = new HashMap<>(); } @Override @@ -90,20 +90,7 @@ private boolean isInitialized() { return blockChain != null; } - @Deprecated - public boolean isOldInstantSendEnabled() - { - return false; - } - - @Deprecated - public boolean isNewInstantSendEnabled() - { - return isInstantSendEnabled(); - } - - public boolean isInstantSendEnabled() - { + public boolean isInstantSendEnabled() { return context.sporkManager.isSporkActive(SporkId.SPORK_2_INSTANTSEND_ENABLED); } @@ -188,7 +175,7 @@ private boolean preVerifyInstantSendLock(InstantSendLock islock) return false; } - HashSet dups = new HashSet(); + HashSet dups = new HashSet<>(); for (TransactionOutPoint o : islock.inputs) { if (!dups.add(o)) { return false; @@ -242,6 +229,7 @@ public void run() { } }; + @Deprecated public void start() { if(!runWithoutThread) { if (workThread != null) @@ -253,6 +241,7 @@ public void start() { quorumSigningManager.addRecoveredSignatureListener(this); } + @Deprecated public void stop() { quorumSigningManager.removeRecoveredSignatureListener(this); @@ -304,7 +293,7 @@ public boolean checkCanLock(Transaction tx, boolean printDebug) value = value.add(utxo.getValue()); } catch (BlockStoreException x) { - log.error("BlockStoreException: "+ x.getMessage()); + log.error("BlockStoreException: ", x); } } else { @@ -333,7 +322,6 @@ boolean checkCanLock(TransactionOutPoint outpoint, boolean printDebug, Sha256Has return false; } - Transaction tx; Sha256Hash hashBlock = null; BlockStore blockStore = blockChain.getBlockStore(); UTXO utxo; @@ -381,17 +369,15 @@ boolean checkCanLock(TransactionOutPoint outpoint, boolean printDebug, Sha256Has if(output != null) { Transaction parent = output.getParentTransaction(); TransactionConfidence confidence = parent.getConfidence(); - if(confidence != null) { - if (confidence.getDepthInBlocks() < nInstantSendConfirmationsRequired) { - StoredBlock block = blockStore.get(confidence.getAppearedAtChainHeight()); - if (context.chainLockHandler.hasChainLock(confidence.getAppearedAtChainHeight(), block.getHeader().getHash())) - { - if (printDebug) { - log.info("txid={}: outpoint {} too new and not ChainLocked. nTxAge={}, nInstantSendConfirmationsRequired={}", - txHash, outpoint.toStringShort(), confidence.getDepthInBlocks(), nInstantSendConfirmationsRequired); - } - return false; + if(confidence != null && confidence.getDepthInBlocks() < nInstantSendConfirmationsRequired) { + StoredBlock block = blockStore.get(confidence.getAppearedAtChainHeight()); + if (context.chainLockHandler.hasChainLock(confidence.getAppearedAtChainHeight(), block.getHeader().getHash())) + { + if (printDebug) { + log.info("txid={}: outpoint {} too new and not ChainLocked. nTxAge={}, nInstantSendConfirmationsRequired={}", + txHash, outpoint.toStringShort(), confidence.getDepthInBlocks(), nInstantSendConfirmationsRequired); } + return false; } } } @@ -494,8 +480,8 @@ HashSet processPendingInstantSendLocks(LLMQParameters.LLMQType llmqT tipHeight = blockChain.getBestChainHeight(); HashSet badISLocks = new HashSet<>(pend.size()); - BLSBatchVerifier batchVerifier = new BLSBatchVerifier(false, true, 8); - HashMap> recSigs = new HashMap>(); + BLSBatchVerifier batchVerifier = new BLSBatchVerifier<>(false, true, 8); + HashMap> recSigs = new HashMap<>(); int verifyCount = 0; int alreadyVerified = 0; @@ -628,7 +614,7 @@ HashSet processPendingInstantSendLocks(LLMQParameters.LLMQType llmqT // TODO: how shall we handle failed islock verification? should we test again or forget? // testing again means that we might be a block behind or something // for now, let us not save this to be retested forever... - //invalidInstantSendLocks.put(islock, Utils.currentTimeSeconds()); + // invalidInstantSendLocks.put(islock, Utils.currentTimeSeconds()); TransactionConfidence confidence = context.getConfidenceTable().get(islock.txid); if(confidence != null) { log.info("islock: set to IX_LOCK_FAILED for {}", islock.txid); @@ -911,11 +897,9 @@ boolean isLocked(Sha256Hash txHash) } } - public boolean isConflicted(Transaction tx) - { + public boolean isConflicted(Transaction tx) { lock.lock(); try { - Sha256Hash dummy; return getConflictingTx(tx) != null; } finally { lock.unlock(); @@ -956,6 +940,8 @@ public void onNewRecoveredSignature(RecoveredSignature recoveredSig) { if (llmqType == LLMQParameters.LLMQType.LLMQ_NONE) { return; } + + // TODO: what does Dash Core do here? } TransactionReceivedInBlockListener transactionReceivedInBlockListener = new TransactionReceivedInBlockListener() { From 1467ffaa27edbb3749bafca2a1c31442af2913fc Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:47:14 -0700 Subject: [PATCH 13/31] tests: fix and simplify some tests in DnsDiscoveryTest and ChainLocksHandlerTest Signed-off-by: HashEngineering --- .../bitcoinj/net/discovery/DnsDiscoveryTest.java | 2 +- .../org/bitcoinj/quorums/ChainLocksHandlerTest.java | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/core/src/test/java/org/bitcoinj/net/discovery/DnsDiscoveryTest.java b/core/src/test/java/org/bitcoinj/net/discovery/DnsDiscoveryTest.java index bce2013833..f5a148cb1e 100644 --- a/core/src/test/java/org/bitcoinj/net/discovery/DnsDiscoveryTest.java +++ b/core/src/test/java/org/bitcoinj/net/discovery/DnsDiscoveryTest.java @@ -31,7 +31,7 @@ public class DnsDiscoveryTest { public void testBuildDiscoveries() throws PeerDiscoveryException { String[] seeds = new String[] { "dnsseed.dash.org" }; DnsDiscovery dnsDiscovery = new DnsDiscovery(seeds, MainNetParams.get()); - assertTrue(dnsDiscovery.seeds.size() == 2); + assertTrue(dnsDiscovery.seeds.size() == 1); for (PeerDiscovery peerDiscovery : dnsDiscovery.seeds) { assertTrue(peerDiscovery.getPeers(0, 100, TimeUnit.MILLISECONDS).length > 0); } diff --git a/core/src/test/java/org/bitcoinj/quorums/ChainLocksHandlerTest.java b/core/src/test/java/org/bitcoinj/quorums/ChainLocksHandlerTest.java index 69015244c0..da840f391d 100644 --- a/core/src/test/java/org/bitcoinj/quorums/ChainLocksHandlerTest.java +++ b/core/src/test/java/org/bitcoinj/quorums/ChainLocksHandlerTest.java @@ -6,10 +6,8 @@ import org.bitcoinj.core.PeerGroup; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.SporkId; -import org.bitcoinj.core.SporkMessage; import org.bitcoinj.core.Utils; import org.bitcoinj.evolution.SimplifiedMasternodeListManager; -import org.bitcoinj.evolution.SimplifiedMasternodesTest; import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.store.BlockStore; import org.bitcoinj.store.BlockStoreException; @@ -20,10 +18,7 @@ import org.junit.Test; import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.net.URL; -import java.nio.file.Files; import java.util.Objects; import static org.junit.Assert.assertEquals; @@ -43,7 +38,7 @@ public class ChainLocksHandlerTest { ChainLockSignature clsig; @Before - public void setUp() throws BlockStoreException, IOException { + public void setUp() throws BlockStoreException { initContext(); clsig = new ChainLockSignature(params, clsigData, false); } @@ -54,7 +49,7 @@ public void tearDown() throws BlockStoreException { blockStore.close(); } - void initContext() throws BlockStoreException, IOException { + void initContext() throws BlockStoreException { if (context == null || !context.getParams().equals(params)) context = new Context(params); blockStore = new SPVBlockStore(params, new File(Objects.requireNonNull(getClass().getResource(blockchainFile)).getPath())); @@ -63,8 +58,6 @@ void initContext() throws BlockStoreException, IOException { context.initDash(true, true); peerGroup = new PeerGroup(context.getParams(), blockChain, blockChain); - InputStream stream = null; - SimplifiedMasternodeListManager manager = context.masternodeListManager; URL mnlistManagerFile = Objects.requireNonNull(getClass().getResource(mnlistFile)); FlatDB db2 = new FlatDB<>(Context.get(), mnlistManagerFile.getFile(), true, manager.getDefaultMagicMessage(), 5); @@ -87,7 +80,7 @@ public void processChainLockTest() { } @Test - public void serializationTest() throws IOException { + public void serializationTest() { URL datafile = Objects.requireNonNull(getClass().getResource("testnet-block-905773.chainlocks")); FlatDB clh = new FlatDB<>(context, datafile.getFile(), true); clh.load(chainLocksHandler); From e336419327f747eb8882033176c819a9e877da8f Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:47:37 -0700 Subject: [PATCH 14/31] refactor: make all fields private in MasternodeListDiffException Signed-off-by: HashEngineering --- .../evolution/MasternodeListDiffException.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/MasternodeListDiffException.java b/core/src/main/java/org/bitcoinj/evolution/MasternodeListDiffException.java index a95ac19ddb..9ca343d780 100644 --- a/core/src/main/java/org/bitcoinj/evolution/MasternodeListDiffException.java +++ b/core/src/main/java/org/bitcoinj/evolution/MasternodeListDiffException.java @@ -1,11 +1,11 @@ package org.bitcoinj.evolution; public class MasternodeListDiffException extends Exception { - boolean requireReset; - boolean findNewPeer; + private final boolean requireReset; + private final boolean findNewPeer; - boolean sameHeight; - boolean merkleRootMismatch; + private final boolean sameHeight; + private final boolean merkleRootMismatch; public MasternodeListDiffException(String message, boolean requireReset, boolean findNewPeer, boolean sameHeight, boolean merkleRootMismatch) { super(message); this.requireReset = requireReset; @@ -25,4 +25,8 @@ public boolean isRequiringNewPeer() { public boolean hasMerkleRootMismatch() { return merkleRootMismatch; } + + public boolean isSameHeight() { + return sameHeight; + } } From 873a1a6493c959708187d7fdd5b1048835e1b3b2 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:48:31 -0700 Subject: [PATCH 15/31] refactor: remove deprecated methods and simplify some getters in QuorumRotationInfo Signed-off-by: HashEngineering --- .../bitcoinj/quorums/QuorumRotationInfo.java | 52 ++----------------- 1 file changed, 4 insertions(+), 48 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/quorums/QuorumRotationInfo.java b/core/src/main/java/org/bitcoinj/quorums/QuorumRotationInfo.java index 41e9add93b..25a7c8c717 100644 --- a/core/src/main/java/org/bitcoinj/quorums/QuorumRotationInfo.java +++ b/core/src/main/java/org/bitcoinj/quorums/QuorumRotationInfo.java @@ -24,16 +24,14 @@ import org.bitcoinj.core.VarInt; import org.bitcoinj.evolution.AbstractDiffMessage; import org.bitcoinj.evolution.SimplifiedMasternodeListDiff; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.List; public class QuorumRotationInfo extends AbstractDiffMessage { - private static final Logger log = LoggerFactory.getLogger(QuorumRotationInfo.class); private static final String SHORT_NAME = "qrinfo"; QuorumSnapshot quorumSnapshotAtHMinusC; @@ -53,11 +51,6 @@ public class QuorumRotationInfo extends AbstractDiffMessage { ArrayList quorumSnapshotList; ArrayList mnListDiffLists; - @Deprecated - QuorumRotationInfo(NetworkParameters params) { - super(params); - } - public QuorumRotationInfo(NetworkParameters params, byte [] payload, int protocolVersion) { super(params, payload, 0, protocolVersion); } @@ -193,15 +186,15 @@ public QuorumSnapshot getQuorumSnapshotAtHMinus4C() { return quorumSnapshotAtHMinus4C; } - public ArrayList getLastCommitmentPerIndex() { + public List getLastCommitmentPerIndex() { return lastCommitmentPerIndex; } - public ArrayList getMnListDiffLists() { + public List getMnListDiffLists() { return mnListDiffLists; } - public ArrayList getQuorumSnapshotList() { + public List getQuorumSnapshotList() { return quorumSnapshotList; } @@ -259,43 +252,6 @@ public String toString(DualBlockChain chain) { return builder.toString(); } - // these are for tests so they have package level access - void setQuorumSnapshotAtHMinusC(QuorumSnapshot quorumSnapshotAtHMinusC) { - this.quorumSnapshotAtHMinusC = quorumSnapshotAtHMinusC; - } - - void setQuorumSnapshotAtHMinus2C(QuorumSnapshot quorumSnapshotAtHMinusC) { - this.quorumSnapshotAtHMinus2C = quorumSnapshotAtHMinusC; - } - - public void setQuorumSnapshotAtHMinus3C(QuorumSnapshot quorumSnapshotAtHMinus3C) { - this.quorumSnapshotAtHMinus3C = quorumSnapshotAtHMinus3C; - } - - public void setQuorumSnapshotAtHMinus34(QuorumSnapshot quorumSnapshotAtHMinus4C) { - this.quorumSnapshotAtHMinus4C = quorumSnapshotAtHMinus4C; - } - - public void setMnListDiffTip(SimplifiedMasternodeListDiff mnListDiffTip) { - this.mnListDiffTip = mnListDiffTip; - } - - public void setMnListDiffAtHMinusC(SimplifiedMasternodeListDiff mnListDiffAtHMinusC) { - this.mnListDiffAtHMinusC = mnListDiffAtHMinusC; - } - - public void setMnListDiffAtHMinus2C(SimplifiedMasternodeListDiff mnListDiffAtHMinus2C) { - this.mnListDiffAtHMinus2C = mnListDiffAtHMinus2C; - } - - public void setMnListDiffAtHMinus3C(SimplifiedMasternodeListDiff mnListDiffAtHMinus3C) { - this.mnListDiffAtHMinus3C = mnListDiffAtHMinus3C; - } - - public void setMnListDiffAtHMinus4C(SimplifiedMasternodeListDiff mnListDiffAtHMinus4C) { - this.mnListDiffAtHMinus4C = mnListDiffAtHMinus4C; - } - public boolean hasChanges() { return mnListDiffTip.hasChanges() || mnListDiffAtH.hasChanges() || mnListDiffAtHMinusC.hasChanges() || mnListDiffAtHMinus2C.hasChanges() || mnListDiffAtHMinus3C.hasChanges() || From 2204cb1d6da76c04aaf72fe4262c1f4df04ca946 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:53:54 -0700 Subject: [PATCH 16/31] fix: fix equals in FinalCommitment and refactor some * add hashCode Signed-off-by: HashEngineering --- .../org/bitcoinj/quorums/FinalCommitment.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/quorums/FinalCommitment.java b/core/src/main/java/org/bitcoinj/quorums/FinalCommitment.java index f7c6c98f77..c855c68a5c 100644 --- a/core/src/main/java/org/bitcoinj/quorums/FinalCommitment.java +++ b/core/src/main/java/org/bitcoinj/quorums/FinalCommitment.java @@ -68,11 +68,11 @@ public FinalCommitment(NetworkParameters params, byte [] payload, int offset) { } public FinalCommitment(NetworkParameters params, LLMQParameters llmqParameters, Sha256Hash quorumHash) { - super(0); + super(params,0); this.llmqType = llmqParameters.type.getValue(); this.quorumHash = quorumHash; - this.signers = new ArrayList(llmqParameters.size); - this.validMembers = new ArrayList(llmqParameters.size); + this.signers = new ArrayList<>(llmqParameters.size); + this.validMembers = new ArrayList<>(llmqParameters.size); } public FinalCommitment(NetworkParameters params, Transaction tx) { @@ -83,7 +83,7 @@ public FinalCommitment(NetworkParameters params, int version, int llmqType, Sha256Hash quorumHash, int quorumIndex, int signersCount, byte [] signers, int validMembersCount, byte [] validMembers, byte [] quorumPublicKey, Sha256Hash quorumVvecHash, BLSLazySignature signature, BLSLazySignature membersSignature) { - super(version); + super(params, version); this.llmqType = llmqType; this.quorumHash = quorumHash; this.quorumIndex = quorumIndex; @@ -237,8 +237,8 @@ public int countValidMembers() { return Collections.frequency(validMembers, Boolean.TRUE); } - public boolean verify(StoredBlock block, ArrayList members, boolean checkSigs) { - int expectedVersion = LEGACY_BLS_NON_INDEXED_QUORUM_VERSION; + public boolean verify(StoredBlock block, List members, boolean checkSigs) { + int expectedVersion; if (LLMQUtils.isQuorumRotationEnabled(block, params, LLMQParameters.LLMQType.fromValue(llmqType))) { expectedVersion = params.isV19Active(block.getHeight()) ? BASIC_BLS_INDEXED_QUORUM_VERSION : LEGACY_BLS_INDEXED_QUORUM_VERSION; } else { @@ -420,7 +420,7 @@ public boolean isVerified() { public boolean equals(Object o) { if (o instanceof FinalCommitment) { FinalCommitment fc = (FinalCommitment) o; - if (version == fc.version && + return version == fc.version && llmqType == fc.llmqType && quorumHash.equals(fc.quorumHash) && quorumIndex == fc.quorumIndex && @@ -431,11 +431,16 @@ public boolean equals(Object o) { quorumPublicKey.equals(fc.quorumPublicKey) && quorumVvecHash.equals(fc.quorumVvecHash) && quorumSignature.equals(fc.quorumSignature) && - membersSignature.equals(fc.membersSignature) - ) { - return true; - } + membersSignature.equals(fc.membersSignature); } return false; } + + @Override + public int hashCode() { + int result = llmqType; + result = 31 * result + quorumHash.hashCode(); + result = 31 * result + quorumIndex; + return result; + } } From 313b581f10824d2ed4614afb7553c21d8edb6f91 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:55:58 -0700 Subject: [PATCH 17/31] refactor: make one field final in GetSimplifiedMasternodeListDiff Signed-off-by: HashEngineering --- .../org/bitcoinj/evolution/GetSimplifiedMasternodeListDiff.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/GetSimplifiedMasternodeListDiff.java b/core/src/main/java/org/bitcoinj/evolution/GetSimplifiedMasternodeListDiff.java index 4951e4828b..35ffccb446 100644 --- a/core/src/main/java/org/bitcoinj/evolution/GetSimplifiedMasternodeListDiff.java +++ b/core/src/main/java/org/bitcoinj/evolution/GetSimplifiedMasternodeListDiff.java @@ -14,7 +14,7 @@ public class GetSimplifiedMasternodeListDiff extends AbstractQuorumRequest { Sha256Hash baseBlockHash; Sha256Hash blockHash; - public static int MESSAGE_SIZE = 64; + public static final int MESSAGE_SIZE = 64; public GetSimplifiedMasternodeListDiff(Sha256Hash baseBlockHash, Sha256Hash blockHash) { super(); From ec3191d81311aa8f46b1c3d7541edfd1078f7065 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:58:35 -0700 Subject: [PATCH 18/31] refactor: remove deprecated methods and simplify getters in SimplifiedMasternodeListManager Signed-off-by: HashEngineering --- .../evolution/SimplifiedMasternodeListManager.java | 13 ++++--------- .../org/bitcoinj/governance/GovernanceManager.java | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java index f2a80a8a15..28fa689fb2 100644 --- a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java +++ b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java @@ -75,8 +75,8 @@ public class SimplifiedMasternodeListManager extends AbstractManager implements public static final int BLS_SCHEME_FORMAT_VERSION = 4; public static final int SMLE_VERSION_FORMAT_VERSION = 5; - public static int MAX_CACHE_SIZE = 10; - public static int MIN_CACHE_SIZE = 1; + public static final int MAX_CACHE_SIZE = 10; + public static final int MIN_CACHE_SIZE = 1; private ExecutorService threadPool = Executors.newFixedThreadPool(1, new ContextPropagatingThreadFactory("process-qrinfo")); public List getAllQuorums(LLMQParameters.LLMQType llmqType) { @@ -249,7 +249,7 @@ protected void bitcoinSerializeToStream(OutputStream stream) throws IOException if (getFormatVersion() >= LLMQ_FORMAT_VERSION) { quorumState.serializeQuorumsToStream(stream); if (quorumState.syncOptions != MasternodeListSyncOptions.SYNC_MINIMUM) { - stream.write(new VarInt(quorumState.getPendingBlocks().size() + otherPendingBlocks.size()).encode()); + stream.write(new VarInt((long)quorumState.getPendingBlocks().size() + otherPendingBlocks.size()).encode()); ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); log.info("saving {} blocks to catch up mnList", otherPendingBlocks.size()); for (StoredBlock block : otherPendingBlocks) { @@ -438,11 +438,6 @@ public String toString() { quorumRotationState.getPendingBlocks().size() + (height != -1? ("-->height: "+height+")") : "" ) +"}"; } - @Deprecated - public long getSpork15Value() { - return 0; - } - public boolean isQuorumRotationEnabled(LLMQParameters.LLMQType type) { if (blockChain == null) { return formatVersion == QUORUM_ROTATION_FORMAT_VERSION; @@ -514,7 +509,7 @@ public SimplifiedQuorumList getQuorumListForBlock(Sha256Hash blockHash, LLMQPara } } - public ArrayList getAllQuorumMembers(LLMQParameters.LLMQType llmqType, Sha256Hash blockHash) + public List getAllQuorumMembers(LLMQParameters.LLMQType llmqType, Sha256Hash blockHash) { if (isQuorumRotationEnabled(llmqType)) { return quorumRotationState.getAllQuorumMembers(llmqType, blockHash); diff --git a/core/src/main/java/org/bitcoinj/governance/GovernanceManager.java b/core/src/main/java/org/bitcoinj/governance/GovernanceManager.java index 159cbfd35e..35a8f4ed63 100644 --- a/core/src/main/java/org/bitcoinj/governance/GovernanceManager.java +++ b/core/src/main/java/org/bitcoinj/governance/GovernanceManager.java @@ -862,7 +862,7 @@ public void checkAndRemove() { public void updateCachesAndClean() { log.info("gobject--CGovernanceManager::UpdateCachesAndClean"); - ArrayList vecDirtyHashes = context.masternodeMetaDataManager.getAndClearDirtyGovernanceObjectHashes(); + List vecDirtyHashes = context.masternodeMetaDataManager.getAndClearDirtyGovernanceObjectHashes(); lock.lock(); try { From 7b25bea0ce0853d6689a4376aad460272a5de62f Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 10:59:15 -0700 Subject: [PATCH 19/31] refactor: make atomic fields final in QuorumUpdateRequest Signed-off-by: HashEngineering --- .../main/java/org/bitcoinj/evolution/QuorumUpdateRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/QuorumUpdateRequest.java b/core/src/main/java/org/bitcoinj/evolution/QuorumUpdateRequest.java index 76b902fd73..b1ac21c786 100644 --- a/core/src/main/java/org/bitcoinj/evolution/QuorumUpdateRequest.java +++ b/core/src/main/java/org/bitcoinj/evolution/QuorumUpdateRequest.java @@ -31,8 +31,8 @@ public class QuorumUpdateRequest { T request; long time; - private AtomicBoolean fulfilled = new AtomicBoolean(false); - private AtomicBoolean received = new AtomicBoolean(false); + private final AtomicBoolean fulfilled = new AtomicBoolean(false); + private final AtomicBoolean received = new AtomicBoolean(false); private PeerAddress peerAddress; public QuorumUpdateRequest(T request) { From 887f08ac6c61d132c72f54fbd6ce5876b663e0ca Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:01:04 -0700 Subject: [PATCH 20/31] refactor: simplify some code in QuorumState Signed-off-by: HashEngineering --- .../org/bitcoinj/evolution/QuorumState.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/QuorumState.java b/core/src/main/java/org/bitcoinj/evolution/QuorumState.java index 645e344f5e..3d4649dbd2 100644 --- a/core/src/main/java/org/bitcoinj/evolution/QuorumState.java +++ b/core/src/main/java/org/bitcoinj/evolution/QuorumState.java @@ -32,7 +32,6 @@ import org.bitcoinj.quorums.SigningManager; import org.bitcoinj.quorums.SimplifiedQuorumList; import org.bitcoinj.store.BlockStoreException; -import org.bitcoinj.utils.Threading; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -253,7 +252,6 @@ public SimplifiedQuorumList getQuorumListAtTip() { @Override public void processDiff(@Nullable Peer peer, SimplifiedMasternodeListDiff mnlistdiff, DualBlockChain blockChain, boolean isLoadingBootStrap, PeerGroup.SyncStage syncStage) throws VerificationException { - StoredBlock block = null; long newHeight = ((CoinbaseTx) mnlistdiff.coinBaseTx.getExtraPayloadObject()).getHeight(); if (peer != null) peer.queueMasternodeListDownloadedListeners(MasternodeListDownloadedListener.Stage.Received, mnlistdiff); Stopwatch watch = Stopwatch.createStarted(); @@ -266,16 +264,13 @@ public void processDiff(@Nullable Peer peer, SimplifiedMasternodeListDiff mnlist mnlistdiff.dump(mnList.getHeight(), newHeight); lastRequest.setReceived(); - // TODO: remove this -// if (lock.isLocked() && !initChainTipSyncComplete()) { -// Threading.dump(); -// } + lock.lock(); try { log.info("lock acquired when processing mnlistdiff"); applyDiff(peer, blockChain, mnlistdiff, isLoadingBootStrap); - log.info(this.toString()); + log.info("{}", this); lastRequest.setFulfilled(); unCache(); clearFailedAttempts(); @@ -288,8 +283,8 @@ public void processDiff(@Nullable Peer peer, SimplifiedMasternodeListDiff mnlist if (peer != null && isSyncingHeadersFirst) peer.queueMasternodeListDownloadedListeners(MasternodeListDownloadedListener.Stage.Finished, mnlistdiff); watch.stop(); - log.info("processing mnlistdiff times : Total: " + watch + "mnList: " + watchMNList + " quorums" + watchQuorums + "mnlistdiff" + mnlistdiff); - log.info(toString()); + log.info("processing mnlistdiff times : Total: {} mnList: {} quorums: {} mnlistdiff: {}", watch, watchMNList, watchQuorums, mnlistdiff); + log.info("{}", this); finishDiff(isLoadingBootStrap); } catch(MasternodeListDiffException x) { // we already have this mnlistdiff or doesn't match our current tipBlockHash @@ -326,7 +321,7 @@ public void processDiff(@Nullable Peer peer, SimplifiedMasternodeListDiff mnlist finishDiff(isLoadingBootStrap); } catch(VerificationException x) { //request this block again and close this peer - log.info("verification error: close this peer" + x.getMessage()); + log.info("verification error: close this peer: {}", x.getMessage()); incrementFailedAttempts(); finishDiff(isLoadingBootStrap); throw x; @@ -336,7 +331,7 @@ public void processDiff(@Nullable Peer peer, SimplifiedMasternodeListDiff mnlist finishDiff(isLoadingBootStrap); throw new VerificationException("verification error: " + x.getMessage()); } catch(BlockStoreException x) { - log.info(x.getMessage()); + log.info("{}", x.getMessage()); incrementFailedAttempts(); finishDiff(isLoadingBootStrap); throw new ProtocolException(x); From 62794f8fc9eccab778d3bdba2fcfcac94e17a94d Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:01:59 -0700 Subject: [PATCH 21/31] chore: remove commented code and deprecated methods in QuorumRotationState Signed-off-by: HashEngineering --- .../evolution/QuorumRotationState.java | 214 ++++++------------ 1 file changed, 72 insertions(+), 142 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java b/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java index 834a2f3df4..03f10325d9 100644 --- a/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java +++ b/core/src/main/java/org/bitcoinj/evolution/QuorumRotationState.java @@ -90,11 +90,11 @@ public class QuorumRotationState extends AbstractQuorumState>> mapQuorumMembers = new HashMap<>(); + EnumMap>> mapQuorumMembers = new EnumMap<>(LLMQParameters.LLMQType.class); private ReentrantLock indexedMemberLock = Threading.lock("indexedMemberLock"); @GuardedBy("indexedMemberLock") - HashMap, ArrayList>> mapIndexedQuorumMembers = new HashMap<>(); + EnumMap, ArrayList>> mapIndexedQuorumMembers = new EnumMap<>(LLMQParameters.LLMQType.class); public QuorumRotationState(Context context) { super(context); @@ -598,7 +598,7 @@ private ArrayList> computeQuorumMembersByQuarterRotation(L builder.append(m.getProTxHash().toString().substring(0, 4)).append("|"); } builder.append(" ]"); - log.info("QuarterComposition h[{}] i[{}]:{}", quorumBaseBlock.getHeight(), i, builder.toString()); + log.info("QuarterComposition h[{}] i[{}]:{}", quorumBaseBlock.getHeight(), i, builder); } } @@ -662,7 +662,7 @@ private void printList(List list, String name) { builder.append(" ").append(mn.getProTxHash()).append("\n"); } - log.info(builder.toString()); + log.info("{}", builder); } private static ArrayList> createNewQuarterQuorumMembers(LLMQParameters llmqParameters) { @@ -792,72 +792,6 @@ private ArrayList> buildNewQuorumQuarte return quarterQuorumMembers; } - @Deprecated - private QuorumSnapshot buildQuorumSnapshot(LLMQParameters llmqParameters, SimplifiedMasternodeList allMns, SimplifiedMasternodeList mnUsedAtH, ArrayList sortedCombinedMns, ArrayList skipList) { - QuorumSnapshot quorumSnapshot = new QuorumSnapshot(allMns.getAllMNsCount()); - - AtomicInteger index = new AtomicInteger(); - allMns.forEachMN(true, mn -> { - if (mnUsedAtH.containsMN(mn.getProTxHash())) { - quorumSnapshot.setActiveQuorumMember(index.get(), true); - } - index.getAndIncrement(); - }); - - // TODO: do we need this? - // buildQuorumSnapshotSkipList(llmqParameters, mnUsedAtH, sortedCombinedMns, quorumSnapshot); - if (skipList.isEmpty()) { - quorumSnapshot.setSkipListMode(SnapshotSkipMode.MODE_NO_SKIPPING); - quorumSnapshot.getSkipList().clear(); - } else { - quorumSnapshot.setSkipListMode(SnapshotSkipMode.MODE_SKIPPING_ENTRIES); - quorumSnapshot.setSkipList(skipList); - } - return quorumSnapshot; - } - - - @Deprecated // this method is not used, may not be required" - void buildQuorumSnapshotSkipList(LLMQParameters llmqParameters, SimplifiedMasternodeList mnUsedAtH, ArrayList sortedCombinedMns, QuorumSnapshot quorumSnapshot) { - if (mnUsedAtH.getAllMNsCount() == 0) { - quorumSnapshot.setSkipListMode(SnapshotSkipMode.MODE_NO_SKIPPING); - quorumSnapshot.getSkipList().clear(); - } else if (mnUsedAtH.getAllMNsCount() < sortedCombinedMns.size() / 2) { - quorumSnapshot.setSkipListMode(SnapshotSkipMode.MODE_SKIPPING_ENTRIES); - - int first_entry_index = 0; - int index = 0; - - for (Masternode mn : sortedCombinedMns) { - if (mnUsedAtH.containsMN(sortedCombinedMns.get(index).getProTxHash())) { - if (first_entry_index == 0) { - first_entry_index = index; - quorumSnapshot.getSkipList().add(index); - } else - quorumSnapshot.getSkipList().add(index - first_entry_index); - } - index++; - } - } else { - //Mode 2: Non-Skipping entries - quorumSnapshot.setSkipListMode(SnapshotSkipMode.MODE_NO_SKIPPING_ENTRIES); - - int first_entry_index = 0; - int index = 0; - - for (Masternode mn : sortedCombinedMns) { - if (!mnUsedAtH.containsMN(sortedCombinedMns.get(index).getProTxHash())) { - if (first_entry_index == 0) { - first_entry_index = index; - quorumSnapshot.getSkipList().add(index); - } else - quorumSnapshot.getSkipList().add(index - first_entry_index); - } - index++; - } - } - } - PreviousQuorumQuarters getPreviousQuorumQuarterMembers(LLMQParameters llmqParameters, StoredBlock blockHMinusC, StoredBlock blockHMinus2C, StoredBlock blockHMinus3C, int height) { PreviousQuorumQuarters quarters = new PreviousQuorumQuarters(); @@ -888,87 +822,83 @@ PreviousQuorumQuarters getPreviousQuorumQuarterMembers(LLMQParameters llmqParame ArrayList> getQuorumQuarterMembersBySnapshot(LLMQParameters llmqParameters, StoredBlock cycleQuorumBaseBlock, QuorumSnapshot snapshot, int height) { checkArgument(llmqParameters.useRotation()); checkArgument(cycleQuorumBaseBlock.getHeight() % llmqParameters.getDkgInterval() == 0); - //try { - int numQuorums = llmqParameters.getSigningActiveQuorumCount(); - int quorumSize = llmqParameters.getSize(); - int quarterSize = quorumSize / 4; - - ArrayList> quarterQuorumMembers = Lists.newArrayListWithCapacity(numQuorums); - for (int i = 0; i < numQuorums; ++i) { - quarterQuorumMembers.add(Lists.newArrayList()); - } - Sha256Hash modifier = getHashModifier(llmqParameters, cycleQuorumBaseBlock); + int numQuorums = llmqParameters.getSigningActiveQuorumCount(); + int quorumSize = llmqParameters.getSize(); + int quarterSize = quorumSize / 4; - Pair result = getMNUsageBySnapshot(llmqParameters, cycleQuorumBaseBlock, snapshot, height); - SimplifiedMasternodeList mnsUsedAtH = result.getFirst(); - SimplifiedMasternodeList mnsNotUsedAtH = result.getSecond(); - ArrayList sortedMnsUsedAtH = mnsUsedAtH.calculateQuorum(mnsUsedAtH.getAllMNsCount(), modifier); - ArrayList sortedMnsNotUsedAtH = mnsNotUsedAtH.calculateQuorum(mnsNotUsedAtH.getAllMNsCount(), modifier); - ArrayList sortedCombinedMnsList = new ArrayList<>(sortedMnsNotUsedAtH); + ArrayList> quarterQuorumMembers = Lists.newArrayListWithCapacity(numQuorums); + for (int i = 0; i < numQuorums; ++i) { + quarterQuorumMembers.add(Lists.newArrayList()); + } - for (Masternode m1 : sortedMnsUsedAtH) { - for (Masternode m2 : sortedMnsNotUsedAtH) { - if (m1.equals(m2)) { - log.info("{} is in both lists", m1); - } + Sha256Hash modifier = getHashModifier(llmqParameters, cycleQuorumBaseBlock); + + Pair result = getMNUsageBySnapshot(llmqParameters, cycleQuorumBaseBlock, snapshot, height); + SimplifiedMasternodeList mnsUsedAtH = result.getFirst(); + SimplifiedMasternodeList mnsNotUsedAtH = result.getSecond(); + ArrayList sortedMnsUsedAtH = mnsUsedAtH.calculateQuorum(mnsUsedAtH.getAllMNsCount(), modifier); + ArrayList sortedMnsNotUsedAtH = mnsNotUsedAtH.calculateQuorum(mnsNotUsedAtH.getAllMNsCount(), modifier); + ArrayList sortedCombinedMnsList = new ArrayList<>(sortedMnsNotUsedAtH); + + for (Masternode m1 : sortedMnsUsedAtH) { + for (Masternode m2 : sortedMnsNotUsedAtH) { + if (m1.equals(m2)) { + log.info("{} is in both lists", m1); } } - sortedCombinedMnsList.addAll(sortedMnsUsedAtH); - - //Mode 0: No skipping - if (snapshot.getSkipListMode() == SnapshotSkipMode.MODE_NO_SKIPPING.getValue()) { - Iterator itm = sortedCombinedMnsList.iterator(); - for (int i = 0; i < llmqParameters.getSigningActiveQuorumCount(); ++i) { - //Iterate over the first quarterSize elements - while (quarterQuorumMembers.get(i).size() < quarterSize) { - Masternode m = itm.next(); - quarterQuorumMembers.get(i).add((SimplifiedMasternodeListEntry) m); - if (!itm.hasNext()) { - itm = sortedCombinedMnsList.iterator(); - } + } + sortedCombinedMnsList.addAll(sortedMnsUsedAtH); + + //Mode 0: No skipping + if (snapshot.getSkipListMode() == SnapshotSkipMode.MODE_NO_SKIPPING.getValue()) { + Iterator itm = sortedCombinedMnsList.iterator(); + for (int i = 0; i < llmqParameters.getSigningActiveQuorumCount(); ++i) { + //Iterate over the first quarterSize elements + while (quarterQuorumMembers.get(i).size() < quarterSize) { + Masternode m = itm.next(); + quarterQuorumMembers.get(i).add((SimplifiedMasternodeListEntry) m); + if (!itm.hasNext()) { + itm = sortedCombinedMnsList.iterator(); } } } - //Mode 1: List holds entries to be skipped - else if (snapshot.getSkipListMode() == SnapshotSkipMode.MODE_SKIPPING_ENTRIES.getValue()) { - int first_entry_index = 0; - ArrayList processedSkipList = Lists.newArrayList(); - for (int s : snapshot.getSkipList()) { - if (first_entry_index == 0) { - first_entry_index = s; - processedSkipList.add(s); - } else { - processedSkipList.add(first_entry_index + s); - } + } + //Mode 1: List holds entries to be skipped + else if (snapshot.getSkipListMode() == SnapshotSkipMode.MODE_SKIPPING_ENTRIES.getValue()) { + int firstEntryIndex = 0; + ArrayList processedSkipList = Lists.newArrayList(); + for (int s : snapshot.getSkipList()) { + if (firstEntryIndex == 0) { + firstEntryIndex = s; + processedSkipList.add(s); + } else { + processedSkipList.add(firstEntryIndex + s); } + } - int idx = 0; - int idxk = 0; - for (int i = 0; i < llmqParameters.getSigningActiveQuorumCount(); ++i) { - //Iterate over the first quarterSize elements - while (quarterQuorumMembers.get(i).size() < quarterSize) { - if (idxk != processedSkipList.size() && idx == processedSkipList.get(idxk)) - idxk++; - else - quarterQuorumMembers.get(i).add((SimplifiedMasternodeListEntry) sortedCombinedMnsList.get(idx)); - idx++; - if (idx == sortedCombinedMnsList.size()) - idx = 0; - } + int idx = 0; + int idxk = 0; + for (int i = 0; i < llmqParameters.getSigningActiveQuorumCount(); ++i) { + //Iterate over the first quarterSize elements + while (quarterQuorumMembers.get(i).size() < quarterSize) { + if (idxk != processedSkipList.size() && idx == processedSkipList.get(idxk)) + idxk++; + else + quarterQuorumMembers.get(i).add((SimplifiedMasternodeListEntry) sortedCombinedMnsList.get(idx)); + idx++; + if (idx == sortedCombinedMnsList.size()) + idx = 0; } } - //Mode 2: List holds entries to be kept - else if (snapshot.getSkipListMode() == SnapshotSkipMode.MODE_NO_SKIPPING_ENTRIES.getValue()) { - // Mode 2 will be written later - } - //Mode 3: Every node was skipped. Returning empty quarterQuorumMembers + } + //Mode 2: List holds entries to be kept + else if (snapshot.getSkipListMode() == SnapshotSkipMode.MODE_NO_SKIPPING_ENTRIES.getValue()) { + // Mode 2 will be written later + } + //Mode 3: Every node was skipped. Returning empty quarterQuorumMembers - return quarterQuorumMembers; - // } - /*catch (BlockStoreException x) { - throw new RuntimeException(x); - }*/ + return quarterQuorumMembers; } Pair getMNUsageBySnapshot(LLMQParameters llmqParameters, @@ -1001,13 +931,13 @@ Pair getMNUsageBySnapshot(LL return new Pair<>(usedMNs, nonUsedMNs); } - void initIndexedQuorumsCache(HashMap, ArrayList>> cache) { + void initIndexedQuorumsCache(EnumMap, ArrayList>> cache) { for (Map.Entry llmq : params.getLlmqs().entrySet()) { cache.put(llmq.getKey(), new HashMap<>(llmq.getValue().getSigningActiveQuorumCount() + 1)); } } - void initQuorumsCache(HashMap>> cache) { + void initQuorumsCache(EnumMap>> cache) { for (Map.Entry llmq : params.getLlmqs().entrySet()) { cache.put(llmq.getKey(), new HashMap<>(llmq.getValue().getSigningActiveQuorumCount() + 1)); } @@ -1265,7 +1195,7 @@ public void processDiff(@Nullable Peer peer, QuorumRotationInfo quorumRotationIn peer.queueMasternodeListDownloadedListeners(MasternodeListDownloadedListener.Stage.Finished, quorumRotationInfo.getMnListDiffTip()); watch.stop(); log.info("processing qrinfo: Total: {} mnlistdiff: {}", watch, quorumRotationInfo.getMnListDiffTip()); - log.info(toString()); + log.info("{}", this); } catch (MasternodeListDiffException x) { //we already have this qrinfo or doesn't match our current tipBlockHash if (mnListAtH.getBlockHash().equals(quorumRotationInfo.getMnListDiffAtH().getBlockHash())) { @@ -1296,7 +1226,7 @@ public void processDiff(@Nullable Peer peer, QuorumRotationInfo quorumRotationIn finishDiff(isLoadingBootStrap); } catch (VerificationException x) { //request this block again and close this peer - log.info("verification error: close this peer" + x.getMessage()); + log.info("verification error: close this peer: {}", x.getMessage()); failedAttempts++; finishDiff(isLoadingBootStrap); throw x; From 27ab88bf807f5e6f8de01a0fe882a70f56260753 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:02:59 -0700 Subject: [PATCH 22/31] refactor: remove deprecated methods and constants and simplify SimplifiedMasternodeListDiff Signed-off-by: HashEngineering --- .../bitcoinj/evolution/SimplifiedMasternodeListDiff.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiff.java b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiff.java index 0de4dbc89a..59a7dae5f2 100644 --- a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiff.java +++ b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListDiff.java @@ -7,8 +7,6 @@ import org.bitcoinj.crypto.BLSSignature; import org.bitcoinj.quorums.FinalCommitment; import org.bitcoinj.utils.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.OutputStream; @@ -16,12 +14,7 @@ public class SimplifiedMasternodeListDiff extends AbstractDiffMessage { - private static final Logger log = LoggerFactory.getLogger(SimplifiedMasternodeListDiff.class); private static final String SHORT_NAME = "mnlistdiff"; - @Deprecated - public static final short LEGACY_BLS_VERSION = 1; - @Deprecated - public static final short BASIC_BLS_VERSION = 2; public static final short CURRENT_VERSION = 1; private short version; private Sha256Hash prevBlockHash; @@ -267,7 +260,7 @@ public boolean hasBasicSchemeKeys() { return false; } - public HashMap> getQuorumsCLSigs() { + public Map> getQuorumsCLSigs() { return quorumsCLSigs; } } From 6be4827f6f6f622f1bfa0d3f1224fcc39221ae87 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:04:01 -0700 Subject: [PATCH 23/31] refactor: remove deprecated methods and constants and simplify SimplifiedMasternodeList Signed-off-by: HashEngineering --- .../evolution/SimplifiedMasternodeList.java | 205 +++++++----------- 1 file changed, 82 insertions(+), 123 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java index 6f51e8f762..fa07508524 100644 --- a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java +++ b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeList.java @@ -1,3 +1,19 @@ +/* + * Copyright 2018 Dash Core Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.bitcoinj.evolution; import com.google.common.base.Preconditions; @@ -16,14 +32,15 @@ import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Stream; import static java.lang.Math.min; -import static org.bitcoinj.core.Sha256Hash.hashTwice; +import static org.bitcoinj.evolution.SimplifiedMasternodeListDiff.CURRENT_VERSION; public class SimplifiedMasternodeList extends Message { - private final Logger log = LoggerFactory.getLogger(SimplifiedMasternodeList.class); - private ReentrantLock lock = Threading.lock("SimplifiedMasternodeList"); + private static final Logger log = LoggerFactory.getLogger(SimplifiedMasternodeList.class); + private final ReentrantLock lock = Threading.lock("SimplifiedMasternodeList"); private short version; private Sha256Hash blockHash; @@ -31,8 +48,6 @@ public class SimplifiedMasternodeList extends Message { private StoredBlock storedBlock; private boolean storedBlockMatchesRequest; HashMap mnMap; - @Deprecated - HashMap> mnUniquePropertyMap = new HashMap<>(); private CoinbaseTx coinbaseTxPayload; @@ -40,7 +55,7 @@ public SimplifiedMasternodeList(NetworkParameters params) { super(params); blockHash = params.getGenesisBlock().getHash(); height = -1; - mnMap = new HashMap(5000); + mnMap = new HashMap<>(5000); storedBlock = new StoredBlock(params.getGenesisBlock(), BigInteger.ZERO, 0); initProtocolVersion(); } @@ -58,7 +73,7 @@ private void initProtocolVersion() { this.version = version; this.blockHash = other.blockHash; this.height = other.height; - mnMap = new HashMap(other.mnMap); + mnMap = new HashMap<>(other.mnMap); this.storedBlock = other.storedBlock; initProtocolVersion(); } @@ -66,10 +81,10 @@ private void initProtocolVersion() { SimplifiedMasternodeList(NetworkParameters params, ArrayList entries, int protocolVersion) { super(params); this.protocolVersion = protocolVersion; - this.version = SimplifiedMasternodeListDiff.CURRENT_VERSION; + this.version = CURRENT_VERSION; this.blockHash = params.getGenesisBlock().getHash(); this.height = -1; - mnMap = new HashMap(entries.size()); + mnMap = new HashMap<>(entries.size()); for(SimplifiedMasternodeListEntry entry : entries) addMN(entry); storedBlock = new StoredBlock(params.getGenesisBlock(), BigInteger.ZERO, 0); @@ -81,7 +96,7 @@ protected void parse() throws ProtocolException { if (protocolVersion >= NetworkParameters.ProtocolVersion.BLS_SCHEME.getBitcoinProtocolVersion()) { version = (short) readUint16(); } else { - version = SimplifiedMasternodeListDiff.LEGACY_BLS_VERSION; + version = CURRENT_VERSION; } blockHash = readHash(); height = (int)readUint32(); @@ -202,56 +217,13 @@ public SimplifiedMasternodeListEntry getMN(Sha256Hash proTxHash) { lock.lock(); try { - SimplifiedMasternodeListEntry p = mnMap.get(proTxHash); - if (p == null) { - return null; - } - return p; - } finally { - lock.unlock(); - } - } - - - @Deprecated - void addUniqueProperty(SimplifiedMasternodeListEntry dmn, T value) - { - lock.lock(); - try { - Sha256Hash hash = value.getHash(); - int i = 1; - Pair oldEntry = mnUniquePropertyMap.get(hash); - //assert(oldEntry == null || oldEntry.getFirst().equals(dmn.proRegTxHash)); - if (oldEntry != null) - i = oldEntry.getSecond() + 1; - Pair newEntry = new Pair<>(dmn.proRegTxHash, i); - - mnUniquePropertyMap.put(hash, newEntry); - } finally { - lock.unlock(); - } - } - @Deprecated - - void deleteUniqueProperty(SimplifiedMasternodeListEntry dmn, T oldValue) - { - lock.lock(); - try { - Sha256Hash oldHash = oldValue.getHash(); - Pair p = mnUniquePropertyMap.get(oldHash); - //assert(p != null && p.getFirst() == dmn.proRegTxHash); - if (p.getSecond() == 1) { - mnUniquePropertyMap.remove(oldHash); - } else { - mnUniquePropertyMap.put(oldHash, new Pair<>(dmn.proRegTxHash, p.getSecond() - 1)); - } + return mnMap.get(proTxHash); } finally { lock.unlock(); } } - public - boolean verify(Transaction coinbaseTx, SimplifiedMasternodeListDiff mnlistdiff, SimplifiedMasternodeList prevList) throws MasternodeListDiffException { + public boolean verify(Transaction coinbaseTx, SimplifiedMasternodeListDiff mnlistdiff, SimplifiedMasternodeList prevList) throws MasternodeListDiffException { //check mnListMerkleRoot if(!(coinbaseTx.getExtraPayloadObject() instanceof CoinbaseTx)) @@ -260,30 +232,25 @@ boolean verify(Transaction coinbaseTx, SimplifiedMasternodeListDiff mnlistdiff, CoinbaseTx cbtx = (CoinbaseTx)coinbaseTx.getExtraPayloadObject(); if(mnlistdiff.mnList.isEmpty() && mnlistdiff.deletedMNs.isEmpty() && - prevList != null && prevList.coinbaseTxPayload != null) { - if (cbtx.getMerkleRootMasternodeList().equals(prevList.coinbaseTxPayload.getMerkleRootMasternodeList())) + prevList != null && prevList.coinbaseTxPayload != null && + cbtx.getMerkleRootMasternodeList().equals(prevList.coinbaseTxPayload.getMerkleRootMasternodeList())) return true; - } + lock.lock(); try { - ArrayList proTxHashes = new ArrayList(mnMap.size()); + ArrayList proTxHashes = new ArrayList<>(mnMap.size()); for (Map.Entry entry : mnMap.entrySet()) { proTxHashes.add(entry.getValue().proRegTxHash); } - Collections.sort(proTxHashes, new Comparator() { - @Override - public int compare(Sha256Hash o1, Sha256Hash o2) { - return o1.compareTo(o2); - } - }); + proTxHashes.sort(Comparator.naturalOrder()); - ArrayList smnlHashes = new ArrayList(mnMap.size()); + ArrayList smnlHashes = new ArrayList<>(mnMap.size()); for (Sha256Hash hash : proTxHashes) { smnlHashes.add(mnMap.get(hash).getHash()); } - if (smnlHashes.size() == 0) + if (smnlHashes.isEmpty()) return true; if (!cbtx.getMerkleRootMasternodeList().equals(MerkleRoot.calculateMerkleRoot(smnlHashes))) @@ -297,18 +264,14 @@ public int compare(Sha256Hash o1, Sha256Hash o2) { public Sha256Hash calculateMerkleRoot() { lock.lock(); try { - ArrayList proTxHashes = new ArrayList(mnMap.size()); + ArrayList proTxHashes = new ArrayList<>(mnMap.size()); for (Map.Entry entry : mnMap.entrySet()) { proTxHashes.add(entry.getValue().proRegTxHash); } - Collections.sort(proTxHashes, new Comparator() { - @Override - public int compare(Sha256Hash o1, Sha256Hash o2) { - return o1.compareTo(o2); - } - }); - ArrayList smnlHashes = new ArrayList(mnMap.size()); + proTxHashes.sort(Comparator.naturalOrder()); + + ArrayList smnlHashes = new ArrayList<>(mnMap.size()); for (Sha256Hash hash : proTxHashes) { for (Map.Entry entry : mnMap.entrySet()) if (entry.getValue().proRegTxHash.equals(hash)) @@ -364,12 +327,15 @@ public void forEachMN(boolean onlyValid, ForeachMNCallback callback) { public void forEachMN(boolean onlyValid, ForeachMNCallback callback, Comparator comparator) { lock.lock(); try { - // TODO: need to sort by comparator - for (Map.Entry entry : mnMap.entrySet()) { - if (!onlyValid || isMNValid(entry.getValue())) { - callback.processMN(entry.getValue()); + Collection entries = mnMap.values(); + Stream sortedEntries = entries.stream().sorted(comparator); + + sortedEntries.forEach(entry -> { + if (onlyValid && !entry.isValid()) { + return; } - } + callback.processMN (entry); + }); } finally { lock.unlock(); } @@ -402,48 +368,41 @@ public boolean isMNValid(SimplifiedMasternodeListEntry entry) { ArrayList> calculateScores(final Sha256Hash modifier, boolean hpmnOnly) { - final ArrayList> scores = new ArrayList>(getAllMNsCount()); - - forEachMN(true, new ForeachMNCallback() { - @Override - public void processMN(SimplifiedMasternodeListEntry mn) { - if(mn.getConfirmedHash().isZero()) { - // we only take confirmed MNs into account to avoid hash grinding on the ProRegTxHash to sneak MNs into a - // future quorums - return; - } - if (hpmnOnly) { - if (mn.type != MasternodeType.HIGHPERFORMANCE.index) - return; - } + final ArrayList> scores = new ArrayList<>(getAllMNsCount()); - // calculate sha256(sha256(proTxHash, confirmedHash), modifier) per MN - // Please note that this is not a double-sha256 but a single-sha256 - // The first part is already precalculated (confirmedHashWithProRegTxHash) - // TODO When https://github.com/bitcoin/bitcoin/pull/13191 gets backported, implement something that is similar but for single-sha256 - try { - UnsafeByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(64); - bos.write(mn.getConfirmedHashWithProRegTxHash().getReversedBytes()); - bos.write(modifier.getReversedBytes()); - scores.add(new Pair<>(Sha256Hash.of(bos.toByteArray()), mn)); //we don't reverse this, it is not for a wire message - } catch (IOException x) { - throw new RuntimeException(x); - } + forEachMN(true, mn -> { + if(mn.getConfirmedHash().isZero()) { + // we only take confirmed MNs into account to avoid hash grinding on the ProRegTxHash to sneak MNs into a + // future quorums + return; + } + if (hpmnOnly && mn.type != MasternodeType.HIGHPERFORMANCE.index) + return; + + + // calculate sha256(sha256(proTxHash, confirmedHash), modifier) per MN + // Please note that this is not a double-sha256 but a single-sha256 + // The first part is already precalculated (confirmedHashWithProRegTxHash) + // TODO When https://github.com/bitcoin/bitcoin/pull/13191 gets backported, implement something that is similar but for single-sha256 + try { + UnsafeByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(64); + bos.write(mn.getConfirmedHashWithProRegTxHash().getReversedBytes()); + bos.write(modifier.getReversedBytes()); + scores.add(new Pair<>(Sha256Hash.of(bos.toByteArray()), mn)); //we don't reverse this, it is not for a wire message + } catch (IOException x) { + throw new RuntimeException(x); } }); return scores; } - static class CompareScoreMN implements Comparator + static class CompareScoreMN implements Comparator> { - public int compare(Object t1, Object t2) { - Pair p1 = (Pair)t1; - Pair p2 = (Pair)t2; - - if(p1.getFirst().compareTo(p2.getFirst()) < 0) + public int compare(Pair a, Pair b) { + if(a.getFirst().compareTo(b.getFirst()) < 0) return -1; - if(p1.getFirst().equals(p2.getFirst())) + if(a.getFirst().equals(b.getFirst())) return 0; else return 1; } @@ -502,12 +461,12 @@ ArrayList calculateQuorum(int maxSize, Sha256Hash modifier, boolean ArrayList> scores = calculateScores(modifier, hpmnOnly); // sort is descending order - Collections.sort(scores, Collections.reverseOrder(new CompareScoreMN())); + scores.sort(Collections.reverseOrder(new CompareScoreMN())); // take top maxSize entries and return it int size = min(scores.size(), maxSize); - ArrayList result = new ArrayList(size); - if (scores.size() > 0) { + ArrayList result = new ArrayList<>(size); + if (!scores.isEmpty()) { for (int i = 0; i < size; i++) { result.add(scores.get(i).getSecond()); } @@ -532,23 +491,23 @@ public int countEnabled() { return size(); } - public Collection getSortedList(Comparator comparator) { + public Collection getSortedList(Comparator comparator) { ArrayList list = Lists.newArrayList(); forEachMN(true, list::add); list.sort(comparator); return list; } - static class CompareMNProTxWithModifier implements Comparator + static class CompareMNProTxWithModifier implements Comparator { - private Sha256Hash dkgBlockHash; + private final Sha256Hash dkgBlockHash; public CompareMNProTxWithModifier(Sha256Hash dkgBlockHash) { this.dkgBlockHash = dkgBlockHash; } - public int compare(Object t1, Object t2) { - Sha256Hash p1 = LLMQUtils.buildProTxDkgBlockHash(((SimplifiedMasternodeListEntry)t1).proRegTxHash, dkgBlockHash); - Sha256Hash p2 = LLMQUtils.buildProTxDkgBlockHash(((SimplifiedMasternodeListEntry)t2).proRegTxHash, dkgBlockHash); + public int compare(Masternode t1, Masternode t2) { + Sha256Hash p1 = LLMQUtils.buildProTxDkgBlockHash(t1.proRegTxHash, dkgBlockHash); + Sha256Hash p2 = LLMQUtils.buildProTxDkgBlockHash(t2.proRegTxHash, dkgBlockHash); if(p1.compareTo(p2) < 0) return -1; @@ -559,6 +518,6 @@ public int compare(Object t1, Object t2) { } public Collection getListSortedByModifier(Block dkgBlockHash) { - return getSortedList(new CompareMNProTxWithModifier<>(dkgBlockHash.getHash())); + return getSortedList(new CompareMNProTxWithModifier(dkgBlockHash.getHash())); } } From 711ecb1d73fdee64cd65450cc3c3dc4a955d6685 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:04:54 -0700 Subject: [PATCH 24/31] refactor: make field final in SimplifiedMasternodeListEntry Signed-off-by: HashEngineering --- .../org/bitcoinj/evolution/SimplifiedMasternodeListEntry.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListEntry.java b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListEntry.java index e4ff0d5b26..4a79d4796d 100644 --- a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListEntry.java +++ b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListEntry.java @@ -20,7 +20,7 @@ public class SimplifiedMasternodeListEntry extends Masternode { int type; int platformHTTPPort; KeyId platformNodeId; - static int MESSAGE_SIZE = 151; + static final int MESSAGE_SIZE = 151; //In Memory Sha256Hash confirmedHashWithProRegTxHash; @@ -137,7 +137,6 @@ private void serializeWithoutVersionToStream(OutputStream stream) throws IOExcep Utils.uint16ToByteStreamLE(type, stream); if (type == MasternodeType.HIGHPERFORMANCE.index) { Utils.uint16ToByteStreamLE(platformHTTPPort, stream); - // TODO: not in beta 5 platformNodeId.bitcoinSerialize(stream); } } From 8d498e6400b3ba433340b8747396e93decc8ea4c Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:05:59 -0700 Subject: [PATCH 25/31] refactor: use lamdas and simplify SimplifiedQuorumList Signed-off-by: HashEngineering --- .../quorums/SimplifiedQuorumList.java | 88 +++++++++---------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java b/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java index 4bd62264bd..c0666d12fa 100644 --- a/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java +++ b/core/src/main/java/org/bitcoinj/quorums/SimplifiedQuorumList.java @@ -1,3 +1,19 @@ +/* + * Copyright 2019 Dash Core Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.bitcoinj.quorums; import org.bitcoinj.core.*; @@ -5,6 +21,7 @@ import org.bitcoinj.evolution.*; import org.bitcoinj.evolution.Masternode; import org.bitcoinj.store.BlockStoreException; +import org.bitcoinj.utils.MerkleRoot; import org.bitcoinj.utils.Pair; import org.bitcoinj.utils.Threading; import org.slf4j.Logger; @@ -15,8 +32,6 @@ import java.util.*; import java.util.concurrent.locks.ReentrantLock; -import static org.bitcoinj.core.Sha256Hash.hashTwice; - public class SimplifiedQuorumList extends Message { // critical section to protect the inner data structures @@ -35,8 +50,8 @@ public SimplifiedQuorumList(NetworkParameters params) { super(params); blockHash = params.getGenesisBlock().getHash(); height = -1; - minableCommitmentsByQuorum = new HashMap, Sha256Hash>(10); - minableCommitments = new LinkedHashMap(10); + minableCommitmentsByQuorum = new HashMap<>(10); + minableCommitments = new LinkedHashMap<>(10); isFirstQuorumCheck = true; } @@ -48,8 +63,8 @@ public SimplifiedQuorumList(SimplifiedQuorumList other) { super(other.params); this.blockHash = other.blockHash; this.height = other.height; - minableCommitmentsByQuorum = new HashMap, Sha256Hash>(other.minableCommitmentsByQuorum); - minableCommitments = new LinkedHashMap(other.minableCommitments); + minableCommitmentsByQuorum = new HashMap<>(other.minableCommitmentsByQuorum); + minableCommitments = new LinkedHashMap<>(other.minableCommitments); this.isFirstQuorumCheck = other.isFirstQuorumCheck; } @@ -58,7 +73,7 @@ protected void parse() throws ProtocolException { blockHash = readHash(); height = (int)readUint32(); int size = (int)readVarInt(); - minableCommitmentsByQuorum = new HashMap, Sha256Hash>(size); + minableCommitmentsByQuorum = new HashMap<>(size); for(int i = 0; i < size; ++i) { int type = readBytes(1)[0]; @@ -68,7 +83,7 @@ protected void parse() throws ProtocolException { } size = (int)readVarInt(); - minableCommitments = new LinkedHashMap(size); + minableCommitments = new LinkedHashMap<>(size); for(long i = 0; i < size; ++i) { Sha256Hash hash = readHash(); @@ -117,7 +132,7 @@ public String toString() { return builder.toString(); } - BLSSignature getSignatureForIndex(HashMap> quorumsCLSigs, int index) { + BLSSignature getSignatureForIndex(Map> quorumsCLSigs, int index) { Optional>> answer = quorumsCLSigs.entrySet().stream().filter(entry -> entry.getValue().contains(index)).findFirst(); return answer.map(Map.Entry::getKey).orElse(null); } @@ -211,7 +226,7 @@ void addCommitment(FinalCommitment commitment) lock.lock(); try { - Pair pair = new Pair(commitment.llmqType, commitment.quorumHash); + Pair pair = new Pair<>(commitment.llmqType, commitment.quorumHash); minableCommitmentsByQuorum.put(pair, commitmentHash); minableCommitments.put(commitmentHash, commitment); } finally { @@ -278,24 +293,19 @@ public boolean verify(Transaction coinbaseTx, SimplifiedMasternodeListDiff mnlis CoinbaseTx cbtx = (CoinbaseTx) coinbaseTx.getExtraPayloadObject(); - if(mnlistdiff.getNewQuorums().isEmpty() && mnlistdiff.getDeletedQuorums().isEmpty() && - prevList != null && prevList.coinbaseTxPayload != null) { - if(cbtx.getMerkleRootQuorums().equals(prevList.coinbaseTxPayload.getMerkleRootQuorums())) - return true; + if (mnlistdiff.getNewQuorums().isEmpty() && mnlistdiff.getDeletedQuorums().isEmpty() && + prevList != null && prevList.coinbaseTxPayload != null && + cbtx.getMerkleRootQuorums().equals(prevList.coinbaseTxPayload.getMerkleRootQuorums())) { + return true; } - ArrayList commitmentHashes = new ArrayList(); + ArrayList commitmentHashes = new ArrayList<>(); for (FinalCommitment commitment : minableCommitments.values()) { commitmentHashes.add(commitment.getHash()); } - commitmentHashes.sort(new Comparator() { - @Override - public int compare(Sha256Hash o1, Sha256Hash o2) { - return o1.compareTo(o2); - } - }); + commitmentHashes.sort(Comparator.naturalOrder()); if (!cbtx.getMerkleRootQuorums().isZero() && !commitmentHashes.isEmpty() && @@ -308,20 +318,15 @@ public int compare(Sha256Hash o1, Sha256Hash o2) { } } - public static boolean verifyMerkleRoot(ArrayList minableCommitments, Sha256Hash merkleRootQuorums) { + public static boolean verifyMerkleRoot(List minableCommitments, Sha256Hash merkleRootQuorums) { - ArrayList commitmentHashes = new ArrayList(); + ArrayList commitmentHashes = new ArrayList<>(); for (FinalCommitment commitment : minableCommitments) { commitmentHashes.add(commitment.getHash()); } - commitmentHashes.sort(new Comparator() { - @Override - public int compare(Sha256Hash o1, Sha256Hash o2) { - return o1.compareTo(o2); - } - }); + commitmentHashes.sort(Comparator.naturalOrder()); return merkleRootQuorums.isZero() || commitmentHashes.isEmpty() || @@ -331,18 +336,13 @@ public int compare(Sha256Hash o1, Sha256Hash o2) { public Sha256Hash calculateMerkleRoot() { lock.lock(); try { - ArrayList commitmentHashes = new ArrayList(); + ArrayList commitmentHashes = new ArrayList<>(); for(FinalCommitment commitment : minableCommitments.values()) { commitmentHashes.add(commitment.getHash()); } - Collections.sort(commitmentHashes, new Comparator() { - @Override - public int compare(Sha256Hash o1, Sha256Hash o2) { - return o1.compareTo(o2); - } - }); + commitmentHashes.sort(Comparator.naturalOrder()); return MerkleRoot.calculateMerkleRoot(commitmentHashes); } finally { @@ -371,13 +371,11 @@ public void forEachQuorum(boolean onlyValid, ForeachQuorumCallback callback) { } } - public int getCount() - { + public int getCount() { return minableCommitments.size(); } - public int getValidCount() - { + public int getValidCount() { int count = 0; for (Map.Entry p : minableCommitments.entrySet()) { if (isCommitmentValid(p.getValue())) { @@ -433,14 +431,12 @@ private boolean checkCommitment(FinalCommitment commitment, StoredBlock prevBloc LLMQParameters llmqParameters = params.getLlmqs().get(LLMQParameters.LLMQType.fromValue(commitment.llmqType)); - if (commitment.isNull()) { - if (!commitment.verifyNull()) { - throw new VerificationException("invalid commitment: null value"); - } + if (commitment.isNull() && !commitment.verifyNull()) { + throw new VerificationException("invalid commitment: null value"); } if (validateQuorums) { - ArrayList members = manager.getAllQuorumMembers(llmqParameters.type, commitment.quorumHash); + List members = manager.getAllQuorumMembers(llmqParameters.type, commitment.quorumHash); if (members == null) { //no information about this quorum because it is before we were downloading @@ -453,7 +449,7 @@ private boolean checkCommitment(FinalCommitment commitment, StoredBlock prevBloc for (Masternode mn : members) { builder.append("\n ").append(mn.getProTxHash()); } - log.info(builder.toString()); + log.info("{}", builder); } if (!commitment.verify(quorumBlock, members, true)) { From 9439bb71faec30857f99bf275b6a256b42c27fa5 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:06:24 -0700 Subject: [PATCH 26/31] refactor: make constructors protected in SpecialTxPayload Signed-off-by: HashEngineering --- .../java/org/bitcoinj/evolution/SpecialTxPayload.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/SpecialTxPayload.java b/core/src/main/java/org/bitcoinj/evolution/SpecialTxPayload.java index dab66266a7..abf0b246b3 100644 --- a/core/src/main/java/org/bitcoinj/evolution/SpecialTxPayload.java +++ b/core/src/main/java/org/bitcoinj/evolution/SpecialTxPayload.java @@ -27,21 +27,21 @@ public abstract class SpecialTxPayload extends ChildMessage { protected int version; - public SpecialTxPayload(NetworkParameters params, Transaction tx) { + protected SpecialTxPayload(NetworkParameters params, Transaction tx) { super(params, tx.getExtraPayload(), 0); setParent(tx); } - public SpecialTxPayload(int version) { + protected SpecialTxPayload(int version) { this.version = version; } - SpecialTxPayload(NetworkParameters params, int version) { + protected SpecialTxPayload(NetworkParameters params, int version) { super(params); this.version = version; } - public SpecialTxPayload(NetworkParameters params, byte [] payload, int offset) { + protected SpecialTxPayload(NetworkParameters params, byte [] payload, int offset) { super(params, payload, offset); } From b6e26339f15dac33d5599a0a1f4868c7f1483483 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:07:00 -0700 Subject: [PATCH 27/31] tests: update mnlistdiff related tests Signed-off-by: HashEngineering --- .../evolution/SimplifiedMasternodesTest.java | 3 ++- .../bitcoinj/quorums/QuorumRotationInfoTest.java | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/core/src/test/java/org/bitcoinj/evolution/SimplifiedMasternodesTest.java b/core/src/test/java/org/bitcoinj/evolution/SimplifiedMasternodesTest.java index 3a2ac19a9d..0012e5e8dc 100644 --- a/core/src/test/java/org/bitcoinj/evolution/SimplifiedMasternodesTest.java +++ b/core/src/test/java/org/bitcoinj/evolution/SimplifiedMasternodesTest.java @@ -32,6 +32,7 @@ import org.bitcoinj.store.FlatDB; import org.bitcoinj.store.MemoryBlockStore; +import org.bitcoinj.utils.MerkleRoot; import org.junit.BeforeClass; import org.junit.Test; @@ -197,7 +198,7 @@ public void quorumHashTest() { hashes.add(Sha256Hash.wrapReversed(Utils.HEX.decode(hashesAsStrings[i]))); } - System.out.println(SimplifiedQuorumList.calculateMerkleRoot(hashes)); + System.out.println(MerkleRoot.calculateMerkleRoot(hashes)); } } diff --git a/core/src/test/java/org/bitcoinj/quorums/QuorumRotationInfoTest.java b/core/src/test/java/org/bitcoinj/quorums/QuorumRotationInfoTest.java index 579da3b589..50500f6dc0 100644 --- a/core/src/test/java/org/bitcoinj/quorums/QuorumRotationInfoTest.java +++ b/core/src/test/java/org/bitcoinj/quorums/QuorumRotationInfoTest.java @@ -60,7 +60,7 @@ public void core18RoundTripTest() throws IOException { assertArrayEquals(payloadOne, quorumRotationInfo.bitcoinSerialize()); assertTrue(quorumRotationInfo.hasChanges()); - assertEquals(Sha256Hash.wrap("000003227cf2f83a1faa683ece5b875abeb555ebf1252f62cb28a96d459bcc11"), quorumRotationInfo.mnListDiffTip.blockHash); + assertEquals(Sha256Hash.wrap("000003227cf2f83a1faa683ece5b875abeb555ebf1252f62cb28a96d459bcc11"), quorumRotationInfo.mnListDiffTip.getBlockHash()); } // 2023-06-16: mainnet is on 19.1 with protocol 70227 @@ -73,8 +73,8 @@ public void qrinfo_70227() throws IOException { assertArrayEquals(payloadOne, quorumRotationInfo.bitcoinSerialize()); assertTrue(quorumRotationInfo.hasChanges()); - assertEquals(Sha256Hash.wrap("000000000000000cafc4b174a51768b6216880613ce1ef19add8e84ca48c97d1"), quorumRotationInfo.mnListDiffTip.blockHash); - assertEquals(SimplifiedMasternodeListDiff.LEGACY_BLS_VERSION, quorumRotationInfo.mnListDiffAtH.getVersion()); + assertEquals(Sha256Hash.wrap("000000000000000cafc4b174a51768b6216880613ce1ef19add8e84ca48c97d1"), quorumRotationInfo.mnListDiffTip.getBlockHash()); + assertEquals(SimplifiedMasternodeListDiff.CURRENT_VERSION, quorumRotationInfo.mnListDiffAtH.getVersion()); assertFalse(quorumRotationInfo.mnListDiffAtH.hasBasicSchemeKeys()); assertArrayEquals(payloadOne, quorumRotationInfo.bitcoinSerialize()); @@ -88,7 +88,7 @@ public void qrinfo_70228_beforeActivation() throws IOException { assertArrayEquals(payloadOne, quorumRotationInfo.bitcoinSerialize()); assertTrue(quorumRotationInfo.hasChanges()); - assertEquals(Sha256Hash.wrap("000001a7d846454edba8aa61df85ce277980897503e45fcc0c39bd19ff2ccbcf"), quorumRotationInfo.mnListDiffTip.blockHash); + assertEquals(Sha256Hash.wrap("000001a7d846454edba8aa61df85ce277980897503e45fcc0c39bd19ff2ccbcf"), quorumRotationInfo.mnListDiffTip.getBlockHash()); assertEquals(1, quorumRotationInfo.mnListDiffAtH.getVersion()); assertFalse(quorumRotationInfo.mnListDiffAtH.hasBasicSchemeKeys()); @@ -103,7 +103,7 @@ public void qrinfo_70228_afterActivation() throws IOException { assertArrayEquals(payloadOne, quorumRotationInfo.bitcoinSerialize()); assertTrue(quorumRotationInfo.hasChanges()); - assertEquals(Sha256Hash.wrap("00000134f050317635efc92333a106c25219c8d0fe3ad8fbccba48b6dd4057d3"), quorumRotationInfo.mnListDiffTip.blockHash); + assertEquals(Sha256Hash.wrap("00000134f050317635efc92333a106c25219c8d0fe3ad8fbccba48b6dd4057d3"), quorumRotationInfo.mnListDiffTip.getBlockHash()); assertEquals(1, quorumRotationInfo.mnListDiffAtH.getVersion()); assertTrue(quorumRotationInfo.mnListDiffAtH.hasBasicSchemeKeys()); @@ -119,7 +119,7 @@ public void qrinfo_70230_afterActivation() throws IOException { assertTrue(quorumRotationInfo.hasChanges()); // 905770 - assertEquals(Sha256Hash.wrap("000002920ed0a1295fbd27e0acbdc5451200040fafb5fecd56f355cd7d7b9b73"), quorumRotationInfo.mnListDiffTip.blockHash); + assertEquals(Sha256Hash.wrap("000002920ed0a1295fbd27e0acbdc5451200040fafb5fecd56f355cd7d7b9b73"), quorumRotationInfo.mnListDiffTip.getBlockHash()); assertEquals(1, quorumRotationInfo.mnListDiffAtH.getVersion()); assertTrue(quorumRotationInfo.mnListDiffAtH.hasBasicSchemeKeys()); @@ -133,8 +133,8 @@ public void qrinfo_70230() throws IOException { assertArrayEquals(payloadOne, quorumRotationInfo.bitcoinSerialize()); assertTrue(quorumRotationInfo.hasChanges()); - assertEquals(Sha256Hash.wrap("00000000000000239004bad185d58602b8b90cc8211d29f55b93d72bdaa3a098"), quorumRotationInfo.mnListDiffTip.blockHash); - assertEquals(SimplifiedMasternodeListDiff.LEGACY_BLS_VERSION, quorumRotationInfo.mnListDiffAtH.getVersion()); + assertEquals(Sha256Hash.wrap("00000000000000239004bad185d58602b8b90cc8211d29f55b93d72bdaa3a098"), quorumRotationInfo.mnListDiffTip.getBlockHash()); + assertEquals(SimplifiedMasternodeListDiff.CURRENT_VERSION, quorumRotationInfo.mnListDiffAtH.getVersion()); assertTrue(quorumRotationInfo.mnListDiffAtH.hasBasicSchemeKeys()); assertArrayEquals(payloadOne, quorumRotationInfo.bitcoinSerialize()); From d622b2c31e3769d06ff256e648aab35aaef5f1c9 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:07:28 -0700 Subject: [PATCH 28/31] refactor: use <> in MasternodeMetaDataManager Signed-off-by: HashEngineering --- .../org/bitcoinj/evolution/MasternodeMetaDataManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/MasternodeMetaDataManager.java b/core/src/main/java/org/bitcoinj/evolution/MasternodeMetaDataManager.java index 58403185a5..f199f619d2 100644 --- a/core/src/main/java/org/bitcoinj/evolution/MasternodeMetaDataManager.java +++ b/core/src/main/java/org/bitcoinj/evolution/MasternodeMetaDataManager.java @@ -1,9 +1,9 @@ package org.bitcoinj.evolution; import org.bitcoinj.core.*; -import org.bitcoinj.governance.GovernanceObject; import java.util.ArrayList; +import java.util.List; public class MasternodeMetaDataManager extends AbstractManager { @@ -19,8 +19,8 @@ public void removeGovernanceObject(Sha256Hash governanceObject) { } - public ArrayList getAndClearDirtyGovernanceObjectHashes() { - return new ArrayList(); + public List getAndClearDirtyGovernanceObjectHashes() { + return new ArrayList<>(); } @Override From 2c8bd40ece96df1a617d9fc7d4b916bf0bc77761 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:07:49 -0700 Subject: [PATCH 29/31] refactor: use <> in GovernanceObject Signed-off-by: HashEngineering --- .../bitcoinj/governance/GovernanceObject.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/governance/GovernanceObject.java b/core/src/main/java/org/bitcoinj/governance/GovernanceObject.java index 5cec51ea38..ef7ba4475b 100644 --- a/core/src/main/java/org/bitcoinj/governance/GovernanceObject.java +++ b/core/src/main/java/org/bitcoinj/governance/GovernanceObject.java @@ -65,10 +65,10 @@ public class GovernanceObject extends Message implements Serializable { public static final long GOVERNANCE_FEE_CONFIRMATIONS = 6; public static final long GOVERNANCE_MIN_RELAY_FEE_CONFIRMATIONS = 1; - public static final long GOVERNANCE_UPDATE_MIN = 60*60; - public static final long GOVERNANCE_DELETION_DELAY = 10*60; - public static final long GOVERNANCE_ORPHAN_EXPIRATION_TIME = 10*60; - public static final long GOVERNANCE_WATCHDOG_EXPIRATION_TIME = 2*60*60; + public static final long GOVERNANCE_UPDATE_MIN = 60*60L; + public static final long GOVERNANCE_DELETION_DELAY = 10*60L; + public static final long GOVERNANCE_ORPHAN_EXPIRATION_TIME = 10*60L; + public static final long GOVERNANCE_WATCHDOG_EXPIRATION_TIME = 2*60*60L; public static final int GOVERNANCE_TRIGGER_EXPIRATION_BLOCKS = 576; @@ -154,13 +154,13 @@ public GovernanceObject(NetworkParameters params) { public GovernanceObject(NetworkParameters params, byte[] payload) { super(params, payload, 0); context = Context.get(); - mapCurrentMNVotes = new HashMap(); + mapCurrentMNVotes = new HashMap<>(); } public GovernanceObject(NetworkParameters params, byte[] payload, int cursor) { super(params, payload, cursor); context = Context.get(); - mapCurrentMNVotes = new HashMap(); + mapCurrentMNVotes = new HashMap<>(); } public final long getCreationTime() { @@ -250,7 +250,7 @@ void parseFromDisk() { nDeletionTime = readInt64(); fExpired = readBytes(1)[0] == 0 ? false : true; int size = (int)readVarInt(); - mapCurrentMNVotes = new HashMap(); + mapCurrentMNVotes = new HashMap<>(); for(int i = 0; i < size; ++i) { TransactionOutPoint vin = new TransactionOutPoint(params, payload, offset); cursor += vin.getMessageSize(); @@ -649,7 +649,7 @@ public int getAbstainCount(VoteSignal eVoteSignalIn) { } public Pair getCurrentMNVotes(TransactionOutPoint mnCollateralOutpoint) { - Pair result = new Pair(false, null); //default to failure + Pair result = new Pair<>(false, null); //default to failure VoteRecord it = mapCurrentMNVotes.get(mnCollateralOutpoint); if (it == null) { return result; @@ -668,7 +668,7 @@ public boolean processVote(Peer pfrom, GovernanceVote vote, GovernanceException context.masternodeListManager.getListAtChainTip().getMNByCollateral(vote.getMasternodeOutpoint()) == null) { String message = "CGovernanceObject::ProcessVote -- Masternode index not found"; exception.setException(message, GOVERNANCE_EXCEPTION_WARNING); - if (mapOrphanVotes.put(vote.getMasternodeOutpoint(), new Pair((int)(Utils.currentTimeSeconds() + GOVERNANCE_ORPHAN_EXPIRATION_TIME), vote))) { + if (mapOrphanVotes.put(vote.getMasternodeOutpoint(), new Pair<>((int)(Utils.currentTimeSeconds() + GOVERNANCE_ORPHAN_EXPIRATION_TIME), vote))) { if (pfrom != null) { //TODO: context.masternodeManager.askForMN(pfrom, vote.getMasternodeOutpoint()); } From a559bcaa98a9ddb1065a3cc93cd5ee7158f663f4 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 12 Jul 2024 11:10:11 -0700 Subject: [PATCH 30/31] refactor: make bootStrapLoaded private and add a getter Signed-off-by: HashEngineering --- .../bitcoinj/evolution/AbstractQuorumState.java | 16 ++++++++++------ .../SimplifiedMasternodeListManager.java | 8 ++++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java b/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java index 6d1f9b4fa6..9e6d5a958e 100644 --- a/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java +++ b/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java @@ -119,19 +119,19 @@ boolean initChainTipSyncComplete() { String bootstrapFilePath = null; InputStream bootstrapStream = null; int bootStrapFileFormat = 0; - public SettableFuture bootStrapLoaded; + private SettableFuture bootStrapLoaded; boolean isLoadingBootstrap = false; protected static Random random = new Random(); - public AbstractQuorumState(Context context) { + protected AbstractQuorumState(Context context) { super(context.getParams()); this.context = context; initializeOnce(); initialize(); } - public AbstractQuorumState(NetworkParameters params, byte[] payload, int offset, int protocolVersion) { + protected AbstractQuorumState(NetworkParameters params, byte[] payload, int offset, int protocolVersion) { super(params, payload, offset, protocolVersion); initialize(); } @@ -200,6 +200,10 @@ public boolean reachedMaxFailedAttempts() { return failedAttempts > MAX_ATTEMPTS; } + public SettableFuture getBootStrapLoadedFuture() { + return bootStrapLoaded; + } + abstract int getBlockHeightOffset(); public abstract int getUpdateInterval(); @@ -231,13 +235,13 @@ public void retryLastUpdate(Peer peer) { public abstract SimplifiedMasternodeList getMasternodeListAtTip(); - public abstract LinkedHashMap getMasternodeListCache(); + public abstract Map getMasternodeListCache(); - public abstract LinkedHashMap getQuorumsCache(); + public abstract Map getQuorumsCache(); public abstract SimplifiedQuorumList getQuorumListAtTip(); - public abstract ArrayList getAllQuorumMembers(LLMQParameters.LLMQType llmqType, Sha256Hash blockHash); + public abstract List getAllQuorumMembers(LLMQParameters.LLMQType llmqType, Sha256Hash blockHash); public void resetMNList() { resetMNList(false, true); diff --git a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java index 28fa689fb2..bce0758599 100644 --- a/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java +++ b/core/src/main/java/org/bitcoinj/evolution/SimplifiedMasternodeListManager.java @@ -103,11 +103,11 @@ public StoredBlock getBlockTip() { } public void waitForBootstrapLoaded() throws ExecutionException, InterruptedException { - if (quorumState.bootStrapLoaded != null) { - quorumState.bootStrapLoaded.get(); + if (quorumState.getBootStrapLoadedFuture() != null) { + quorumState.getBootStrapLoadedFuture().get(); } - if (quorumRotationState.bootStrapLoaded != null) { - quorumRotationState.bootStrapLoaded.get(); + if (quorumRotationState.getBootStrapLoadedFuture() != null) { + quorumRotationState.getBootStrapLoadedFuture().get(); } } From ea5dfa5210707bd2733170bc3e06e43deef64271 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Wed, 24 Jul 2024 14:56:05 -0700 Subject: [PATCH 31/31] fix: log and remove a deadlock when logging a diff message using the blockchain Signed-off-by: HashEngineering --- .../evolution/AbstractQuorumState.java | 10 +++-- .../org/bitcoinj/evolution/QuorumState.java | 21 ++++++++++ .../bitcoinj/utils/DebugReentrantLock.java | 39 +++++++++++++++++++ .../java/org/bitcoinj/utils/Threading.java | 5 +++ 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/org/bitcoinj/utils/DebugReentrantLock.java diff --git a/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java b/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java index 9e6d5a958e..4b315cacfb 100644 --- a/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java +++ b/core/src/main/java/org/bitcoinj/evolution/AbstractQuorumState.java @@ -47,6 +47,7 @@ import org.bitcoinj.quorums.SimplifiedQuorumList; import org.bitcoinj.store.BlockStoreException; import org.bitcoinj.utils.ContextPropagatingThreadFactory; +import org.bitcoinj.utils.DebugReentrantLock; import org.bitcoinj.utils.Threading; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -89,7 +90,8 @@ public abstract class AbstractQuorumState getQueuedThreads() { + return super.getQueuedThreads(); + } + + public Thread getOwner() { + return super.getOwner(); + } +} diff --git a/core/src/main/java/org/bitcoinj/utils/Threading.java b/core/src/main/java/org/bitcoinj/utils/Threading.java index 8632b36161..9e60575da0 100644 --- a/core/src/main/java/org/bitcoinj/utils/Threading.java +++ b/core/src/main/java/org/bitcoinj/utils/Threading.java @@ -162,6 +162,11 @@ public static ReentrantLock lock(String name) { return factory.newReentrantLock(name); } + /** creates a lock that gives access to the owner thread **/ + public static DebugReentrantLock debugLock(String name) { + return new DebugReentrantLock(name); + } + public static void setUseDefaultAndroidPolicy(boolean use) { useDefaultAndroidPolicy = use; }