File: blk03381.txt
/ViaBTC/Mined by meno5371/, 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474nd 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474nC FjDOUT:3D9F0A0449D91C3343F0BFEF3EB23AA18B601B147213F9A4559BA64718E8AF0E IjGREFUND:56077326FC10BAAB6E0230AF2A8E293F37BAF1EF14CD5A0D4F4C1C3FA04A0262 FjDOUT:96BECFE94CA30AD066515B74539F97AF51EABF2945E03DCC80D0A406DF0DA249 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474n text/html;charset=utf-8 <!doctypehtml><html lang="en"><meta charset="UTF-8"><title>600 000 000 000</title><style>body{background:#f7931a;color:#fff;text-align:center}h1{font-weight:700;margin:0;font-size:666px;line-height:.9;font-family:sans}</style><h1>600 000 000 000</h1> GjE=:LTC.LTC:ltc1qtwfdwwp3adpwdgyvejj8k4mcrawnqvdr7gtzr6:2497261523:t:30 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474n4% c/Foundry USA Pool #dropgold/ Bj@=:ETH.ETH:0xa34fc44e304de518b781bfb3eadef606e5fe9cab:204141549:t Aj?=:ETH.ETH:0x002eF02188CD5Af452662BAB7B0d217C98edc4c8:61461:te:0 Bj@=:BNB.BNB:bnb1z203xu68awz2ucee2ld92redjwh5kk4f7ttga0:260675:te:0 CjA=:BNB.BNB:bnb1277llrkm5luch2ac8p32g06qmwapczffgdy7d6:1135575:te:0 GjE=:BNB.BTCB-1DE:bnb1xnfqwtwzlxp5d6kk4teejkwt6n7kh665a5t86t:156826:te:0 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474nh DjB=:ETH.ETH:0xf3BF52129B6aB158bA6d52BB4eD9ba830D837744:23625389:te:0 c/Foundry USA Pool #dropgold/ 8j6BERNSTEIN 2.0 REG f039ea5c-096d-4530-8e95-756b5b652d79 FjD=:THOR.RUNE:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474n:396898401658 7j5ion:10.QmY1BXnLuPWpG886pjZySeHZvfdF9QY5DYkmkw5PKcWRhc LjJs:GAIA.ATOM:cosmos1j0gdax9mfqk46msh25wh6l9gdhrqjdmeffw30e:13271266013:ss:0 Bj@=:ETH.ETH:0x4d7d7c6cC1171f52eEE75a4FAB1F45FAe66afdc2:182237:te:0 CjA=:BNB.BNB:bnb1xnfqwtwzlxp5d6kk4teejkwt6n7kh665a5t86t:2639585:te:0 JjH=:BNB.TWT-8C2:bnb1qfl4g38saelylhw6exkcf3ndcrtajy8k8248zk:1681715407:te:0 KjI=:BNB.BUSD-BD1:bnb1rwtf79az0wu49aju63kcaxayq7aql6qt6uq4mv:2310102804:te:0 FjDOUT:F862F6564CB1139A350C0E40300C50281EDBE5DB5292D1D4B715845C044BF004 IjGREFUND:86892234ED3FA22800E2B0461A4A63E11A976120D8618C42A0F444B5ECBD50F8 EjCs:ETH.ETH:0x749885b96c8989C32DF08480E5F064BcAE8C5Fa3:116120048:ss:0 CjA=:BNB.BNB:bnb17svwgfv49na3xz0e2v90aje5naht5y3k0xz23c:1564930:te:0 c/Foundry USA Pool #dropgold/ FjDOUT:55348E9171D3FE3300F4C09C34E021057116D71E8F309288045F0CF3E2D3DC5B FjDOUT:3DEA8A61D52864F6A2DB10F76B5BD3800276C0263DC542E5CEE0699A5131B53F FjDOUT:5B05C23B4CBB896A730AB2775BEBB53E74B3C8499FD1AD381083FE2D6B7B676A FjDOUT:42273D42823F4157B5640FD5177779052747563AEFC8BF880A152060143197E9 CjAs:DOGE.DOGE:DQB6j8npPxMAd5haECgcoHX2t33kwVSKBm:1036183033564:ss:0 SjLPs:ETH.USDT-13D831EC7:0x32EeD2A329794589294C321b76AE7fF49113a1C2:99371668544:ss:0 Bj@=:ETH.ETH:0xA34fc44E304dE518b781Bfb3eadEF606e5Fe9cAB:202490329:t2 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474n 4j2DC-L5:UE664nEDRqD3F9LQMtKVrcqEvtCZwKLSYQyUr3iTMVE= =j;Stell Dir vor es ist Krieg und keiner geht hin fuck Putin Q /ViaBTC/Mined by amosss1/, KjISWAPTX:0x9da3abe6075f8e967bbb07467408cad6833ce759e7c03eeaf107d7902206e7e5 text/plain;charset=utf-8 Ordinals Puzzle #2 ... ... dropping soon on the timechain. This is a key piece to solving the puzzle. Follow @ordinalsindex on Twitter for updates. :j8=:RUNE:thor1y5mhjjz8pkzsyha06clwse64s2e48w0y4grttj::wr:0 /ViaBTC/Mined by fermakgbmk/, Bj@=:ETH.ETH:0x5e8f151E9bc6E30bc99c03b5e1D3198DE8d12D54:837634:te:0 Bj@=:BNB.BNB:bnb1hj0trl02xw5jyk9sgwyq2eraa5tzxf27tlvd72:340214:te:0 Bj@=:ETH.ETH:0x1D45997F8f6ea825A48Eb7aB5B9EbE035b86658c:124526:te:0 KjI=:BNB.TWT-8C2:bnb1jnfpv4jc9w7dam2k2436ny57460jcg36wfslsg:22309929123:te:0 LjJ=:BNB.BUSD-BD1:bnb1lp2c45aa6zdqdevtdjy5eg89t4zfw4tjlke8tj:32491936277:te:0 CjA=:BNB.BNB:bnb1hv9czre8fl6p39wah5f6vg3v9rxcd3pkassjez:2898773:te:0 1\ Powered by Luxor Tech \ 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474n IjGREFUND:E74A26F47301A40D04B406B894AD3CC1E5FDD988C2B70B030CE70D66547BE3DB IjGREFUND:E19F8EF750D74D4456A618E44F81F5EAD1D6F6ED7EC405940C15FF9CA0826602 KjI=:BNB.BUSD-BD1:bnb1yhpptekq6n78d89w50d4d9ywgrle33p7dc0guv:5970795845:te:0 FjD=:LTC.LTC:ltc1qqx9v7r4e6s39u5twr22trf5lxnlt4qxsj9qfwj:169947468:t:30 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474n SjLPs:ETH.USDC-E3606EB48:0x834bf675dAf52B5D6fD953a57c83e824521E5ab6:44729677632:ss:0 FjDOUT:0822E39D05D2A2EC8B3F7B76C24CB6A745AF54C82D29959E70BC6F1AA52D0FB9 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474n4J FjDOUT:A9133BC8DB0377D8E62B2CBEC40E11E4FE3CF46FA7E4E727FFA3EFAFE844F5B4 FjDOUT:0B343EF4DB83F7E26A7E99923530DCDFA3A94D6C48EE918AC8D9A6DABAD86C0A FjDOUT:65C2EEA9B1495C224076CD341CE6F877E456AED4016092CDB09134C5C0A17FB5 FjDOUT:929E96C055817046DF38568F05ED344E1826363DF414D5A79DF7FBC0D38870F9 FjDOUT:BD9B469110DE9DE96318B455976D3F9028FBD2AE527B0B07B5377617B66F4570 FjDOUT:58808E85E050B8E58933F660199022A6A6EDF384AD197D378E6F9EDC5CD470DE DjB=:BNB.BNB:bnb1zf0k59rcpqr5r40p2acyqvsep07xklde6d5tv5:22297495:te:0 LjJ=:BNB.BUSD-BD1:bnb1lp2c45aa6zdqdevtdjy5eg89t4zfw4tjlke8tj:24531416280:te:0 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474nc FjDOUT:F66CAA38058DFE211215945AD96A60148C46D8868D78DAEC42D7FBF51B989FE6 FjDOUT:2004FE2E7DC28F5ABC76CFC86C0227F417E12E6E977277866815E3E461C65758 FjDOUT:D7E7451B4579E1E5F1E2367C124F9D7543AE0070D54EF5FB7F0CDA6FBCC6BF2B 7j5ion:15.QmbK6uUxWaBS8uqh6pd1iXp4ikQN3xa8AuoYrdvjNVDDzL Bj@=:ETH.ETH:0x422b204F960ea7A70144D4469ECE10EBE3F3f586:367636:te:0 c/Foundry USA Pool #dropgold/ KjISWAPTX:0x1a0276204b506559f45d1be3834b6e84cbbfee2f1b8e98d0712ec038e7d815b3 FjDOUT:6A3C40F228718A34B8F78AF954C1D62803A9ED5577D798279737EACF67598ABA FjDOUT:D39DDC050E29BD7172B573535E74CFDDA9CDA576843A557E603521A9C3725FF1 Bj@=:BNB.BNB:bnb1cxdmzc5dk8wrk8v5lr9zksmhzfmm0wwa4f90wr:441835:te:0 2ce981b37a0440f7353880d1546c91ecH0E c/Foundry USA Pool #dropgold/ 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474n[ FjDOUT:E7F8B93686B68EA211F92E2FF95F6E1DEBC24DFC33DA41617BA5B6A34046E94D FjDOUT:E7255F20E202A6A113F374C435BA2D9DC38E590F9BF22B0A0FB1FCFFE7CA4A94 DjB=:BNB.BNB:bnb1xlrj688ksx7sw2qatvhrkful7t088l0r282wfd:28216438:te:0 KjI=:BNB.BUSD-BD1:bnb1lp2c45aa6zdqdevtdjy5eg89t4zfw4tjlke8tj:5987743462:te:0 text/html;charset=utf-8 <title>Basic Missile Command HTML Game</title> <meta charset="UTF-8"> background: black; align-items: center; justify-content: center; cursor: crosshair; border: 1px solid white; <canvas width="800" height="550" id="game"></canvas> const canvas = document.getElementById('game'); const context = canvas.getContext('2d');M const groundY = 500; // y position of where the ground starts const cityWidth = 45; // how wide a city rect is const cityHeight = 25; // how tall a city rect is const cityY = groundY - cityHeight; // y position of the city const siloY = groundY - 30; // y position of the top of the silo const missileSize = 4; // the radius/size of a missile const missileSpeed = 1; // how fast a missile moves const counterMissileSpeed = 15; // how fast a counter-missile moves // information about each missile let counterMissiles = []; // information about each explosion let explosions = []; // how many missiles to spawn at each interval of the level (in this // case spawn 4 missiles at the start of level 1 and 4 more missiles // at the next interval of level 1) const levels = [ [4, 4] ]; let currInterval = 0; // the x/y position of all cities and if the city is currently alive { x: 140, y: cityY, alive: true }, { x: 220, y: cityY, alive: true }, { x: 300, y: cityY, aliveM { x: 500, y: cityY, alive: true }, { x: 580, y: cityY, alive: true }, { x: 660, y: cityY, alive: true } // the x position of each of the 3 silos const siloPos = [ 55, canvas.width / 2, 745 ]; // the x/y position of each silo, the number of missiles left, and if // it is still alive { x: siloPos[0], y: siloY, missiles: 10, alive: true }, { x: siloPos[1], y: siloY, missiles: 10, alive: true }, { x: siloPos[2], y: siloY, missiles: 10, alive: true } // the x/y position of eaM ch missile spawn point. missiles spawn // directly above each city and silo plus the two edges const missileSpawns = cities .concat([{ x: 0, y: 0 }, { x: canvas.width, y: 0 }]) .map(pos => ({ x: pos.x, y: 0 })); // return a random integer between min (inclusive) and max (inclusive) // @see https://stackoverflow.com/a/1527820/2124254 function randInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; // get the angle between two points function angleBetweenPoints(source,M // atan2 returns the counter-clockwise angle in respect to the // x-axis, but the canvas rotation system is based on the y-axis // (rotation of 0 = up). // so we need to add a quarter rotation to return a // counter-clockwise rotation in respect to the y-axis return Math.atan2(target.y - source.y, target.x - source.x) + Math.PI / 2; // distance between two points function distance(source, target) { return Math.hypot(source.x - target.x, source.y - target.y); // spawn a missile by choM osing a spawn point and a target. // a missile can target any city or silo function spawnMissile() { const targets = cities.concat(silos); const randSpawn = randInt(0, missileSpawns.length - 1); const randTarget = randInt(0, targets.length - 1); const start = missileSpawns[randSpawn]; const target = targets[randTarget]; const angle = angleBetweenPoints(start, target); start, // where the missile started target, // where the missile is going pos: { x: start.x, y: start.y M }, // current position alive: true, // if we should still draw the missile // used to update the position every frame dx: missileSpeed * Math.sin(angle), dy: missileSpeed * -Math.cos(angle) // start at -2 seconds (time is in milliseconds) to give the player 1 // second before the missiles start let lastTime = -2000; function loop(time) { requestAnimationFrame(loop); context.clearRect(0,0,canvas.width,canvas.height); // spawn missiles every interval of 3 seconds (if thM if (time - lastTime > 3000 && currInterval < levels[currLevel].length) { for (let i = 0; i < levels[currLevel][currInterval]; i++) { spawnMissile(); lastTime = time; context.fillStyle = 'blue'; cities.forEach(city => { // center the city on the x position context.fillRect(city.x - cityWidth / 2, city.y, cityWidth, cityHeight); // draw ground and silos context.fillStyle = 'yellow'; context.moveTo(0, canvas.height); context.lineTo(0, groundY); // draw each silo hill siloPos.forEach(x => { context.lineTo(x - 40, groundY); context.lineTo(x - 20, siloY); context.lineTo(x + 20, siloY); context.lineTo(x + 40, groundY); context.lineTo(canvas.width, groundY); context.lineTo(canvas.width, canvas.height); // draw the number of counter-missiles each silo context.fillStyle = 'black'; silos.forEach(silo => { // draw missiles in aM triangular shape by incrementing how many // missiles we can draw per row let missilesPerRow = 1; let y = silo.y + 5; for (let i = 0; i < silo.missiles; i++) { context.fillRect(x, y, 4, 10); if (++count === missilesPerRow) { x = silo.x - 6 * count; missilesPerRow++; // update and draw missiles context.strokeStyle = 'red'; context.lineWidth = 2; te color based on time so it "blinks" // by dividing by a number and seeing if it's odd or even we can // change the speed of the blinking context.fillStyle = 'white'; if (Math.round(time / 2) % 2 === 0) { context.fillStyle = 'black'; missiles.forEach(missile => { missile.pos.x += missile.dx; missile.pos.y += missile.dy; // check if the missile hit an explosion by doing a circle-circle // collision check explosions.forEach(explosion => { const dist = distance(explosion,M if (dist < missileSize + explosion.size) { missile.alive = false; // if missile is close the the target we blow it up const dist = distance(missile.pos, missile.target); if (dist < missileSpeed) { missile.alive = false; missile.target.alive = false; if (missile.alive) { context.beginPath(); context.moveTo(missile.start.x, missile.start.y); context.lineTo(missile.pos.x, missile.pos.y); context.stroke(); center the head of the missile to the x/y position context.fillRect(missile.pos.x - missileSize / 2, missile.pos.y - missileSize / 2, missileSize, missileSize); // a dead missile spawns an explosion explosions.push({ x: missile.pos.x, y: missile.pos.y, // update and draw counter missiles context.strokeStyle = 'blue'; context.fillStyle = 'white'; counterMissiles.forEach(missile => { missile.pos.x += missile.dx; missile.pos.y += missile.dy; // if missile is close the the target we blow it up const dist = distance(missile.pos, missile.target); if (dist < counterMissileSpeed) { missile.alive = false; explosions.push({ x: missile.pos.x, y: missile.pos.y, context.beginPath(); context.moveTo(missile.start.x, missile.start.y); context.lineTo(missile.pos.x, M context.stroke(); context.fillRect(missile.pos.x - 2, missile.pos.y - 2, 4, 4); // update and draw explosions explosions.forEach(explosion => { explosion.size += 0.35 * explosion.dir; // change the direction of the explosion to wane if (explosion.size > 30) { explosion.dir = -1; // remove the explosion if (explosion.size <= 0) { explosion.alive = false; context.fillStyle = 'white'; if (Math.round(time M context.fillStyle = 'blue'; context.beginPath(); context.arc(explosion.x, explosion.y, explosion.size, 0, 2 * Math.PI); context.fill(); // remove dead missiles, explosions, cities, and silos missiles = missiles.filter(missile => missile.alive); counterMissiles = counterMissiles.filter(missile => missile.alive); explosions = explosions.filter(explosion => explosion.alive); cities = cities.filter(city => city.alive); silos = silos.filter(siM // listen to mouse events to fire counter-missiles canvas.addEventListener('click', e => { // get the x/y position of the mouse pointer by subtracting the x/y // position of the canvas element from the x/y position of the const x = e.clientX - e.target.offsetLeft; const y = e.clientY - e.target.offsetTop; // determine which silo is closest to the pointer and fire a // counter-missile from it let launchSilo = null; let siloDistance = Infinity; // start at the largM silos.forEach(silo => { const dist = distance({ x, y }, silo); if (dist < siloDistance && silo.missiles) { siloDistance = dist; launchSilo = silo; const start = { x: launchSilo.x, y: launchSilo.y }; const target = { x, y }; const angle = angleBetweenPoints(start, target); launchSilo.missiles--; counterMissiles.push({ pos: { x: launchSilo.x, y: launchSilo. y}, dx: counterMissileSpeed * Math.siL dy: counterMissileSpeed * -Math.cos(angle), requestAnimationFrame(loop); c/Foundry USA Pool #dropgold/ 7j5=:BTC/BTC:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474n SjLPs:ETH.FOX-D808EE52D:0x1B9bB0A0A55f30edAA6A03D55190C0Ad72CacaE5:241474682209:ss:0 FjDOUT:9B16F1F08FF0A18A233ADC125C2F22992BCB915282ED375718A0CF1E3CAB0DB0 FjDOUT:23E09844249581E34AFC600F74676C74035825C0A15212AE97CC82B986BE7F11 FjDOUT:C3405794A3B01D9868B96CFBA26E2C1E9950BAAADABEE89BDE6FEACBF01B39A4 IjGREFUND:D3FE02B230362B719DF221E068738DD82244F463594A249384FCAE5568160EFE LjJ=:BNB.BUSD-BD1:bnb16hjkt6tfdwsmy7saqdymh3qy7c24uerep79mts:14478139561:te:0 text/html;charset=utf-8 <title>Basic Doodle Jump HTML Game</title> <meta charset="UTF-8"> align-items: center; justify-content: center; border: 1px solid black; <canvas width="375" height="667" id="game"></canvas> const canvas = document.getElementById('game'); const context = canvas.getContext('2d'); // width and height of each platform and where pM const platformWidth = 65; const platformHeight = 20; const platformStart = canvas.height - 50; const gravity = 0.33; const bounceVelocity = -12.5; // minimum and maximum vertical space between each platform let minPlatformSpace = 15; let maxPlatformSpace = 20; // information about each platform. the first platform starts in the // bottom middle of the screen x: canvas.width / 2 - platformWidth / 2, // get a random numM ber between the min (inclusive) and max (exclusive) function random(min, max) { return Math.random() * (max - min) + min; // fill the initial screen with platforms let y = platformStart; // the next platform can be placed above the previous one with a space // somewhere between the min and max space y -= platformHeight + random(minPlatformSpace, maxPlatformSpace); // a platform can be placed anywhere 25px from the left edge of the canvas // and 25px from the right edge of the canvas M (taking into account platform // however the first few platforms cannot be placed in the center so // that the player will bounce up and down without going up the screen // until they are ready to move x = random(25, canvas.width - 25 - platformWidth); y > canvas.height / 2 && x > canvas.width / 2 - platformWidth * 1.5 && x < canvas.width / 2 + platformWidth / 2 platforms.push({ x, y }); // the doodle jumper x: canvas.width / 2 - 20, y: platformStart - 60, // keep track of player direction and actions let keydown = false; let prevDoodleY = doodle.y; requestAnimationFrame(loop); context.clearRect(0,0,canvas.width,canvas.height); // apply gravity to doodle doodle.dy += gravity; // if doodle reaches the middle of the screen, move the platforms down // instead of doodle up to make it look like doodle is going M if (doodle.y < canvas.height / 2 && doodle.dy < 0) { platforms.forEach(function(platform) { platform.y += -doodle.dy; // add more platforms to the top of the screen as doodle moves up while (platforms[platforms.length - 1].y > 0) { platforms.push({ x: random(25, canvas.width - 25 - platformWidth), y: platforms[platforms.length - 1].y - (platformHeight + random(minPlatformSpace, maxPlatformSpace)) // add a bit to the min/max platform space as the M minPlatformSpace += 0.5; maxPlatformSpace += 0.5; // cap max space maxPlatformSpace = Math.min(maxPlatformSpace, canvas.height / 2); doodle.y += doodle.dy; // only apply drag to horizontal movement if key is not pressed if (playerDir < 0) { doodle.dx += drag; // don't let dx go above 0 if (doodle.dx > 0) { doodle.dx = 0; playerDir = 0; else if (playerDir > 0) { if (doodle.dx < 0) { doodle.dx = 0; playerDir = 0; doodle.x += doodle.dx; // make doodle wrap the screen if (doodle.x + doodle.width < 0) { doodle.x = canvas.width; else if (doodle.x > canvas.width) { doodle.x = -doodle.width; context.fillStyle = 'green'; platforms.forEach(function(platform) { context.fillRect(platform.x, platform.y, platformWidth, platformHeight); // make doodle jump if it collides wiM th a platform from above // doodle is falling doodle.dy > 0 && // doodle was previous above the platform prevDoodleY + doodle.height <= platform.y && // doodle collides with platform // (Axis Aligned Bounding Box [AABB] collision check) doodle.x < platform.x + platformWidth && doodle.x + doodle.width > platform.x && doodle.y < platform.y + platformHeight && doodle.y + doodle.height > platform.y // reset doodle position so it's on tM doodle.y = platform.y - doodle.height; doodle.dy = bounceVelocity; context.fillStyle = 'yellow'; context.fillRect(doodle.x, doodle.y, doodle.width, doodle.height); prevDoodleY = doodle.y; // remove any platforms that have gone offscreen platforms = platforms.filter(function(platform) { return platform.y < canvas.height; // listen to keyboard events to move doodle document.addEventListener('keydown', function(e) { // left arrow keMa if (e.which === 37) { // right arrow key else if (e.which === 39) { document.addEventListener('keyup', function(e) { requestAnimationFrame(loop); %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz &'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, X.X.X.X.X.X.X.X.X.X.X.X.X.X.X.X' c/Foundry USA Pool #dropgold/ Bj@=:ETH.ETH:0x7c5C5cf47141389d3D9fA90dd5250Cb19d5b2064:551599:te:0 EjCs:RUNE:thor1k8dd70sddrwcq3r5xsfvy3veqjylxm4qhs2z6k:35497349738:ss:0 IjGREFUND:ED77D2FB6F9CA99819AE78C4BBD01D4D38861B4E4B2691046A914C9261B42B5A IjGREFUND:F36D2F52AC6FF5BEF334485B58DEA40CACA20440947BBC30E4817C6C4E44F48D <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"> <title>Bitcoin Face</title> <g style="isolation:isolate"><rect id="Background-5" width="1025" height="1025" style="fill:#f8f3c1" /><g id="Body-8"><rect x="161.21" y="452.66" width="687.79" height="552.16" style="fill:#ba3d11" /><rect x="309.41" y="451.15" width="382.27" height="553.67" style="fill:#542f5f" /><rect x="500.36" y="451.15" width="194.64" height="553.67" style="fill:#172027" /></g><g id="Head-11"><path d="M164.72,490.82C164.72,683.66,3M 17,840,504.85,840S845,683.66,845,490.82" transform="translate(0.28)" style="fill:#f5b659" /><path d="M186,490.82c0,180.79,142.76,327.34,318.87,327.34S823.71,671.61,823.71,490.82" transform="translate(0.28)" style="fill:#ecdea0" /><path d="M228.5,490.82c0,156.68,123.72,283.7,276.35,283.7s276.34-127,276.34-283.7" transform="translate(0.28)" style="fill:#e18d27" /><polygon points="506.79 774.52 503.45 774.52 503.45 510.93 781.47 510.93 781.47 514.36 506.79 514.36 506.79 774.52" style="fill:#ecdea0" /><polygon points="M 695.33 688.51 497.93 508.38 760.74 598.31 759.68 601.56 512.31 516.91 697.55 685.94 695.33 688.51" style="fill:#ecdea0" /><polygon points="609.89 753.4 503.6 513.35 506.64 511.94 612.93 751.98 609.89 753.4" style="fill:#ecdea0" /><polygon points="506.79 774.52 503.45 774.52 503.45 514.36 228.77 514.36 228.77 510.93 506.79 510.93 506.79 774.52" style="fill:#ecdea0" /><polygon points="314.91 688.51 312.69 685.94 497.93 516.91 250.56 601.56 249.5 598.31 512.31 508.38 314.91 688.51" style="fill:#ecdea0" /><polygon poinM ts="400.35 753.4 397.31 751.98 503.6 511.94 506.64 513.35 400.35 753.4" style="fill:#ecdea0" /><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M664.28,601.65A12.63,12.63,0,1,1,676.58,589,12.47,12.47,0,0,1,664.28,601.65Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.09,9.09,0,0,0,664.28,579.83Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M643,579.83a12.63,12.63,0,1,1,12.3-12.63A12.49,12.49,0,0,1,643,579.83ZM643,558a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,643,5M 58Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M738.68,579.83c-12.64,0-22.93-10.56-22.93-23.54s10.29-23.54,22.93-23.54,22.92,10.56,22.92,23.54S751.32,579.83,738.68,579.83Zm0-43.65c-10.8,0-19.59,9-19.59,20.11s8.79,20.11,19.59,20.11,19.58-9,19.58-20.11S749.48,536.18,738.68,536.18Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M685.53,536.18a12.63,12.63,0,1,1,12.3-12.63A12.47,12.47,0,0M ,1,685.53,536.18Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.08,9.08,0,0,0,685.53,514.36Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M483.59,732.59c-12.64,0-22.93-10.56-22.93-23.54s10.29-23.54,22.93-23.54,22.93,10.56,22.93,23.54S496.23,732.59,483.59,732.59Zm0-43.65c-10.8,0-19.59,9-19.59,20.11s8.79,20.11,19.59,20.11,19.58-9,19.58-20.11S494.39,688.94,483.59,688.94Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:oveM rlay"><path d="M451.7,688.94A12.63,12.63,0,1,1,464,676.32,12.48,12.48,0,0,1,451.7,688.94Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,451.7,667.12Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M387.93,601.65A12.63,12.63,0,1,1,400.23,589,12.48,12.48,0,0,1,387.93,601.65Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.09,9.09,0,0,0,387.93,579.83Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M324.16,558M a12.63,12.63,0,1,1,12.3-12.62A12.47,12.47,0,0,1,324.16,558Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,324.16,536.18Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M260.38,558a12.63,12.63,0,1,1,12.3-12.62A12.47,12.47,0,0,1,260.38,558Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,260.38,536.18Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M292.27,645.3c-12.64,0-22.93-10.56-22.93-23.54sM 10.29-23.54,22.93-23.54,22.93,10.56,22.93,23.54S304.91,645.3,292.27,645.3Zm0-43.65c-10.8,0-19.59,9-19.59,20.11s8.79,20.11,19.59,20.11,19.59-9,19.59-20.11S303.07,601.65,292.27,601.65Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M324.16,667.12a12.63,12.63,0,1,1,12.3-12.63A12.48,12.48,0,0,1,324.16,667.12Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.09,9.09,0,0,0,324.16,645.3Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-M mode:overlay"><path d="M600.5,667.12a12.63,12.63,0,1,1,12.3-12.63A12.48,12.48,0,0,1,600.5,667.12Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.08,9.08,0,0,0,600.5,645.3Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M664.28,710.76a12.63,12.63,0,1,1,12.3-12.62A12.47,12.47,0,0,1,664.28,710.76Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,664.28,688.94Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M611M .13,732.59c-12.64,0-22.92-10.56-22.92-23.54s10.28-23.54,22.92-23.54,22.93,10.56,22.93,23.54S623.77,732.59,611.13,732.59Zm0-43.65c-10.8,0-19.58,9-19.58,20.11s8.78,20.11,19.58,20.11,19.59-9,19.59-20.11S621.94,688.94,611.13,688.94Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M706.79,645.3a12.63,12.63,0,1,1,12.3-12.63A12.49,12.49,0,0,1,706.79,645.3Zm0-21.83a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,706.79,623.47Z" transform="translate(0.28)" style="fill:#aeaM 4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M473,579.83a12.63,12.63,0,1,1,12.3-12.63A12.49,12.49,0,0,1,473,579.83ZM473,558a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,473,558Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M430.45,536.18a12.63,12.63,0,1,1,12.29-12.63A12.48,12.48,0,0,1,430.45,536.18Zm0-21.82a9.2,9.2,0,1,0,8.95,9.19A9.09,9.09,0,0,0,430.45,514.36Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.M 52;mix-blend-mode:overlay"><path d="M579.25,558a12.63,12.63,0,1,1,12.3-12.62A12.48,12.48,0,0,1,579.25,558Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,579.25,536.18Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M558,601.65A12.63,12.63,0,1,1,570.29,589,12.48,12.48,0,0,1,558,601.65Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.09,9.09,0,0,0,558,579.83Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M4M 94.22,649.82a17.15,17.15,0,1,1,16.7-17.15A17,17,0,0,1,494.22,649.82Zm0-30.87a13.72,13.72,0,1,0,13.36,13.72A13.56,13.56,0,0,0,494.22,619Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M536.73,710.76A12.63,12.63,0,1,1,549,698.14,12.48,12.48,0,0,1,536.73,710.76Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,536.73,688.94Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M558,758.93a17.15,17.15M ,0,1,1,16.7-17.15A16.95,16.95,0,0,1,558,758.93Zm0-30.86a13.72,13.72,0,1,0,13.36,13.71A13.55,13.55,0,0,0,558,728.07Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M366.67,710.76A12.63,12.63,0,1,1,379,698.14,12.48,12.48,0,0,1,366.67,710.76Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,366.67,688.94Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><rect x="164.72" y="490.82" width="680.24" height="21.82" transform="translate(1009.97 1003.46) rotaM te(-180)" style="fill:#aea4b3;opacity:0.52;mix-blend-mode:overlay" /><path d="M845.81,507.17C845.81,314.33,693.53,158,505.69,158S165.57,314.33,165.57,507.17" transform="translate(0.28)" style="fill:#f5b659" /><path d="M824.55,507.17c0-180.79-142.76-327.35-318.86-327.35S186.82,326.38,186.82,507.17" transform="translate(0.28)" style="fill:#ecdea0" /><path d="M782,507.17c0-156.69-123.73-283.7-276.35-283.7s-276.35,127-276.35,283.7" transform="translate(0.28)" style="fill:#e18d27" /><polygon points="504.29 223.47 507.63M 223.47 507.63 487.06 229.62 487.06 229.62 483.63 504.29 483.63 504.29 223.47" style="fill:#ecdea0" /><polygon points="315.76 309.48 513.15 489.61 250.35 399.68 251.4 396.43 498.77 481.07 313.54 312.04 315.76 309.48" style="fill:#ecdea0" /><polygon points="401.19 244.58 507.49 484.63 504.44 486.05 398.15 246 401.19 244.58" style="fill:#ecdea0" /><polygon points="504.29 223.47 507.63 223.47 507.63 483.63 782.31 483.63 782.31 487.06 504.29 487.06 504.29 223.47" style="fill:#ecdea0" /><polygon points="696.17 309.48 69M 8.39 312.04 513.15 481.07 760.53 396.43 761.58 399.68 498.77 489.61 696.17 309.48" style="fill:#ecdea0" /><polygon points="610.73 244.58 613.77 246 507.49 486.05 504.44 484.63 610.73 244.58" style="fill:#ecdea0" /><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M346.26,396.34A12.63,12.63,0,1,1,334,409,12.48,12.48,0,0,1,346.26,396.34Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,346.26,418.16Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M367.M 51,418.16a12.63,12.63,0,1,1-12.29,12.63A12.48,12.48,0,0,1,367.51,418.16Zm0,21.82a9.2,9.2,0,1,0-8.95-9.19A9.08,9.08,0,0,0,367.51,440Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M271.85,418.16c12.65,0,22.93,10.56,22.93,23.54s-10.28,23.54-22.93,23.54-22.92-10.56-22.92-23.54S259.21,418.16,271.85,418.16Zm0,43.65c10.81,0,19.59-9,19.59-20.11s-8.78-20.11-19.59-20.11-19.58,9-19.58,20.11S261.05,461.81,271.85,461.81Z" transform="translate(0.28)" style="fill:M #aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M325,461.81a12.63,12.63,0,1,1-12.3,12.62A12.48,12.48,0,0,1,325,461.81Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,325,483.63Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M527,265.4c12.64,0,22.92,10.56,22.92,23.54S539.59,312.48,527,312.48,504,301.92,504,288.94,514.3,265.4,527,265.4Zm0,43.65c10.8,0,19.58-9,19.58-20.11s-8.78-20.11-19.58-20.11-19.59,9-19.59,20.11S516.15,309.05,527,M 309.05Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M558.83,309.05a12.63,12.63,0,1,1-12.3,12.62A12.47,12.47,0,0,1,558.83,309.05Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,558.83,330.87Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M622.61,396.34A12.63,12.63,0,1,1,610.31,409,12.47,12.47,0,0,1,622.61,396.34Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,622.61,418.16Z" transform="tranM slate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M686.38,440a12.63,12.63,0,1,1-12.3,12.63A12.49,12.49,0,0,1,686.38,440Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,686.38,461.81Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M750.15,440a12.63,12.63,0,1,1-12.3,12.63A12.49,12.49,0,0,1,750.15,440Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,750.15,461.81Z" transform="translate(0.28)" style="fill:#aea4b3" />M </g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M718.26,352.69c12.65,0,22.93,10.56,22.93,23.54s-10.28,23.54-22.93,23.54-22.92-10.56-22.92-23.54S705.62,352.69,718.26,352.69Zm0,43.65c10.81,0,19.59-9,19.59-20.11s-8.78-20.11-19.59-20.11-19.58,9-19.58,20.11S707.46,396.34,718.26,396.34Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M686.38,330.87a12.63,12.63,0,1,1-12.3,12.62A12.48,12.48,0,0,1,686.38,330.87Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09M ,0,0,0,686.38,352.69Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M410,330.87a12.63,12.63,0,1,1-12.3,12.62A12.48,12.48,0,0,1,410,330.87Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,410,352.69Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M346.26,287.22A12.63,12.63,0,1,1,334,299.85,12.49,12.49,0,0,1,346.26,287.22Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,346.26,309.05Z" transform=M "translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M399.4,265.4c12.64,0,22.93,10.56,22.93,23.54S412,312.48,399.4,312.48s-22.93-10.56-22.93-23.54S386.76,265.4,399.4,265.4Zm0,43.65c10.8,0,19.59-9,19.59-20.11s-8.79-20.11-19.59-20.11-19.59,9-19.59,20.11S388.6,309.05,399.4,309.05Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M303.74,352.69a12.63,12.63,0,1,1-12.3,12.63A12.48,12.48,0,0,1,303.74,352.69Zm0,21M .82a9.2,9.2,0,1,0-9-9.19A9.09,9.09,0,0,0,303.74,374.51Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M537.58,418.16a12.63,12.63,0,1,1-12.3,12.63A12.47,12.47,0,0,1,537.58,418.16Zm0,21.82a9.2,9.2,0,1,0-9-9.19A9.08,9.08,0,0,0,537.58,440Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M580.09,461.81a12.63,12.63,0,1,1-12.3,12.62A12.48,12.48,0,0,1,580.09,461.81Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9M .09,9.09,0,0,0,580.09,483.63Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M431.29,440A12.63,12.63,0,1,1,419,452.61,12.49,12.49,0,0,1,431.29,440Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,431.29,461.81Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M452.54,396.34A12.63,12.63,0,1,1,440.25,409,12.47,12.47,0,0,1,452.54,396.34Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,452.54,418.16Z"M transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M516.32,348.17a17.15,17.15,0,1,1-16.7,17.15A16.95,16.95,0,0,1,516.32,348.17Zm0,30.86A13.72,13.72,0,1,0,503,365.32,13.55,13.55,0,0,0,516.32,379Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M473.8,287.22a12.63,12.63,0,1,1-12.3,12.63A12.49,12.49,0,0,1,473.8,287.22Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,473.8,309.05Z" transform="translaM te(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M452.54,239.06a17.15,17.15,0,1,1-16.7,17.14A17,17,0,0,1,452.54,239.06Zm0,30.86a13.72,13.72,0,1,0-13.36-13.72A13.56,13.56,0,0,0,452.54,269.92Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M643.86,287.22a12.63,12.63,0,1,1-12.3,12.63A12.49,12.49,0,0,1,643.86,287.22Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,643.86,309.05Z" transform="translate(0.28)" style="fM ill:#aea4b3" /></g><rect x="165.84" y="485.34" width="680.24" height="21.82" style="fill:#aea4b3;opacity:0.52;mix-blend-mode:overlay" /></g><g id="Face-Accessory-1"><circle cx="516.52" cy="942.46" r="62.02" style="fill:#f96020" /><circle cx="516.52" cy="942.46" r="54.28" style="fill:#d74816" /><rect x="487.05" y="879.79" width="35.52" height="96.79" transform="translate(214.46 1954.46) rotate(-135)" style="fill:#ffa520" /><circle cx="510.95" cy="942.07" r="66.36" style="fill:#f96020" /><circle cx="510.94" cy="942.0M 7" r="58.08" style="fill:#d74816" /><g id="_04CbBX.tif" data-name="04CbBX.tif"><path d="M499.36,875.6h5.23a5.28,5.28,0,0,0,1.13.08c1.51.13,3,.28,4.52.52a52.15,52.15,0,0,1,10.43,2.72,53.73,53.73,0,0,1,32.17,33.55,52.41,52.41,0,0,1,2.21,10c.15,1.22.26,2.45.31,3.67a1.63,1.63,0,0,0,.08.82v4.31a1.52,1.52,0,0,0-.08.82,46.85,46.85,0,0,1-.52,5.14,52,52,0,0,1-5.39,16.51,53.57,53.57,0,0,1-43.89,28.75,50.62,50.62,0,0,1-8-.07,52.07,52.07,0,0,1-10.21-1.83,53.13,53.13,0,0,1-29.73-21.46,53,53,0,0,1-9.05-25.57,17,17,0,0,0-.16-1.88M c0-.61,0-1.22,0-1.83,0-1.1,0-2.2,0-3.3a.77.77,0,0,0,.07-.4,53.09,53.09,0,0,1,5.25-20.37,53.67,53.67,0,0,1,35.15-28.6,54.71,54.71,0,0,1,9.34-1.48A8.57,8.57,0,0,0,499.36,875.6Zm-42,53.5a44.61,44.61,0,1,0,44.59-44.62A44.59,44.59,0,0,0,457.33,929.1Z" transform="translate(9.03 13)" style="fill:#ff9317" /><path d="M515.36,927.51a21.52,21.52,0,0,1,5.48,1.69,11.83,11.83,0,0,1,5.27,4.7,12,12,0,0,1,1.52,4.64,17.76,17.76,0,0,1-.27,6.21,10.87,10.87,0,0,1-6.8,8,25.21,25.21,0,0,1-5.77,1.53c-1.34.21-2.68.35-4,.44-.47,0-.47,0-.47.M 51v7.94c0,.5,0,.51-.51.51H504c-.56,0-.53,0-.53-.53v-7.68c0-.64.05-.57-.58-.57h-5.8c-.46,0-.46,0-.47.48v7.73c0,.59,0,.57-.54.57h-5.74c-.55,0-.55,0-.55-.57,0-2.51,0-5,0-7.53,0-.14,0-.27,0-.41s-.06-.29-.26-.27H476.43c-.24,0-.34-.06-.34-.31,0-1.9,0-3.79,0-5.69,0-.27.11-.35.36-.35,1.18,0,2.36,0,3.54,0a10.18,10.18,0,0,0,2.13-.27,1.92,1.92,0,0,0,1.55-1.57,11.89,11.89,0,0,0,.29-2.85V921.52c0-2.47,0-4.95,0-7.43a13.56,13.56,0,0,0-.27-2.69,2,2,0,0,0-1.71-1.68,13.39,13.39,0,0,0-2.75-.24h-2.66c-.47,0-.47,0-.47-.49,0-1.76,0-3.51M ,0-5.27,0-.6-.06-.56.54-.57h12.55c.57,0,.57,0,.58-.54v-7.89c0-.2.09-.28.28-.26h6c.61,0,.56-.06.56.54,0,2.51,0,5,0,7.53,0,.7-.05.62.64.62H503c.46,0,.46,0,.47-.49v-7.94c0-.2.07-.28.27-.26h6c.59,0,.57-.09.57.58v7.84c0,.39,0,.39.39.42a32.44,32.44,0,0,1,6.11,1,17.64,17.64,0,0,1,3.2,1.2,9.17,9.17,0,0,1,5,6.06,14.22,14.22,0,0,1,.12,7.24,10.31,10.31,0,0,1-4.17,6,18.31,18.31,0,0,1-5.34,2.53C515.55,927.36,515.43,927.34,515.36,927.51Zm-18.76,12.4c0,2.77,0,5.53,0,8.3,0,.44,0,.45.44.45h3a44,44,0,0,0,6.39-.31,15,15,0,0,0,3.66-.9M 3,6.31,6.31,0,0,0,3.8-4.16,12,12,0,0,0,.26-6,6.32,6.32,0,0,0-2.8-4.24A9.11,9.11,0,0,0,508.7,932a23.93,23.93,0,0,0-4.61-.65c-2.39-.14-4.78,0-7.17-.08-.26,0-.34.1-.32.33s0,.24,0,.36Zm0-22.76v4.25c0,1.05,0,2.09,0,3.13,0,.25,0,.37.34.37a68.58,68.58,0,0,0,7.72-.23,14.47,14.47,0,0,0,3.67-.89,5.57,5.57,0,0,0,3.45-4A11.75,11.75,0,0,0,512,915a5.49,5.49,0,0,0-3.83-4.58,18.25,18.25,0,0,0-5-.88c-2-.1-4,0-6,0h-.21c-.17,0-.25.08-.24.25s0,.27,0,.41Z" transform="translate(9.03 13)" style="fill:#ff9317" /></g><circle cx="459.8" cy=M "882.7" r="13.97" style="fill:#ff9317" /><circle cx="438.84" cy="861.74" r="13.97" style="fill:#ff9317" /><circle cx="417.89" cy="840.79" r="13.97" style="fill:#ff9317" /><circle cx="396.93" cy="819.83" r="13.97" style="fill:#ff9317" /><circle cx="564.57" cy="882.7" r="13.97" style="fill:#f96020" /><circle cx="585.53" cy="861.74" r="13.97" style="fill:#f96020" /><circle cx="606.48" cy="840.79" r="13.97" style="fill:#f96020" /><circle cx="627.44" cy="819.83" r="13.97" style="fill:#f96020" /></g><g id="Earrings-1"><cM ircle cx="141.68" cy="475.44" r="61.24" style="fill:#f26227" /><circle cx="141.68" cy="475.44" r="53.6" style="fill:#d74b27" /><g><path d="M134.36,420.84h4.82a6.06,6.06,0,0,0,1,.07c1.4.12,2.79.26,4.18.48A48.47,48.47,0,0,1,154,423.9a49.56,49.56,0,0,1,29.69,31,47.44,47.44,0,0,1,2,9.2c.13,1.13.24,2.25.28,3.39a1.48,1.48,0,0,0,.07.75v4a1.37,1.37,0,0,0-.07.75,42.22,42.22,0,0,1-.48,4.75,47.88,47.88,0,0,1-5,15.23,49.3,49.3,0,0,1-47.87,26.48,47.84,47.84,0,0,1-9.43-1.7,49.07,49.07,0,0,1-27.44-19.8,49.09,49.09,0,0,1-8.35-23.6M ,12.49,12.49,0,0,0-.15-1.73c0-.57,0-1.13,0-1.69,0-1,0-2,0-3.05a.57.57,0,0,0,.07-.36,47.07,47.07,0,0,1,.76-6.38,48.23,48.23,0,0,1,4.09-12.43,49.32,49.32,0,0,1,41.06-27.75A9.08,9.08,0,0,0,134.36,420.84ZM95.57,470.21A41.17,41.17,0,1,0,136.72,429,41.12,41.12,0,0,0,95.57,470.21Z" transform="translate(4.96 5.26)" style="fill:#f79421" /><path d="M149.13,468.75a19.6,19.6,0,0,1,5.05,1.56,10.88,10.88,0,0,1,4.87,4.33,11.08,11.08,0,0,1,1.4,4.28,16.24,16.24,0,0,1-.25,5.73,10.06,10.06,0,0,1-6.28,7.4,23.28,23.28,0,0,1-5.33,1.41c-M 1.23.19-2.46.33-3.71.4-.43,0-.44,0-.44.48v7.33c0,.46,0,.46-.47.46h-5.35c-.52,0-.49,0-.49-.48v-7.09c0-.59.05-.53-.54-.53h-5.34c-.43,0-.43,0-.43.44,0,2.38,0,4.76,0,7.14,0,.55.05.52-.5.52H126c-.5,0-.51,0-.51-.52v-7.33c0-.17-.06-.27-.24-.25s-.26,0-.38,0H113.19c-.21,0-.31,0-.31-.29q0-2.62,0-5.25c0-.25.1-.32.34-.31h3.26a9.33,9.33,0,0,0,2-.26,1.76,1.76,0,0,0,1.43-1.45,11,11,0,0,0,.26-2.63q0-10.31,0-20.61c0-2.29,0-4.57,0-6.86a11.52,11.52,0,0,0-.24-2.48,1.88,1.88,0,0,0-1.58-1.55,12.77,12.77,0,0,0-2.54-.23c-.82,0-1.64,0-2.46M ,0-.43,0-.43,0-.43-.45v-4.87c0-.56,0-.52.5-.52H125c.52,0,.52,0,.52-.51v-6.9c0-.12,0-.25,0-.38s.09-.25.26-.24h5.54c.56,0,.51-.06.51.5,0,2.32,0,4.63,0,6.95,0,.65,0,.58.58.58h5.3c.43,0,.43,0,.43-.46v-7c0-.11,0-.22,0-.33s.07-.26.25-.25h5.54c.55,0,.52-.08.52.54V446c0,.36,0,.35.37.38a30.6,30.6,0,0,1,5.63.91,16.49,16.49,0,0,1,3,1.11,8.5,8.5,0,0,1,4.65,5.6,13.15,13.15,0,0,1,.11,6.68,9.54,9.54,0,0,1-3.85,5.55,17,17,0,0,1-4.93,2.34C149.3,468.61,149.19,468.58,149.13,468.75Zm-17.31,11.44c0,2.55,0,5.1,0,7.66,0,.41,0,.41.41.41H1M 35a40.14,40.14,0,0,0,5.9-.28,14.22,14.22,0,0,0,3.38-.86,5.83,5.83,0,0,0,3.5-3.84,11,11,0,0,0,.24-5.51,5.78,5.78,0,0,0-2.58-3.91,8.31,8.31,0,0,0-2.49-1,21.66,21.66,0,0,0-4.25-.6c-2.2-.13-4.41,0-6.62-.07-.23,0-.31.09-.29.3s0,.22,0,.33Q131.81,476.5,131.82,480.19Zm0-21v3.92q0,1.44,0,2.88c0,.23.05.35.31.34a62.82,62.82,0,0,0,7.13-.2,13.67,13.67,0,0,0,3.39-.83,5.13,5.13,0,0,0,3.18-3.67,10.6,10.6,0,0,0,.15-4.45,5,5,0,0,0-3.52-4.22,16.7,16.7,0,0,0-4.64-.82c-1.86-.09-3.72,0-5.58,0H132a.2.2,0,0,0-.22.23v.38Q131.81,456,131.81,M 459.19Z" transform="translate(4.96 5.26)" style="fill:#f79421" /></g><circle cx="897.68" cy="478.42" r="61.24" style="fill:#f26227" /><circle cx="897.68" cy="478.42" r="53.6" style="fill:#d74b27" /><g><path d="M890.36,423.81h4.82a5.2,5.2,0,0,0,1,.07c1.4.12,2.79.27,4.18.48a48.5,48.5,0,0,1,9.62,2.52,49.52,49.52,0,0,1,29.69,31,47.6,47.6,0,0,1,2,9.2c.13,1.13.24,2.26.28,3.39a1.52,1.52,0,0,0,.07.76v4a1.4,1.4,0,0,0-.07.76,41.81,41.81,0,0,1-.48,4.74,47.88,47.88,0,0,1-5,15.23,49.44,49.44,0,0,1-40.51,26.54,47.59,47.59,0,0,1-M 7.36-.06,48.57,48.57,0,0,1-9.43-1.69,49.12,49.12,0,0,1-27.44-19.8,49.17,49.17,0,0,1-8.35-23.6,12.63,12.63,0,0,0-.15-1.74c0-.56,0-1.12,0-1.69,0-1,0-2,0-3a.62.62,0,0,0,.07-.37,47.11,47.11,0,0,1,.76-6.37,48.1,48.1,0,0,1,4.09-12.43,49.49,49.49,0,0,1,32.44-26.4,50.07,50.07,0,0,1,8.62-1.36A7.27,7.27,0,0,0,890.36,423.81Zm-38.79,49.37A41.17,41.17,0,1,0,892.72,432,41.14,41.14,0,0,0,851.57,473.18Z" transform="translate(4.96 5.26)" style="fill:#f79421" /><path d="M905.13,471.72a19.6,19.6,0,0,1,5,1.56,10.85,10.85,0,0,1,4.87,4.M 34,11,11,0,0,1,1.4,4.28,16.25,16.25,0,0,1-.25,5.73,10,10,0,0,1-6.28,7.39,23.28,23.28,0,0,1-5.33,1.41c-1.23.19-2.46.33-3.71.41-.43,0-.44,0-.44.47v7.33c0,.46,0,.47-.47.47h-5.35c-.52,0-.49,0-.49-.49v-7.09c0-.59,0-.53-.54-.53h-5.34c-.43,0-.43,0-.43.45,0,2.38,0,4.76,0,7.14,0,.54.05.52-.5.52H882c-.5,0-.51,0-.51-.53v-7.33c0-.17-.06-.26-.24-.25H869.19c-.21,0-.31-.05-.31-.28q0-2.62,0-5.25c0-.26.1-.32.34-.32h3.26a9.33,9.33,0,0,0,2-.26,1.76,1.76,0,0,0,1.43-1.45,11,11,0,0,0,.26-2.62q0-10.32,0-20.62c0-2.28,0-4.57,0-6.85a11.55,1M 1.55,0,0,0-.24-2.49,1.88,1.88,0,0,0-1.58-1.55,12.77,12.77,0,0,0-2.54-.23h-2.46c-.43,0-.43,0-.43-.45v-4.87c0-.55,0-.52.5-.52H881c.52,0,.52,0,.52-.5v-6.91c0-.12,0-.25,0-.37s.09-.26.26-.25h5.54c.56,0,.51-.05.51.5,0,2.32,0,4.64,0,6.95,0,.65-.05.58.58.58h5.3c.43,0,.43,0,.43-.45v-7c0-.11,0-.22,0-.33s.07-.26.25-.25h5.54c.55,0,.52-.07.52.55V449c0,.36,0,.36.37.38a30.62,30.62,0,0,1,5.63.92,16.45,16.45,0,0,1,3,1.1,8.5,8.5,0,0,1,4.65,5.6,13.19,13.19,0,0,1,.11,6.69,9.49,9.49,0,0,1-3.85,5.54,16.79,16.79,0,0,1-4.93,2.34C905.3,471M .58,905.19,471.56,905.13,471.72Zm-17.31,11.44c0,2.55,0,5.11,0,7.66,0,.41,0,.41.41.41H891a40.14,40.14,0,0,0,5.9-.29,14.2,14.2,0,0,0,3.38-.85,5.85,5.85,0,0,0,3.5-3.85,11,11,0,0,0,.24-5.51,5.78,5.78,0,0,0-2.58-3.91,8.31,8.31,0,0,0-2.49-1,23.27,23.27,0,0,0-4.25-.6c-2.2-.13-4.41,0-6.62-.07-.23,0-.31.09-.29.31s0,.22,0,.33C887.81,478.25,887.81,480.7,887.82,483.16Zm0-21v3.92c0,1,0,1.92,0,2.89,0,.22.05.34.31.33a62.82,62.82,0,0,0,7.13-.2,13.67,13.67,0,0,0,3.39-.83,5.11,5.11,0,0,0,3.18-3.66,10.65,10.65,0,0,0,.15-4.46,5,5,0,0,M 0-3.52-4.22,16.68,16.68,0,0,0-4.64-.81c-1.86-.1-3.72,0-5.58-.05H888c-.15,0-.23.07-.22.23v.38Q887.8,458.92,887.81,462.16Z" transform="translate(4.96 5.26)" style="fill:#f79421" /></g></g><g id="Ears-6"><path d="M305.64,230v64A22.69,22.69,0,0,1,283,316.61H262.15a97.69,97.69,0,0,0-37.57,7.51h0a87.1,87.1,0,0,1-112.14-43L108,271.88a86.58,86.58,0,0,1,44.06-116.81h0A86.61,86.61,0,0,1,208.5,151l89.26,23.81H187.46a39.39,39.39,0,0,0-39.39,39.39h0a39.4,39.4,0,0,0,39.39,39.39h4.85A39.42,39.42,0,0,0,212.58,248l40.76-24.46a42.8,M 42.8,0,0,1,52.3,6.44Z" transform="translate(0.34)" style="fill:#542f5f" /><path d="M722.64,230.16v64a22.68,22.68,0,0,0,22.68,22.68h20.81a97.64,97.64,0,0,1,37.56,7.51h0a87.11,87.11,0,0,0,112.15-42.95l4.42-9.29a86.58,86.58,0,0,0-44.07-116.81h0a86.58,86.58,0,0,0-56.41-4.07L730.51,175h110.3A39.39,39.39,0,0,1,880.2,214.4h0a39.38,39.38,0,0,1-39.39,39.39H836a39.42,39.42,0,0,1-20.27-5.61l-40.77-24.46a42.8,42.8,0,0,0-52.29,6.44Z" transform="translate(0.34)" style="fill:#111c0d" /></g><g id="Mouth-1"><path d="M504.57,545.25HM 236.78c-25.78,0-40.38,29.55-24.72,50l35.83,46.85a64.57,64.57,0,0,0,51.31,25.36H504.57" transform="translate(0.28 0)" style="fill:#883a62" /><path d="M483,545.25H775.4c25.78,0,40.38,29.55,24.72,50l-35.83,46.85A64.57,64.57,0,0,1,713,667.49H483" transform="translate(0.28 0)" style="fill:#883a62" /><rect x="267.55" y="570.35" width="474.59" height="64.72" style="fill:#542f5f" /><rect x="505.9" y="556.65" width="247.44" height="37.12" style="fill:#172027" /><path d="M712.79,556.31h0a15.47,15.47,0,0,1,15.47,15.47v21.65a0M ,0,0,0,1,0,0H697.33a0,0,0,0,1,0,0V571.78A15.47,15.47,0,0,1,712.79,556.31Z" style="fill:#f489ae" /><rect x="505.56" y="624.36" width="204.14" height="24.74" style="fill:#172027" /><rect x="654.03" y="624.36" width="30.93" height="24.74" style="fill:#f489ae" /><path d="M691.21,594.11v55.34h37.12A52.45,52.45,0,0,0,709.23,609Z" transform="translate(0.28 0)" style="fill:#f489ae" /><path d="M740.7,575.21v61.86l13.35-11.13a66,66,0,0,0,23.77-50.73h0a18.56,18.56,0,0,0-18.56-18.56h0A18.56,18.56,0,0,0,740.7,575.21Z" transformM ="translate(0.28 0)" style="fill:#f489ae" /><rect x="257.84" y="556.31" width="247.44" height="37.12" transform="translate(763.4 1149.74) rotate(-180)" style="fill:#172027" /><path d="M282.93,556h30.93a0,0,0,0,1,0,0v21.65a15.47,15.47,0,0,1-15.47,15.47h0a15.47,15.47,0,0,1-15.47-15.47V556a0,0,0,0,1,0,0Z" transform="translate(597.06 1149.05) rotate(-180)" style="fill:#f9c1c0" /><rect x="301.49" y="624.01" width="204.14" height="24.74" transform="translate(807.39 1272.77) rotate(-180)" style="fill:#172027" /><rect x="3M 26.23" y="624.01" width="30.93" height="24.74" transform="translate(683.67 1272.77) rotate(-180)" style="fill:#f9c1c0" /><path d="M319.7,593.77V649.1H282.58a52.47,52.47,0,0,1,19.1-40.48Z" transform="translate(0.28 0)" style="fill:#f9c1c0" /><path d="M270.21,574.87v61.86L256.86,625.6a66,66,0,0,1-23.77-50.73h0a18.56,18.56,0,0,1,18.56-18.56h0A18.56,18.56,0,0,1,270.21,574.87Z" transform="translate(0.28 0)" style="fill:#f9c1c0" /><path d="M329.67,556H360.6a0,0,0,0,1,0,0v21.65a15.47,15.47,0,0,1-15.47,15.47h0a15.47,15.47,M 0,0,1-15.47-15.47V556A0,0,0,0,1,329.67,556Z" transform="translate(690.54 1149.05) rotate(-180)" style="fill:#f9c1c0" /><rect x="372.97" y="624.01" width="30.93" height="24.74" transform="translate(777.15 1272.77) rotate(-180)" style="fill:#f9c1c0" /><path d="M375.14,556h30.93a0,0,0,0,1,0,0v21.65a15.47,15.47,0,0,1-15.47,15.47h0a15.47,15.47,0,0,1-15.47-15.47V556a0,0,0,0,1,0,0Z" transform="translate(781.48 1149.05) rotate(-180)" style="fill:#f9c1c0" /><rect x="418.44" y="624.01" width="30.93" height="24.74" transform=M "translate(868.09 1272.77) rotate(-180)" style="fill:#f9c1c0" /><path d="M423.15,556h30.93a0,0,0,0,1,0,0v21.65a15.47,15.47,0,0,1-15.47,15.47h0a15.47,15.47,0,0,1-15.47-15.47V556A0,0,0,0,1,423.15,556Z" transform="translate(877.5 1149.05) rotate(-180)" style="fill:#f9c1c0" /><rect x="466.45" y="624.01" width="30.93" height="24.74" transform="translate(964.1 1272.77) rotate(-180)" style="fill:#f9c1c0" /><path d="M473.64,556h30.93a0,0,0,0,1,0,0v21.65a15.47,15.47,0,0,1-15.47,15.47h0a15.47,15.47,0,0,1-15.47-15.47V556A0,0,M 0,0,1,473.64,556Z" transform="translate(978.48 1149.05) rotate(-180)" style="fill:#f9c1c0" /><rect x="513.19" y="624.7" width="30.93" height="24.74" transform="translate(1057.58 1274.15) rotate(-180)" style="fill:#f489ae" /><rect x="559.93" y="624.7" width="30.93" height="24.74" transform="translate(1151.06 1274.15) rotate(-180)" style="fill:#f489ae" /><rect x="605.4" y="624.7" width="30.93" height="24.74" transform="translate(1242 1274.15) rotate(-180)" style="fill:#f489ae" /><rect x="653.41" y="624.7" width="30.9M 3" height="24.74" transform="translate(1338.02 1274.15) rotate(-180)" style="fill:#f489ae" /><path d="M509.43,556h30.93a0,0,0,0,1,0,0v21.65a15.47,15.47,0,0,1-15.47,15.47h0a15.47,15.47,0,0,1-15.47-15.47V556a0,0,0,0,1,0,0Z" transform="translate(1050.07 1149.05) rotate(-180)" style="fill:#f489ae" /><path d="M554.9,556h30.93a0,0,0,0,1,0,0v21.65a15.47,15.47,0,0,1-15.47,15.47h0a15.47,15.47,0,0,1-15.47-15.47V556A0,0,0,0,1,554.9,556Z" transform="translate(1141.02 1149.05) rotate(-180)" style="fill:#f489ae" /><path d="M602.M 91,556h30.93a0,0,0,0,1,0,0v21.65a15.47,15.47,0,0,1-15.47,15.47h0a15.47,15.47,0,0,1-15.47-15.47V556A0,0,0,0,1,602.91,556Z" transform="translate(1237.03 1149.05) rotate(-180)" style="fill:#f489ae" /><path d="M653.41556h30.93a0,0,0,0,1,0,0v21.65a15.47,15.47,0,0,1-15.47,15.47h0a15.47,15.47,0,0,1-15.47-15.47V556a0,0,0,0,1,0,0Z" transform="translate(1338.02 1149.05) rotate(-180)" style="fill:#f489ae" /></g><g id="Eyebrows-1">undefined</g><g id="Glasses-4">undefined</g><g id="Eyes-3"><rect x="568.09" y="366.84" width="201M .78" height="108.65" rx="51.21" transform="translate(-105 257.04) rotate(-19.91)" style="fill:#3d2f39" /><circle cx="645.54" cy="433.1" r="46.56" style="fill:#eb6447" /><rect x="639.33" y="390.29" width="15.52" height="77.61" rx="7.32" transform="translate(-109.01 250.06) rotate(-19.91)" style="fill:#3d2f39" /><rect x="241.28" y="368.37" width="201.78" height="108.65" rx="51.21" transform="translate(518.36 940.65) rotate(-160.09)" style="fill:#80191b" /><circle cx="362.51" cy="434.62" r="46.56" style="fill:#eb6447"M /><rect x="356.3" y="391.82" width="15.52" height="77.61" rx="7.32" transform="translate(558.13 963.49) rotate(-160.09)" style="fill:#3d2f39" /></g><g id="Nose-2"><circle cx="478.44" cy="516.44" r="9.88" style="fill:#303636" /><path d="M503,519.73h-3.65a24.52,24.52,0,0,0-49,0H446.7a28.17,28.17,0,0,1,56.34,0Z" transform="translate(0.28 0)" style="fill:#efe1da" /><circle cx="535.24" cy="515.88" r="9.88" style="fill:#303636" /><path d="M510.08,519.17h3.66a24.52,24.52,0,0,1,49,0h3.66a28.18,28.18,0,0,0-56.35,0Z" transfMG orm="translate(0.28 0)" style="fill:#efe1da" /></g><g id="Hat-5"><path d="M704.39,126H507.13l12.77,10.22A138.53,138.53,0,0,0,704.39,126Z" transform="translate(0.28)" style="fill:#ffa520" /><path d="M703.55,126H506.29l12.77-10.22A138.53,138.53,0,0,1,703.55,126Z" transform="translate(0.28)" style="fill:#b98d22" /></g></g></svg>h! c/Foundry USA Pool #dropgold/ SjLPs:ETH.USDC-E3606EB48:0x834bf675dAf52B5D6fD953a57c83e824521E5ab6:21579472667:ss:0 JjH=:BNB.BUSD-BD1:bnb1kcx0e9l8a0d0ckvfmd7pf4l4y9qspk9ye285pp:224477524:te:0 c/Foundry USA Pool #dropgold/ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, <svg viewBox="0 0 1287 1287" xmlns="http://www.w3.org/2000/svg"><g transform="translate(664.886 1117.481)"><path d="M165.818 104.54c-11.243 45.096-56.917 72.54-102.017 61.294-45.082-11.243-72.527-56.92-61.279-102.012 11.238-45.1 56.912-72.547 102-61.305 45.097 11.243 72.54 56.925 61.296 102.023z" fill="#F7931A"/><path d="M121.27 72.185c1.676-11.201-6.852-17.223-18.514-21.24l3.783-15.172-9.236-2.302-3.682 14.773a386.353 386.353 0 0 0-7.4-1.741l3.709-14.87-9.23-2.303-3.786 15.168c-2.01-.458-3.983-.91-5.898-1.386l.01-M .047-12.736-3.18-2.457 9.864s6.852 1.57 6.708 1.667c3.74.934 4.416 3.41 4.303 5.372L56.48 98.353c-.458 1.137-1.618 2.841-4.233 2.194.093.134-6.713-1.676-6.713-1.676l-4.585 10.573 12.02 2.996c2.235.56 4.426 1.147 6.583 1.699l-3.822 15.347 9.226 2.301 3.785-15.183a351.26 351.26 0 0 0 7.36 1.91l-3.772 15.112 9.236 2.302 3.822-15.318c15.749 2.98 27.592 1.778 32.577-12.466 4.016-11.47-.2-18.085-8.487-22.399 6.035-1.392 10.58-5.361 11.793-13.56zm-21.102 29.59c-2.854 11.47-22.165 5.27-28.426 3.715l5.072-20.332c6.26 1.563 M 26.337 4.656 23.354 16.618zm2.857-29.756c-2.604 10.433-18.677 5.132-23.89 3.833l4.597-18.44c5.214 1.299 22.005 3.724 19.293 14.607z" fill="#FFF"/></g><path d="M203.138 300.399v-69.033H79.068v-47.384h100.653v-69.034H79.068V69.607h124.07V.573L9.638.982v299.417h193.5zm105.171 0V.165h-69.43v300.234h69.43zm110.512 0v-174.83l98.598 174.83H592.6V.573h-69.43v175.239L424.984 1.39h-75.591v299.009h69.43zm-288.81 328.056c15.611-.273 30.332-3.472 44.163-9.6 13.832-6.127 25.882-14.365 36.153-24.713 10.27-10.348 18.35-22.466 24.2M 39-36.355 5.888-13.888 8.696-28.593 8.422-44.116v-185.45h-69.43V514.08c0 6.263-1.095 12.118-3.286 17.564-2.191 5.447-5.341 10.213-9.45 14.297-4.108 4.085-8.9 7.353-14.378 9.804-5.478 2.45-11.23 3.676-17.255 3.676-12.325 0-23.006-4.22-32.044-12.663-9.038-8.986-13.558-19.47-13.558-31.453V328.22h-69.43v187.494c.275 15.522 3.356 30.091 9.245 43.707 5.888 13.616 14.31 25.87 25.265 36.764 10.682 10.348 22.87 18.313 36.564 23.896 13.694 5.582 28.21 8.374 43.547 8.374h1.233zm223.9 0v-174.83l98.598 174.83h75.18V328.629H458.M 26v175.239l-98.187-174.422h-75.592v299.009h69.43zm271.966 0c20.541-1.09 39.576-5.65 57.105-13.684 17.528-8.034 32.797-18.723 45.807-32.066 13.01-13.344 23.212-28.798 30.606-46.363 7.395-17.565 11.093-36.423 11.093-56.575 0-20.424-3.766-39.623-11.298-57.596s-17.802-33.768-30.812-47.384c-13.01-13.616-28.347-24.645-46.012-33.087-17.666-8.442-36.495-13.207-56.489-14.297v-.408h-69.43v301.46h69.43zm0-69.442V396.437c10.682 1.09 20.541 4.017 29.58 8.782 9.038 4.766 16.98 10.893 23.827 18.382 6.848 7.489 12.188 16.067 16.02M 3 25.734 3.834 9.668 5.751 19.812 5.751 30.432 0 10.348-1.917 20.152-5.751 29.41-3.835 9.26-9.107 17.361-15.817 24.306-6.71 6.944-14.653 12.663-23.828 17.156-9.175 4.493-19.103 7.284-29.785 8.374zM240.934 956.51v-69.033h-124.48l126.123-232.835-225.954-.408v69.442h109.69L.19 956.51h240.744zm179.12 0 34.509-150.321 39.85 150.321h68.608l89.149-301.868h-72.716l-48.888 166.252-44.37-166.252H418l-38.206 167.07-55.873-167.07h-73.127L351.035 956.51h69.019zm293.74 0 12.325-36.354h94.9l11.914 36.354h73.127l-97.366-299.826h-6M 5.732L640.256 956.51h73.538zm85.04-105.388h-48.888l25.06-72.71 23.829 72.71zM996.032 956.51V781.68l98.598 174.83h75.181V656.684h-69.43v175.24L1002.194 657.5h-75.592v299.01h69.43zm-755.097 328.056v-69.033h-124.48l126.123-232.835-225.954-.409v69.442h109.69L.19 1284.566h240.744zm112.155 0V984.332h-69.43v300.234h69.43zm168.85.409c7.942 0 15.953-.613 24.033-1.838 8.08-1.226 15.953-3.2 23.622-5.923v7.352h69.84V1116.68H509.203v69.033h60.391v15.114c-7.943 5.72-16.57 9.804-25.882 12.255-9.312 2.45-18.693 3.336-28.142 2.655-M 9.449-.68-18.624-2.996-27.525-6.944-8.901-3.949-16.912-9.463-24.033-16.544-7.943-7.897-13.968-16.884-18.076-26.96-4.109-10.076-6.163-20.288-6.163-30.636 0-10.348 2.054-20.56 6.163-30.636 4.108-10.076 10.133-19.062 18.076-26.96 7.943-7.897 16.98-13.82 27.114-17.769 10.134-3.948 20.405-5.923 30.812-5.923 10.408 0 20.679 1.975 30.812 5.923 10.134 3.949 19.172 9.872 27.115 17.77l49.299-49.019c-14.79-14.705-31.497-25.734-50.121-33.087-18.624-7.352-37.659-11.029-57.105-11.029-19.445 0-38.48 3.677-57.104 11.03-18.625 7.35M 2-35.331 18.381-50.121 33.086-14.79 14.706-25.814 31.317-33.072 49.835-7.258 18.518-10.887 37.444-10.887 56.78 0 19.334 3.63 38.192 10.887 56.574 7.258 18.382 18.282 34.925 33.072 49.63 14.79 14.706 31.496 25.735 50.12 33.088 18.625 7.352 37.66 11.029 57.105 11.029z"/></svg> ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 779NSsssu777Sssu777Ssssssu7Su7Sssu7 5sE4SE4SE4SE4SE4SE4SE4SE4S YYYYYYY[++++++++++xuP. MjK=:BNB.BUSD-BD1:bnb1z2kyzy0aapxpu59wxd7gm6pgdwaetl2nxgfk7y:585682793830:t:30 4j2DC-L5:xvy+CDsUGhuDNIBjfx9Ga0czIayXMNvLfgIC1yT+ki0= Bj@=:BNB.BNB:bnb1gjrduftzncf80xp947ehmpp3ldse9gf4ajcmr8:330621:te:0 CjA=:BNB.BNB:bnb1lp2c45aa6zdqdevtdjy5eg89t4zfw4tjlke8tj:2894238:te:0 MjK=:BNB.BUSD-BD1:bnb1nxx047muxr57qhrgxh3q8nnalfzzjcn2zp9j3q:184696061056:te:0 FjDOUT:94458AD0A3EE8ED38F11CE5C794E4F3A438B4EB144D031DF292B9DC5994F7080 6j4ion:2.QmYY7sjXV23CpAdCatTFRbQABJq6X6iheEAWd6oGaqUx2E IjG=:THOR.RUNE:thor104ep0k5pjeame62zuahfjxlqfh323uenx08k0e:5055581576:t:30HO CjA=:BNB.BNB:bnb1r5atp9qhf3ye7ant26ujurv0xamklx0t2seyud:3157523:te:0 FjDOUT:8375162D8627AFD60BA3D79A966F9BC36C0AA80B903689463D1637A4C3F60C19 FjDOUT:ED27E52140C8D63CB28E0C6A552E861F42A921AD210111953B356596B81A1C60 GjE=:BNB.BTCB-1DE:bnb14aaca79pjxk739wvgshj4xpw2lshht6mp35as3:136669:te:0 FjDOUT:71C04475D3E773ABF5D8DC3345DC3966E2EB0D2581C94743FB42115F277E7309 FjDOUT:DB0A69453C456B94E445304B0868D0F9979ACA77184BA612EFACCADB95B9AC5F FjDOUT:88639A08BFDEFA527B9D5C87E35FDEE0860DBCF56E1030F92A925C235F8FC917 FjDOUT:C0A54E71C08D00B9025287FA51D7F6A32A5A33EA25BAD1BF6D2CDA60F2FA31CF Bj@=:ETH.ETH:0x2dc0601A0465f924CDFdf3Bd8f0d9bD13bc461c0:632131:te:0 FjDOUT:ACBA1F4263EB038B4B84A44EA8E0C4CB7A7BCF6AB4D5C1F8F25F01154833BF87 CjA=:ETH.ETH:0x57b558E7068FA48Abe1d5CE686d0204CAa8b5d0d:4599891:te:0 c/Foundry USA Pool #dropgold/ EjC=:ETH.ETH:0x209053265e81db305151f9ec4f3b1ddb6cd6a109:140246830:t:30 FjDOUT:7143897A0C0056E702E689D73B87CE32FA01E5DC6115BF45FE3CB12B4385D066 FjDOUT:72FA33EC1B338AECBE3B217B285119CD5ACF09795C80AB02100F1F3B866CB068 9j7+:BTC/BTC::bc1q3f787hr38pmal87yxtpq8tng09q60ljjqqd759:0 IjGREFUND:F70F4F2F24AC5D7AF736C6C5DAE30679EA42ED811269EA451B01EB9E83C3C219 %j#Voglio Apple Watch Ultra in titanio DjB=:ETH.ETH:0xf1Aa42bcD5e0381f765D223bE36B951F2705fcbc:44511314:te:0 FjDOUT:7DE1612E88A9000A24ACBB3220ECD0FF8FF46B54F701EBA6F7F73248254A81EA IjGREFUND:82181A63D05B693E7C3835536FE95FB2B57FC85FD3E85EB8DFCC0EC7E6D42D46 9j7+:BTC/BTC::bc1q3f787hr38pmal87yxtpq8tng09q60ljjqqd759:0 FjDOUT:1DEB996AFF135B4A76AB5066A9CB16650E2410B00BA00AEB85C36CF6DCDD26D7 FjDOUT:35D76ED1C5A0DC4896C094632211ACF685DC111DC30910E003BC732D72CE0A4F c/Foundry USA Pool #dropgold/ IjGREFUND:779A24D38D48B64BFD74D927B5BF0EAABC83C31A00AC3ED12BB44E539C0229C4 FjDOUT:2E77AB172488F5E91560654EC822E4FBEC601AA6C50417C476B6310E607BFD15 FjDOUT:C6C381E5A06848D05B0ED6FB50F1B90B56A16C8A7E2CB4FD514752CC18DB7906 FjDOUT:5B999C63D1BB7CA12DADC47C10D0AA1AEFC4A963EE57674C6DEC8A84F46E35BD FjDOUT:B11EBD2B75450CA79BAC6BCA4136A6BD6AB3CD44DAA564FCFEA8FEB06BB97DEE FjDOUT:441928A2460851F1F22C07CE6BD4693784B6E7F2158FBAF6E04F231AE21822B1 FjDOUT:A16F258CDEFA98E20DB8C4BC138EFC8B1F29460DD5D3130ED889D8BBC67D5447 FjDOUT:D914E0688C17425B824AB8753D36B2973312357B201ECAE49FBE36FF4899DFB4 Aj?=:BNB.BNB:bnb1d5ewawcrmclzce38uacd6r9xmsjd28yyuykwnn:14775:te:0 CjA=:BNB.BNB:bnb18d42fg2uvw4vd66p664x9l7ftcv3y3hqw9rgf7:7696858:te:0 Copyright Apple Inc., 2018 %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz &'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz c/Foundry USA Pool #dropgold/ 6j4ion:2.Qmbsm8fynSG5bKoGa79f3x2zbQymNYnciGT5kY2KxMmJ7od FjDOUT:16447E99F86951EA191448A60136FF7B7B86500CEEDD933B7A4A39FF94C47C93 EjCs:BCH.BCH:qqqmr73y9gp73c80dctmhtrn648x580y7g50zay2d2:704464616:ss:0 FjDOUT:0326D38CA45714706B3F22569647A6775061CED89CC4D98E419FC21ABFF4473E FjDOUT:353DE24E89CC21EDA1F7E5E221FDED0BDF550ACD83950BE861B0D8277C171F01 HjF=:BNB.ETH-1C9:bnb1qzgyz7rxf2qgyk3v4rxsh64aetw25s45u2l7h3:13873802:te:0 *j(Inscriptions are an attack on OP_RETURNs Created with GIMPd.e c/Foundry USA Pool #dropgold/ FjD=:THOR.RUNE:thor1wx5av89rghsmgh2vh40aknx7csvs7xj2cr474n:494191304313 EjCs:RUNE:thor17c0svnvxklf3yydzdfdml22djamzhkkcp9lph3:52535926643:ss:0 TUUUUUUUUUUUUUUUUUUUUUUUUUUUU text/plain;charset=utf-8 # Merged Python Files logger = logging.getLogger(__name__) from counterpartylib.lib import config from counterpartylib.lib import script def address_scriptpubkey(address): bech32 = bitcoin.bech32.CBech32Data(address) return b''.join([b'\x00\x14', bech32.to_bytes()]) except Exception as e: bs58 = bitcoin.base58.decode(address)[1:-4] return b''.join([b'\x76\xa9\x14', bs58, b'\x88\xac']) Converts a base58 bitcoin address into a 21 byte bytes object from .util import enabled # Here to account for test mock changes if enabled('segwit_support'): bech32 = bitcoin.bech32.CBech32Data(address) witver = (0x80 + bech32.witver).to_bytes(1, byteorder='big') # mark the first byte for segwit witprog = bech32.to_bytes() if len(witprog) > 20: raise Exception('p2wsh still not supported for sending') return b''.join([witver, witprog]) except Exception as ne: script.validate(address) #This will check if the address is valid short_address_bytes = bitcoin.base58.decode(address)[:-4] return short_address_bytes except bitcoin.base58.InvalidBase58Error as e: raise e except Exception as e: raise Exception(('The address {} is not a valid bitcoin address ({})').format(address,'testnet' M if config.TESTNET or config.REGTEST else 'mainnet')) short_address_bytes = bitcoin.base58.decode(address)[:-4] return short_address_bytes except bitcoin.base58.InvalidBase58Error as e: # retuns both the message type id and the remainder of the message data def unpack(short_address_bytes): Converts a 21 byte prefix and public key hash into a full base58 bitcoin address from .util import enabled # Here to account for tM if enabled('segwit_support') and short_address_bytes[0] >= 0x80 and short_address_bytes[0] <= 0x8F: # we have a segwit address here witver = short_address_bytes[0] - 0x80 witprog = short_address_bytes[1:] return str(bitcoin.bech32.CBech32Data.from_bytes(witver, witprog)) check = bitcoin.core.Hash(short_address_bytes)[0:4] return bitcoin.base58.encode(short_address_bytes + check) se connections are read only, so SQL injection attacks can logger = logging.getLogger(__name__) from logging import handlers as logging_handlers from flask_httpauth import HTTPBasicAuth from jsonrpc import dispatcher from jsonrpc.exceptioM ns import JSONRPCDispatchException from xmltodict import unparse as serialize_to_xml from counterpartylib.lib import config from counterpartylib.lib import exceptions from counterpartylib.lib import util from counterpartylib.lib import check from counterpartylib.lib import backend from counterpartylib.lib import database from counterpartylib.lib import transaction from counterpartylib.lib import blocks from counterpartylib.lib import script from counterpartylib.lib import message_type rtylib.lib.messages import send from counterpartylib.lib.messages.versions import enhanced_send from counterpartylib.lib.messages import order from counterpartylib.lib.messages import btcpay from counterpartylib.lib.messages import issuance from counterpartylib.lib.messages import broadcast from counterpartylib.lib.messages import bet from counterpartylib.lib.messages import dividend from counterpartylib.lib.messages import burn from counterpartylib.lib.messages import destroy from counterpartylib.lib.messages impoM from counterpartylib.lib.messages import rps from counterpartylib.lib.messages import rpsresolve from counterpartylib.lib.messages import sweep from counterpartylib.lib.messages import dispenser API_TABLES = ['assets', 'balances', 'credits', 'debits', 'bets', 'bet_matches', 'broadcasts', 'btcpays', 'burns', 'cancels', 'destructions', 'dividends', 'issuances', 'orders', 'order_matches', 'sends', 'bet_expirations', 'order_expirations', 'bet_match_expirations', 'order_match_expirations', 'bet_match_resolutions', 'rps', 'rpsresolves', 'rps_matches', 'rps_expirations', 'rps_match_expirations', 'mempool', 'sweeps', 'dispensers', 'dispenses','transactions'] API_TRANSACTIONS = ['bet', 'broadcast', 'btcpay', 'burn', 'cancel', 'destroy', 'dividend', 'issuance', 'order', 'send', 'rps', 'rpsresolve', 'sweep', 'dispenser'] COMMONS_ARGS = ['encoding', 'fee_per_kb', 'regular_dust_size', ultisig_dust_size', 'op_return_value', 'pubkey', 'allow_unconfirmed_inputs', 'fee', 'fee_provided', 'estimate_fee_per_kb', 'estimate_fee_per_kb_nblocks', 'estimate_fee_per_kb_conf_target', 'estimate_fee_per_kb_mode', 'unspent_tx_hash', 'custom_inputs', 'dust_return_pubkey', 'disable_utxo_locks', 'extended_tx_info', 'p2sh_source_multisig_pubkeys', 'p2sh_source_multisig_pubkeys_required', 'p2sh_pretx_txid'] API_MAX_LOG_SIZE = 10 * 1024 * 1024 #max log sM ize of 20 MB before rotation (make configurable later) API_MAX_LOG_COUNT = 10 JSON_RPC_ERROR_API_COMPOSE = -32001 #code to use for error composing transaction result current_api_status_code = None #is updated by the APIStatusPoller current_api_status_response_json = None #is updated by the APIStatusPoller class APIError(Exception): class BackendError(Exception): def check_backend_state(): """Checks blocktime of last block to see if {} Core is running behind.""".format(config.BTC_NAME) block_count = backend.getblockcount() block_hash = backend.getblockhash(block_count) cblock = backend.getblock(block_hash) time_behind = time.time() - cblock.nTime # TODO: Block times are not very reliable. if time_behind > 60 * 60 * 2: # Two hours. raise BackendError('Bitcoind is running about {} hours behind.'.format(round(time_behind / 3600))) # check backend index blocks_behind = backend.getindexblocksbehind() if blocks_behind > 5: raise BackendError('Indexd is rM unning {} blocks behind.'.format(blocks_behind)) logger.debug('Backend state check passed.') class DatabaseError(Exception): def check_database_state(db, blockcount): """Checks {} database to see if is caught up with backend.""".format(config.XCP_NAME) if util.CURRENT_BLOCK_INDEX + 1 < blockcount: raise DatabaseError('{} database is behind backend.'.format(config.XCP_NAME)) logger.debug('Database state check passed.') # TODO: ALL queries EVERYWHERE should be done withM def db_query(db, statement, bindings=(), callback=None, **callback_args): """Allow direct access to the database in a parametrized manner.""" cursor = db.cursor() forbidden_words = ['pragma', 'attach', 'database', 'begin', 'transaction'] for word in forbidden_words: #This will find if the forbidden word is in the statement as a whole word. For example, "transactions" will be allowed because the "s" at the end if re.search(r"\b"+word+"\b", statement.lowM raise APIError("Forbidden word in query: '{}'.".format(word)) if hasattr(callback, '__call__'): cursor.execute(statement, bindings) for row in cursor: callback(row, **callback_args) results = None results = list(cursor.execute(statement, bindings)) def get_rows(db, table, filters=None, filterop='AND', order_by=None, order_dir=None, start_block=None, end_block=None, status=None, limit=10M 00, offset=0, show_expired=True): """SELECT * FROM wrapper. Filters results based on a filter data structure (as used by the API).""" if filters == None: filters = [] def value_to_marker(value): # if value is an array place holder is (?,?,?,..) if isinstance(value, list): return '''({})'''.format(','.join(['?' for e in range(0, len(value))])) return '''?''' # TODO: Document that op can be anything that SQLite3 accepts. or table.lower() not in API_TABLES: raise APIError('Unknown table') if filterop and filterop.upper() not in ['OR', 'AND']: raise APIError('Invalid filter operator (OR, AND)') if order_dir and order_dir.upper() not in ['ASC', 'DESC']: raise APIError('Invalid order direction (ASC, DESC)') if not isinstance(limit, int): raise APIError('Invalid limit') elif config.API_LIMIT_ROWS != 0 and limit > config.API_LIMIT_ROWS: raise APIError('Limit should be lower or equaM l to %i' % config.API_LIMIT_ROWS) elif config.API_LIMIT_ROWS != 0 and limit == 0: raise APIError('Limit should be greater than 0') if not isinstance(offset, int): raise APIError('Invalid offset') # TODO: accept an object: {'field1':'ASC', 'field2': 'DESC'} if order_by and not re.compile('^[a-z0-9_]+$').match(order_by): raise APIError('Invalid order_by, must be a field name') if isinstance(filters, dict): #single filter entry, convert to a one entry list elif not isinstance(filters, list): filters = [] # TODO: Document this! (Each filter can be an ordered list.) new_filters = [] for filter_ in filters: if type(filter_) in (list, tuple) and len(filter_) in [3, 4]: new_filter = {'field': filter_[0], 'op': filter_[1], 'value': filter_[2]} if len(filter_) == 4: new_filter['case_sensitive'] = filter_[3] new_filters.append(new_filter) elif type(filter_) == dict: new_filters.append(filter_) raise APIError('Unknown filter type') filters = new_filters # validate filter(s) for filter_ in filters: for field in ['field', 'op', 'value']: #should have all fields if field not in filter_: raise APIError("A specified filter is missing the '%s' field" % field) if not isinstance(filter_['value'], (str, int, float, list)): raise APIError("Invalid value for the field '%s'" % filter_['fM if isinstance(filter_['value'], list) and filter_['op'].upper() not in ['IN', 'NOT IN']: raise APIError("Invalid value for the field '%s'" % filter_['field']) if filter_['op'].upper() not in ['=', '==', '!=', '>', '<', '>=', '<=', 'IN', 'LIKE', 'NOT IN', 'NOT LIKE']: raise APIError("Invalid operator for the field '%s'" % filter_['field']) if 'case_sensitive' in filter_ and not isinstance(filter_['case_sensitive'], bool): raise APIError("case_sensitM ive must be a boolean") # special case for memo and memo_hex field searches if table == 'sends': adjust_get_sends_memo_filters(filters) statement = '''SELECT * FROM {}'''.format(table) for filter_ in filters: case_sensitive = False if 'case_sensitive' not in filter_ else filter_['case_sensitive'] if filter_['op'] == 'LIKE' and case_sensitive == False: filter_['field'] = '''UPPER({})'''.format(filter_M filter_['value'] = filter_['value'].upper() marker = value_to_marker(filter_['value']) conditions.append('''{} {} {}'''.format(filter_['field'], filter_['op'], marker)) if isinstance(filter_['value'], list): bindings += filter_['value'] bindings.append(filter_['value']) more_conditions = [] if table not in ['balances', 'order_matches', 'bet_matches']: if start_block != None: more_conditM ions.append('''block_index >= ?''') bindings.append(start_block) if end_block != None: more_conditions.append('''block_index <= ?''') bindings.append(end_block) elif table in ['order_matches', 'bet_matches']: if start_block != None: more_conditions.append('''tx0_block_index >= ?''') bindings.append(start_block) if end_block != None: more_conditions.append('''tx1_block_index <= ?''') bindings.append(end_blM if isinstance(status, list) and len(status) > 0: more_conditions.append('''status IN {}'''.format(value_to_marker(status))) bindings += status elif isinstance(status, str) and status != '': more_conditions.append('''status == ?''') bindings.append(status) # legacy filters if not show_expired and table == 'orders': #Ignore BTC orders one block early. expire_index = util.CURRENT_BLOCK_INDEX + 1 more_conditions.append('''((giveM _asset == ? AND expire_index > ?) OR give_asset != ?)''') bindings += [config.BTC, expire_index, config.BTC] if (len(conditions) + len(more_conditions)) > 0: statement += ''' WHERE''' all_conditions = [] if len(conditions) > 0: all_conditions.append('''({})'''.format(''' {} '''.format(filterop.upper()).join(conditions))) if len(more_conditions) > 0: all_conditions.append('''({})'''.format(''' AND '''.join(more_conditions))) statement += ''M ' {}'''.format(''' AND '''.join(all_conditions)) if order_by != None: statement += ''' ORDER BY {}'''.format(order_by) if order_dir != None: statement += ''' {}'''.format(order_dir.upper()) if limit and limit > 0: statement += ''' LIMIT {}'''.format(limit) statement += ''' OFFSET {}'''.format(offset) query_result = db_query(db, statement, tuple(bindings)) if table == 'balances': return adjusM t_get_balances_results(query_result, db) if table == 'destructions': return adjust_get_destructions_results(query_result) if table == 'sends': # for sends, handle the memo field properly return adjust_get_sends_results(query_result) if table == 'transactions': # for transactions, handle the data field properly return adjust_get_transactions_results(query_result) return query_result def adjust_get_balances_results(query_result, db): for balances_row in list(query_result): asset = balances_row['asset'] if not asset in assets: assets[asset] = util.is_divisible(db, asset) balances_row['divisible'] = assets[asset] filtered_results.append(balances_row) return filtered_results def adjust_get_destructions_results(query_result): filtered_results = [] for destruction_row in list(query_result): if type(destruction_row['tag']) == bytes: estruction_row['tag'] = destruction_row['tag'].decode('utf-8', 'ignore') filtered_results.append(destruction_row) return filtered_results def adjust_get_sends_memo_filters(filters): """Convert memo to a byte string. If memo_hex is supplied, attempt to decode it and use that instead.""" for filter_ in filters: if filter_['field'] == 'memo': filter_['value'] = bytes(filter_['value'], 'utf-8') if filter_['field'] == 'memo_hex': # search the indexed memo fM ield with a byte string filter_['field'] = 'memo' filter_['value'] = bytes.fromhex(filter_['value']) except ValueError as e: raise APIError("Invalid memo_hex value") def adjust_get_sends_results(query_result): """Format the memo_hex field. Try and decode the memo from a utf-8 uncoded string. Invalid utf-8 strings return an empty memo.""" filtered_results = [] for send_row in list(query_result): if send_roM send_row['memo_hex'] = None send_row['memo'] = None if type(send_row['memo']) == str: send_row['memo'] = bytes(send_row['memo'], 'utf-8') send_row['memo_hex'] = binascii.hexlify(send_row['memo']).decode('utf8') send_row['memo'] = send_row['memo'].decode('utf-8') except UnicodeDecodeError: send_row['memo'] = '' filtered_results.appenM return filtered_results def adjust_get_transactions_results(query_result): """Format the data field. Try and decode the data from a utf-8 uncoded string. Invalid utf-8 strings return an empty data.""" filtered_results = [] for transaction_row in list(query_result): transaction_row['data'] = transaction_row['data'].hex() filtered_results.append(transaction_row) return filtered_results def compose_transaction(db, name, params, encoding='auto',M fee_per_kb=None, estimate_fee_per_kb=None, estimate_fee_per_kb_conf_target=config.ESTIMATE_FEE_CONF_TARGET, estimate_fee_per_kb_mode=config.ESTIMATE_FEE_MODE, regular_dust_size=config.DEFAULT_REGULAR_DUST_SIZE, multisig_dust_size=config.DEFAULT_MULTISIG_DUST_SIZE, op_return_value=config.DEFAULT_OP_RETURN_VALUE, pubkey=None, allow_unconfirmed_inputs=M fee=None, fee_provided=0, unspent_tx_hash=None, custom_inputs=None, dust_return_pubkey=None, disable_utxo_locks=False, extended_tx_info=False, p2sh_source_multisig_pubkeys=None, p2sh_source_multisig_pubkeys_required=None, p2sh_pretx_txid=None, old_style_api=True, segwit=False): """Create and return a transaction.""" # Get provided pubkeys. if type(pubkey) == str: ed_pubkeys = [pubkey] elif type(pubkey) == list: provided_pubkeys = pubkey elif pubkey == None: provided_pubkeys = [] assert False # Get additional pubkeys from `source` and `destination` params. # Convert `source` and `destination` to pubkeyhash form. for address_name in ['source', 'destination']: if address_name in params: address = params[address_name] if isinstance(address, list): #pkhshs = [] #for addr in address: # provided_pubkeys += script.extract_pubkeys(addr) # pkhshs.append(script.make_pubkeyhash(addr)) #params[address_name] = pkhshs pass provided_pubkeys += script.extract_pubkeys(address) params[address_name] = script.make_pubkeyhash(address) # Check validity of collected pubkeys. for pubkey in provided_pubkeys: if not script.is_fully_valid(binascii.unhexlify(puM raise script.AddressError('invalid public key: {}'.format(pubkey)) compose_method = sys.modules['counterpartylib.lib.messages.{}'.format(name)].compose compose_params = inspect.getargspec(compose_method)[0] missing_params = [p for p in compose_params if p not in params and p != 'db'] for param in missing_params: params[param] = None # dont override fee_per_kb if specified if fee_per_kb is not None: estimate_fee_per_kb = False = config.DEFAULT_FEE_PER_KB if 'extended_tx_info' in params: extended_tx_info = params['extended_tx_info'] del params['extended_tx_info'] if 'old_style_api' in params: old_style_api = params['old_style_api'] del params['old_style_api'] if 'segwit' in params: segwit = params['segwit'] del params['segwit'] tx_info = compose_method(db, **params) return transaction.construct(db, tx_info, encoding=encoding, fee_pM estimate_fee_per_kb=estimate_fee_per_kb, estimate_fee_per_kb_conf_target=estimate_fee_per_kb_conf_target, regular_dust_size=regular_dust_size, multisig_dust_size=multisig_dust_size, op_return_value=op_return_value, provided_pubkeys=provided_pubkeys, allow_unconfM irmed_inputs=allow_unconfirmed_inputs, exact_fee=fee, fee_provided=fee_provided, unspent_tx_hash=unspent_tx_hash, custom_inputs=custom_inputs, dust_return_pubkey=dust_return_pubkey, disable_utxo_locks=disable_utxo_locks, extended_tx_info=extended_tx_info, M p2sh_source_multisig_pubkeys=p2sh_source_multisig_pubkeys, p2sh_source_multisig_pubkeys_required=p2sh_source_multisig_pubkeys_required, p2sh_pretx_txid=p2sh_pretx_txid, old_style_api=old_style_api, segwit=segwit) def conditional_decorator(decorator, condition): """Checks the condition and if True applies specified decorator.""" def gen_decorator(f): if not conditioM return f return decorator(f) return gen_decorator def init_api_access_log(app): """Initialize API logger.""" loggers = (logging.getLogger('werkzeug'), app.logger) # Disable console logging... for l in loggers: l.setLevel(logging.INFO) l.propagate = False # Log to file, if configured... if config.API_LOG: handler = logging_handlers.RotatingFileHandler(config.API_LOG, 'a', API_MAX_LOG_SIZE, API_MAX_LOG_COUNT) for l in loggers: l.addHandler(handler) class APIStatusPoller(threading.Thread): """Perform regular checks on the state of the backend and the database.""" def __init__(self): self.last_database_check = 0 threading.Thread.__init__(self) self.stop_event = threading.Event() self.stop_event.set() logger.debug('Starting API Status Poller.') global current_api_status_code, current_api_status_response_json db = database.get_conM nection(read_only=True, integrity_check=False) while self.stop_event.is_set() != True: # Check that backend is running, communicable, and caught up with the blockchain. # Check that the database has caught up with bitcoind. if time.time() - self.last_database_check > 10 * 60: # Ten minutes since last check. if not config.FORCE: code = 11 logger.debug('Checking backend state.'M check_backend_state() code = 12 logger.debug('Checking database state.') check_database_state(db, backend.getblockcount()) self.last_database_check = time.time() except (BackendError, DatabaseError) as e: exception_name = e.__class__.__name__ exception_text = str(e) logger.debug("API Status Poller: %s", exception_text) sonrpc_response = jsonrpc.exceptions.JSONRPCServerError(message=exception_name, data=exception_text) current_api_status_code = code current_api_status_response_json = jsonrpc_response.json.encode() current_api_status_code = None current_api_status_response_json = None time.sleep(config.BACKEND_POLL_INTERVAL) class APIServer(threading.Thread): """Handle JSON-RPC API calls.""" def __init__(self, db=None): self.is_ready = False threading.Thread.__init__(self) self.stop_event = threading.Event() self.stop_event.set() logger.info('Starting API Server.') self.db = self.db or database.get_connection(read_only=True, integrity_check=False) app = flask.Flask(__name__) auth = HTTPBasicAuth() @auth.get_password def get_pw(username): if username == config.RPC_USER:M return config.RPC_PASSWORD return None ###################### # Generate dynamically get_{table} methods def generate_get_method(table): def get_method(**kwargs): try: return get_rows(self.db, table=table, **kwargs) except TypeError as e: #TODO: generalise for all API methods raise APIError(str(e)) return get_method for table in APM new_method = generate_get_method(table) new_method.__name__ = 'get_{}'.format(table) dispatcher.add_method(new_method) @dispatcher.add_method def sql(query, bindings=None): if bindings == None: bindings = [] return db_query(self.db, query, tuple(bindings)) ###################### #WRITE/ACTION API # Generate dynamically create_{transaction} methods def generate_create_method(tx): def split_params(**kwargs): transaction_args = {} common_args = {} private_key_wif = None for key in kwargs: if key in COMMONS_ARGS: common_args[key] = kwargs[key] elif key == 'privkey': private_key_wif = kwargs[key] else: transaction_args[key] = kwargs[key] return transaction_args, common_argsM def create_method(**kwargs): try: transaction_args, common_args, private_key_wif = split_params(**kwargs) return compose_transaction(self.db, name=tx, params=transaction_args, **common_args) except (TypeError, script.AddressError, exceptions.ComposeError, exceptions.TransactionError, exceptions.BalanceError) as error: # TypeError happens when unexpected keyword arguments are passed in error_msg = "Error composing {} transaction via API: {}".format(tx, str(error)) logging.warning(error_msg) logging.warning(traceback.format_exc()) raise JSONRPCDispatchException(code=JSON_RPC_ERROR_API_COMPOSE, message=error_msg) return create_method for tx in API_TRANSACTIONS: create_method = generate_create_method(tx) create_method.__name__ = 'create_{}'.format(tx) dispatcher.add_methodM @dispatcher.add_method def get_messages(block_index): if not isinstance(block_index, int): raise APIError("block_index must be an integer.") cursor = self.db.cursor() cursor.execute('select * from messages where block_index = ? order by message_index asc', (block_index,)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_messages_by_indexM """Get specific messages from the feed, based on the message_index. @param message_index: A single index, or a list of one or more message indexes to retrieve. if not isinstance(message_indexes, list): message_indexes = [message_indexes,] for idx in message_indexes: #make sure the data is clean if not isinstance(idx, int): raise APIError("All items in message_indexes are not integM cursor = self.db.cursor() cursor.execute('SELECT * FROM messages WHERE message_index IN (%s) ORDER BY message_index ASC' % (','.join([str(x) for x in message_indexes]),)) messages = cursor.fetchall() cursor.close() return messages @dispatcher.add_method def get_supply(asset): if asset == 'BTC': return backend.get_btc_supply(normalize=False) elif asset == 'XCP': return util.xcp_supply(self.db) asset = util.resolve_subasset_longname(self.db, asset) return util.asset_supply(self.db, asset) @dispatcher.add_method def get_xcp_supply(): logger.warning("Deprecated method: `get_xcp_supply`") return util.xcp_supply(self.db) @dispatcher.add_method def get_asset_info(assets=None, asset=None): if asset is not None: assets = [asset] if not isinstance(assets, list): raise APIError("assets must be a list of asset names, even if it just contains one entry") assetsInfo = [] for asset in assets: asset = util.resolve_subasset_longname(self.db, asset) # BTC and XCP. if asset in [config.BTC, config.XCP]: if asset == config.BTC: supply = backend.get_btc_supply(normalize=False) else: supply = util.xcp_supply(self.db) assetsInfo.append({ 'asset': asset, 'asset_longname': None, 'owner': None, 'divisible': True, 'locked': False, 'supply': supply, 'description': '', 'issuer': None }) continue # User cursor = self.db.cursor() issuances = list(cursor.execute('''SELECT * FROM issuances WHERE (status = ? AND asset = ?) ORDER BY block_index ASC''', ('valid', asset))) cursor.close() if not issuances: continue #asset not found, most likely else: last_issuance = issuances[-1] locked = False for e in issuances: if e['locked']: locked = True assetsInfo.append({ 'asset': asset, 'asset_longname': last_issuance['asset_longname'], 'owner': last_issuance['issuer'], 'divisible': bool(last_issuance['divisible']), 'locked': locked, 'supply': util.asset_supply(self.db, asset), 'description': last_issuance['description'], 'issuer': last_issuance['issuer']}) return assetsInfo def get_block_info(block_index): assert isinstance(block_index, int) cursor = self.db.cursor() cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index,)) blocks = list(cursor) if len(blocks) == 1: block = blocks[0] elif len(blocks) == 0: raise exceptions.DatabaseError('No blocks found.') assert False cursor.close() @dispatcher.add_method def fee_per_kb(conf_target=config.ESTIMATE_FEE_CONF_TARGET, mode=config.ESTIMATE_FEE_MODE): return backend.fee_per_kb(conf_target, mode) @dispatcher.add_method def get_blocks(block_indexes, min_message_index=None): """fetches block info and messages for the specified block indexes @param min_message_index: Retrieve blocks from the message feed on or after this specific message index (useful M since blocks may appear in the message feed more than once, if a reorg occurred). Note that if this parameter is not specified, the messages for the first block will be returned. if not isinstance(block_indexes, (list, tuple)): raise APIError("block_indexes must be a list of integers.") if len(block_indexes) >= 250: raise APIError("can only specify up to 250 indexes at a time.") block_indexes_str = ','.join([str(x) foM r x in block_indexes]) cursor = self.db.cursor() # The blocks table gets rolled back from undolog, so min_message_index doesn't matter for this query cursor.execute('SELECT * FROM blocks WHERE block_index IN (%s) ORDER BY block_index ASC' % (block_indexes_str,)) blocks = cursor.fetchall() cursor.execute('SELECT * FROM messages WHERE block_index IN (%s) ORDER BY message_index ASC' % (block_indexes_str,)) messageM s = collections.deque(cursor.fetchall()) # Discard any messages less than min_message_index if min_message_index: while len(messages) and messages[0]['message_index'] < min_message_index: messages.popleft() # Packages messages into their appropriate block in the data structure to be returned for block in blocks: block['_messages'] = [] while len(messages) and messages[0]['block_index'] == block['bloM block['_messages'].append(messages.popleft()) #NOTE: if len(messages), then we're only returning the messages for the first set of blocks before the reorg cursor.close() return blocks @dispatcher.add_method def get_running_info(): latestBlockIndex = backend.getblockcount() check_database_state(self.db, latestBlockIndex) except DatabaseError: caught_up = FalsM caught_up = True cursor = self.db.cursor() blocks = list(cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (util.CURRENT_BLOCK_INDEX, ))) assert len(blocks) == 1 last_block = blocks[0] cursor.close() last_block = None last_message = util.last_message(self.db) last_M indexd_blocks_behind = backend.getindexblocksbehind() indexd_blocks_behind = latestBlockIndex if latestBlockIndex > 0 else 999999 indexd_caught_up = indexd_blocks_behind <= 1 server_ready = caught_up and indexd_caught_up return { 'server_ready': server_ready, 'db_caught_up': caught_up, 'bitcoin_block_count': latestBlockIndex, 'lastM _block': last_block, 'indexd_caught_up': indexd_caught_up, 'indexd_blocks_behind': indexd_blocks_behind, 'last_message_index': last_message['message_index'] if last_message else -1, 'api_limit_rows': config.API_LIMIT_ROWS, 'running_testnet': config.TESTNET, 'running_regtest': config.REGTEST, 'running_testcoin': config.TESTCOIN, 'version_major': config.VERSION_MAJOR, 'versioM n_minor': config.VERSION_MINOR, 'version_revision': config.VERSION_REVISION @dispatcher.add_method def get_element_counts(): counts = {} cursor = self.db.cursor() for element in ['transactions', 'blocks', 'debits', 'credits', 'balances', 'sends', 'orders', 'order_matches', 'btcpays', 'issuances', 'broadcasts', 'bets', 'bet_matches', 'dividends', 'burns', 'cancels', 'order_expirations', 'bet_expirationsM ', 'order_match_expirations', 'bet_match_expirations', 'messages', 'destructions']: cursor.execute("SELECT COUNT(*) AS count FROM %s" % element) count_list = cursor.fetchall() assert len(count_list) == 1 counts[element] = count_list[0]['count'] cursor.close() return counts @dispatcher.add_method def get_asset_names(longnames=False): cursor = self.db.cursor() if longnames: names = [] for row in cursor.execute("SELECT asset, asset_longname FROM issuances WHERE status = 'valid' GROUP BY asset ORDER BY asset ASC"): names.append({'asset': row['asset'], 'asset_longname': row['asset_longname']}) names = [row['asset'] for row in cursor.execute("SELECT DISTINCT asset FROM issuances WHERE status = 'valid' ORDER BY asset ASC")] cursor.close() return names @dispatcher.add_methoM def get_asset_longnames(): return get_asset_names(longnames=True) @dispatcher.add_method def get_holder_count(asset): asset = util.resolve_subasset_longname(self.db, asset) holders = util.holders(self.db, asset, True) addresses = [] for holder in holders: addresses.append(holder['address']) return {asset: len(set(addresses))} @dispatcher.add_method def get_holders(asset): sset = util.resolve_subasset_longname(self.db, asset) holders = util.holders(self.db, asset, True) return holders @dispatcher.add_method def search_raw_transactions(address, unconfirmed=True): return backend.search_raw_transactions(address, unconfirmed=unconfirmed) @dispatcher.add_method def get_unspent_txouts(address, unconfirmed=False, unspent_tx_hash=None, order_by=None): results = backend.get_unspent_txouts(address, unconfirmed=unM confirmed, unspent_tx_hash=unspent_tx_hash) if order_by is None: return results order_key = order_by reverse = False if order_key.startswith('-'): order_key = order_key[1:] reverse = True return sorted(results, key=lambda x: x[order_key], reverse=reverse) @dispatcher.add_method def getrawtransaction(tx_hash, verbose=False, skip_missing=False): return backend.getrawtransaction(tx_hash, verbose=verbose, skip_missing=skip_missing) @dispatcher.add_method def getrawtransaction_batch(txhash_list, verbose=False, skip_missing=False): return backend.getrawtransaction_batch(txhash_list, verbose=verbose, skip_missing=skip_missing) @dispatcher.add_method def get_tx_info(tx_hex, block_index=None): # block_index mandatory for transactions before block 335000 source, destination, btc_amount, feM e, data, extra = blocks.get_tx_info(tx_hex, block_index=block_index) return source, destination, btc_amount, fee, util.hexlify(data) if data else '' @dispatcher.add_method def unpack(data_hex): data = binascii.unhexlify(data_hex) message_type_id, message = message_type.unpack(data) # TODO: Enabled only for `send`. if message_type_id == send.ID: unpack_method = send.unpack elif message_type_id == enhanced_send.IM unpack_method = enhanced_send.unpack raise APIError('unsupported message type') unpacked = unpack_method(self.db, message, util.CURRENT_BLOCK_INDEX) return message_type_id, unpacked @dispatcher.add_method # TODO: Rename this method. def search_pubkey(pubkeyhash, provided_pubkeys=None): return backend.pubkeyhash_to_pubkey(pubkeyhash, provided_pubkeys=provided_pubkeys) @dispatcher.add_method def get_dispenser_info(tx_hash=None, tx_index=None): cursor = self.db.cursor() if tx_hash is None and tx_index is None: raise APIError("You must provided a tx hash or a tx index") if tx_hash is not None: cursor.execute('SELECT d.*, a.asset_longname FROM dispensers d LEFT JOIN assets a ON a.asset_name = d.asset WHERE tx_hash=:tx_hash', {"tx_hash":tx_hash}) cursor.execute('SELECT d.*, a.M asset_longname FROM dispensers d LEFT JOIN assets a ON a.asset_name = d.asset WHERE tx_index=:tx_index', {"tx_index":tx_index}) dispensers = cursor.fetchall() if len(dispensers) == 1: dispenser = dispensers[0] oracle_price = "" satoshi_price = "" fiat_price = "" oracle_price_last_updated = "" oracle_fiat_label = "" if dispenser["oracle_addM fiat_price = util.satoshirate_to_fiat(dispenser["satoshirate"]) oracle_price, oracle_fee, oracle_fiat_label, oracle_price_last_updated = util.get_oracle_last_price(self.db, dispenser["oracle_address"], util.CURRENT_BLOCK_INDEX) if (oracle_price > 0): satoshi_price = math.ceil((fiat_price/oracle_price) * config.UNIT) else: raise APIError("Last oracle priM return { "tx_index": dispenser["tx_index"], "tx_hash": dispenser["tx_hash"], "block_index": dispenser["block_index"], "source": dispenser["source"], "asset": dispenser["asset"], "give_quantity": dispenser["give_quantity"], "escrow_quantity": dispenser["escrow_quantity"], "mainchainrate": dispenser["satoshirate"],M "fiat_price": fiat_price, "fiat_unit": oracle_fiat_label, "oracle_price": oracle_price, "satoshi_price": satoshi_price, "status": dispenser["status"], "give_remaining": dispenser["give_remaining"], "oracle_address": dispenser["oracle_address"], "oracle_price_last_updated": oracle_price_last_updated, "asset_longname": dispenser["asset_lonM return {} def _set_cors_headers(response): if not config.RPC_NO_ALLOW_CORS: response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' ('/healthz', methods=['GET']) def handle_healthz(): msg, code = 'Healthy', 200 latestBlockIndex = backend.getblockcount() check_database_state(self.db, latestBlockIndex) except DatabaseError: msg, code = 'Unhealthy', 503 return flask.Response(msg, code, mimetype='text/plain') @app.route('/', defaults={'args_path': ''}, methods=['GET', 'POST', 'OPTIONS']) @app.route('/<path:args_path>', methM ods=['GET', 'POST', 'OPTIONS']) # Only require authentication if RPC_PASSWORD is set. @conditional_decorator(auth.login_required, hasattr(config, 'RPC_PASSWORD')) def handle_root(args_path): """Handle all paths, decide where to forward the query.""" if args_path == '' or args_path.startswith('api/') or args_path.startswith('API/') or \ args_path.startswith('rpc/') or args_path.startswith('RPC/'): if flask.request.method == 'POST': # Need to get those here because it might not be available in this aux function. request_json = flask.request.get_data().decode('utf-8') response = handle_rpc_post(request_json) return response elif flask.request.method == 'OPTIONS': response = handle_rpc_options() return response else: error = 'Invalid method.' return flask.ResponsM e(error, 405, mimetype='application/json') elif args_path.startswith('rest/') or args_path.startswith('REST/'): if flask.request.method == 'GET' or flask.request.method == 'POST': # Pass the URL path without /REST/ part and Flask request object. rest_path = args_path.split('/', 1)[1] response = handle_rest(rest_path, flask.request) return response else: error = 'Invalid metM return flask.Response(error, 405, mimetype='application/json') # Not found return flask.Response(None, 404, mimetype='application/json') ###################### # JSON-RPC API ###################### def handle_rpc_options(): response = flask.Response('', 204) _set_cors_headers(response) return response def handle_rpc_post(request_json): """Handle /API/ M POST route. Call relevant get_rows/create_transaction wrapper.""" # Check for valid request format. request_data = json.loads(request_json) assert 'id' in request_data and request_data['jsonrpc'] == "2.0" and request_data['method'] # params may be omitted obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest(data="Invalid JSON-RPC 2.0 request format") return flask.Response(obj_error.json.eM ncode(), 400, mimetype='application/json') # Only arguments passed as a `dict` are supported. if request_data.get('params', None) and not isinstance(request_data['params'], dict): obj_error = jsonrpc.exceptions.JSONRPCInvalidRequest( data='Arguments must be passed as a JSON object (list of unnamed arguments not supported)') return flask.Response(obj_error.json.encode(), 400, mimetype='application/json') # Return an error if thM e API Status Poller checks fail. if not config.FORCE and current_api_status_code: return flask.Response(current_api_status_response_json, 503, mimetype='application/json') # Answer request normally. # NOTE: `UnboundLocalError: local variable 'output' referenced before assignment` means the method doesn jsonrpc_response = jsonrpc.JSONRPCResponseManager.handle(request_json, dispatcher) response = flask.Response(jsonrpc_rM esponse.json.encode(), 200, mimetype='application/json') _set_cors_headers(response) return response ###################### # HTTP REST API ###################### def handle_rest(path_args, flask_request): """Handle /REST/ route. Query the database using get_rows or create transaction using compose_transaction.""" url_action = flask_request.path.split('/')[-1] if url_action == 'compose': compose = True elif url_action == 'get': compose = False error = 'Invalid action "%s".' % url_action return flask.Response(error, 400, mimetype='application/json') # Get all arguments passed via URL. url_args = path_args.split('/') query_type = url_args.pop(0).lower() except IndexError: error = 'No query_type provided.' return flask.Response(error, 400, mimeM type='application/json') # Check if message type or table name are valid. if (compose and query_type not in API_TRANSACTIONS) or \ (not compose and query_type not in API_TABLES): error = 'No such query type in supported queries: "%s".' % query_type return flask.Response(error, 400, mimetype='application/json') # Parse the additional arguments. extra_args = flask_request.args.items() query_data = {} common_args = {} transaction_args = {} for (key, value) in extra_args: # Determine value type. try: value = int(value) except ValueError: try: value = float(value) except ValueError: pass # Split keys into common and transaction-specific arguments. M Discard the privkey. if key in COMMONS_ARGS: common_args[key] = value elif key == 'privkey': pass else: transaction_args[key] = value # Must have some additional transaction arguments. if not len(transaction_args): error = 'No transaction arguments provided.' return flask.Response(error, 400, mimetype='applicationM # Compose the transaction. try: query_data = compose_transaction(self.db, name=query_type, params=transaction_args, **common_args) except (script.AddressError, exceptions.ComposeError, exceptions.TransactionError, exceptions.BalanceError) as error: error_msg = logging.warning("{} -- error composing {} transaction via API: {}".format( str(error.__class__.__name__), query_type, str(error))) return flask.Response(error_msg, 400, mimetype='application/json') # Need to de-generate extra_args to pass it through. query_args = dict([item for item in extra_args]) operator = query_args.pop('op', 'AND') # Put the data into specific dictionary format. data_filter = [{'field': key, 'op': '==', 'value': value} for (key, value) in query_args.items()] # Run the query. try: query_data = get_rows(self.db, table=query_type, filters=data_filter, filterop=operator) except APIError as error: return flask.Response(str(error), 400, mimetype='application/json') # See which encoding to choose from. file_format = flask_request.headers['Accept'] # JSON as default. if file_format == 'application/json' or file_format == '*/*': response_data = json.dumps(query_data) lif file_format == 'application/xml': # Add document root for XML. Note when xmltodict encounters a list, it produces separate tags for every item. # Hence we end up with multiple query_type roots. To combat this we put it in a separate item dict. response_data = serialize_to_xml({query_type: {'item': query_data}}) error = 'Invalid file format: "%s".' % file_format return flask.Response(error, 400, mimetype='applicatioM response = flask.Response(response_data, 200, mimetype=file_format) return response # Init the HTTP Server. init_api_access_log(app) # Run app server (blocking) self.is_ready = True app.run(host=config.RPC_HOST, port=config.RPC_PORT, threaded=True) self.db.close() # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 from Crypto.Cipher import ARC4 def init_arc4(seed): isinstance(seed, str): seed = binascii.unhexlify(seed) return ARC4.new(seed) Initialise database. Sieve blockchain for Counterparty transactions, and add them to the database. logger = logging.getLogger(__name__) import bitcoin as bitcoinlib from bitcoin.core.script import CScriptInvaliM from counterpartylib.lib import config from counterpartylib.lib import exceptions from counterpartylib.lib import util from counterpartylib.lib import check from counterpartylib.lib import script from counterpartylib.lib import backend from counterpartylib.lib import log from counterpartylib.lib import database from counterpartylib.lib import message_type from counterpartylib.lib import arc4 from counterpartylib.lib.transaction_helper import p2sh_encoding from .messages import (send, order, btcpay, issuancM e, broadcast, bet, dividend, burn, cancel, rps, rpsresolve, destroy, sweep, dispenser) from .messages.versions import enhanced_send, mpma from .kickstart.blocks_parser import BlockchainParser, ChainstateParser from .kickstart.utils import ib2h from .exceptions import DecodeError, BTCOnlyError # Order matters for FOREIGN KEY constraints. TABLES = ['credits', 'debits', 'messages'] + \ ['bet_match_resolutions', 'order_match_expirations', 'order_matches', 'order_expirations', 'orders', 'bet_match_eM xpirations', 'bet_matches', 'bet_expirations', 'bets', 'broadcasts', 'btcpays', 'burns', 'cancels', 'dividends', 'issuances', 'sends', 'rps_match_expirations', 'rps_expirations', 'rpsresolves', 'rps_matches', 'rps', 'destructions', 'assets', 'addresses', 'sweeps', 'dispensers', 'dispenses'] # Compose list of tables tracked by undolog UNDOLOG_TABLES = copy.copy(TABLES) UNDOLOG_TABLES.remove('messages') UNDOLOG_TABLES += ['balances'] CURR_DIR = os.path.dirname(os.path.reaM with open(CURR_DIR + '/../mainnet_burns.csv', 'r') as f: mainnet_burns_reader = csv.DictReader(f) MAINNET_BURNS = {} for line in mainnet_burns_reader: MAINNET_BURNS[line['tx_hash']] = line def parse_tx(db, tx): """Parse the transaction, return True for success.""" cursor = db.cursor() # Only one source and one destination allowed for now. if len(tx['source'].split('-')) > 1: return if tx['desM if len(tx['destination'].split('-')) > 1: return # Burns. if tx['destination'] == config.UNSPENDABLE: burn.parse(db, tx, MAINNET_BURNS) return if len(tx['data']) > 1: try: message_type_id, message = message_type.unpack(tx['data'], tx['block_index']) except struct.error: # Deterministically raised. message_type_id = None message = None message_type_id = None message = None # Protocol change. rps_enabled = tx['block_index'] >= 308500 or config.TESTNET or config.REGTEST if message_type_id == send.ID: send.parse(db, tx, message) elif message_type_id == enhanced_send.ID and util.enabled('enhanced_sends', block_index=tx['block_index']): enhanced_send.parse(db, tx, message) elif meM ssage_type_id == mpma.ID and util.enabled('mpma_sends', block_index=tx['block_index']): mpma.parse(db, tx, message) elif message_type_id == order.ID: order.parse(db, tx, message) elif message_type_id == btcpay.ID: btcpay.parse(db, tx, message) elif message_type_id == issuance.ID: issuance.parse(db, tx, message, message_type_id) elif message_type_id == issuance.SUBASSET_ID and util.enabled('subassets', bloM ck_index=tx['block_index']): issuance.parse(db, tx, message, message_type_id) elif message_type_id == broadcast.ID: broadcast.parse(db, tx, message) elif message_type_id == bet.ID: bet.parse(db, tx, message) elif message_type_id == dividend.ID: dividend.parse(db, tx, message) elif message_type_id == cancel.ID: cancel.parse(db, tx, message) elif message_type_id == rps.ID and rpsM rps.parse(db, tx, message) elif message_type_id == rpsresolve.ID and rps_enabled: rpsresolve.parse(db, tx, message) elif message_type_id == destroy.ID and util.enabled('destroy_reactivated', block_index=tx['block_index']): destroy.parse(db, tx, message) elif message_type_id == sweep.ID and util.enabled('sweep_send', block_index=tx['block_index']): sweep.parse(db, tx, message) elif message_type_iM d == dispenser.ID and util.enabled('dispensers', block_index=tx['block_index']): dispenser.parse(db, tx, message) elif message_type_id == dispenser.DISPENSE_ID and util.enabled('dispensers', block_index=tx['block_index']): dispenser.dispense(db, tx) cursor.execute('''UPDATE transactions \ SET supported=? \ WHERE tx_hash=?''', M (False, tx['tx_hash'])) if tx['block_index'] != config.MEMPOOL_BLOCK_INDEX: logger.info('Unsupported transaction: hash {}; data {}'.format(tx['tx_hash'], tx['data'])) cursor.close() return False # NOTE: for debugging (check asset conservation after every `N` transactions). # if not tx['tx_index'] % N: # check.asset_conservation(db) return True except Exception as e: exceptions.ParseTransactionError("%s" % e) cursor.close() def parse_block(db, block_index, block_time, previous_ledger_hash=None, ledger_hash=None, previous_txlist_hash=None, txlist_hash=None, previous_messages_hash=None): """Parse the block, return hash of new ledger, txlist and messages. The unused arguments `ledger_hash` and `txlist_hash` are for the test suite. undolog_cursor = db.cursor() #remove the row tracer andM exec tracer on this cursor, so we don't utilize them with undolog operations... undolog_cursor.setexectrace(None) undolog_cursor.setrowtrace(None) util.BLOCK_LEDGER = [] database.BLOCK_MESSAGES = [] assert block_index == util.CURRENT_BLOCK_INDEX # Remove undolog records for any block older than we should be tracking undolog_oldest_block_index = block_index - config.UNDOLOG_MAX_PAST_BLOCKS first_undo_index = list(undolog_cursor.execute('''SELECT first_undo_index FROM undolog_blockM WHERE block_index == ?''', (undolog_oldest_block_index,))) if len(first_undo_index) == 1 and first_undo_index[0] is not None: undolog_cursor.execute('''DELETE FROM undolog WHERE undo_index < ?''', (first_undo_index[0][0],)) undolog_cursor.execute('''DELETE FROM undolog_block WHERE block_index < ?''', (undolog_oldest_block_index,)) # Set undolog barrier for this block if block_index != config.BLOCK_FIRST: undolog_cursor.execute('''INSERT OR REPLACE INTO undolog_blockM (block_index, first_undo_index) SELECT ?, seq+1 FROM SQLITE_SEQUENCE WHERE name='undolog' ''', (block_index,)) undolog_cursor.execute('''INSERT OR REPLACE INTO undolog_block(block_index, first_undo_index) VALUES(?,?)''', (block_index, 1,)) undolog_cursor.close() # Expire orders, bets and rps. order.expire(db, block_index) bet.expire(db, block_index, block_time) rps.expire(db, block_index) # Parse transactions, sorting them by type. cursor.execute('''SELECT * FROM transactions \ WHERE block_index=? ORDER BY tx_index''', (block_index,)) for tx in list(cursor): parse_tx(db, tx) txlist.append('{}{}{}{}{}{}'.format(tx['tx_hash'], tx['source'], tx['destination'], tx['btc_amount'], tx['fee'], binascii.hexlify(tx['data']).decode('UTF-8'))) except exceptions.ParseTransactionError as e: logger.warn('ParseTransactionError for tx %s: %s' % (tx['tx_hash'], e)) # Calculate consensus hashes. new_txlist_hash, found_txlist_hash = check.consensus_hash(db, 'txlist_hash', previous_txlist_hash, txlist) new_ledger_hash, found_ledger_hash = check.consensus_hash(db, 'ledger_hash', previous_ledger_hash, util.BLOCK_LEDGER) new_messages_hash, found_messages_hash = check.consensus_haM sh(db, 'messages_hash', previous_messages_hash, database.BLOCK_MESSAGES) return new_ledger_hash, new_txlist_hash, new_messages_hash, found_messages_hash """Initialise data, create and populate the database.""" cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS blocks( block_index INTEGER UNIQUE, block_hash TEXT UNIQUE, block_time INTEGER, previous_block_hash TEXM difficulty INTEGER, PRIMARY KEY (block_index, block_hash)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON blocks (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS index_hash_idx ON blocks (block_index, block_hash) ''') t do `ALTER TABLE IF COLUMN NOT EXISTS`. columns = [column['name'] for column M in cursor.execute('''PRAGMA table_info(blocks)''')] if 'ledger_hash' not in columns: cursor.execute('''ALTER TABLE blocks ADD COLUMN ledger_hash TEXT''') if 'txlist_hash' not in columns: cursor.execute('''ALTER TABLE blocks ADD COLUMN txlist_hash TEXT''') if 'messages_hash' not in columns: cursor.execute('''ALTER TABLE blocks ADD COLUMN messages_hash TEXT''') if 'previous_block_hash' not in columns: cursor.execute('''ALTER TABLE blocks ADD COLUMN previous_block_hash TM if 'difficulty' not in columns: cursor.execute('''ALTER TABLE blocks ADD COLUMN difficulty TEXT''') # Check that first block in DB is BLOCK_FIRST. cursor.execute('''SELECT * from blocks ORDER BY block_index''') blocks = list(cursor) if blocks[0]['block_index'] != config.BLOCK_FIRST: raise exceptions.DatabaseError('First block in database is not block {}.'.format(config.BLOCK_FIRST)) cursor.execute('''CREATE TABLE IF NOTM EXISTS transactions( tx_index INTEGER UNIQUE, tx_hash TEXT UNIQUE, block_index INTEGER, block_hash TEXT, block_time INTEGER, source TEXT, destination TEXT, btc_amount INTEGER, fee INTEGER, data BLOB, supported BOOL DEFAULT 1, FOREIGN KEY (block_index, bloM ck_hash) REFERENCES blocks(block_index, block_hash), PRIMARY KEY (tx_index, tx_hash, block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON transactions (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx_index_idx ON transactions (tx_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx_hash_idx ON transactionsM ''') cursor.execute('''CREATE INDEX IF NOT EXISTS index_index_idx ON transactions (block_index, tx_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS index_hash_index_idx ON transactions (tx_index, tx_hash, block_index) ''') # Purge database of blocks, transactions from before BLOCK_FIRST. cursor.execute('''DELETE FROM blocks WHERE block_index < ?''', (config.BLOCK_FIRST,)) or.execute('''DELETE FROM transactions WHERE block_index < ?''', (config.BLOCK_FIRST,)) # (Valid) debits cursor.execute('''CREATE TABLE IF NOT EXISTS debits( block_index INTEGER, address TEXT, asset TEXT, quantity INTEGER, action TEXT, event TEXT, FOREIGN KEY (block_index) REFERENCES blocks(block_index)) ''') cursor.execute('''CREAM TE INDEX IF NOT EXISTS address_idx ON debits (address) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS asset_idx ON debits (asset) ''') # (Valid) credits cursor.execute('''CREATE TABLE IF NOT EXISTS credits( block_index INTEGER, address TEXT, asset TEXT, quantity INTEGER, calling_function TEXT, FOREIGN KEY (block_index) REFERENCES blocks(block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS address_idx ON credits (address) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS asset_idx ON credits (asset) ''') cursor.execute('''CREATE TABLE IF NOT EXISTS balances( address TEXT, asset TEXT, quantity INTEGER) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS address_asset_idx ON balances (address, asset) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS address_idx ON balances (address) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS asset_idx ON balances (asset) ''') # TODO: Store more asset info here?! sor.execute('''CREATE TABLE IF NOT EXISTS assets( asset_id TEXT UNIQUE, asset_name TEXT UNIQUE, block_index INTEGER, asset_longname TEXT) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS name_idx ON assets (asset_name) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS id_idx ON assets (asset_id) ''') # Add asset_lonM gname for sub-assets t do `ALTER TABLE IF COLUMN NOT EXISTS`. columns = [column['name'] for column in cursor.execute('''PRAGMA table_info(assets)''')] if 'asset_longname' not in columns: cursor.execute('''ALTER TABLE assets ADD COLUMN asset_longname TEXT''') cursor.execute('''CREATE UNIQUE INDEX IF NOT EXISTS asset_longname_idx ON assets(asset_longname)''') cursor.execute('''SELECT * FROM assets WHERE asset_name = ?''', ('BTC',)) if not list(cursor): execute('''INSERT INTO assets VALUES (?,?,?,?)''', ('0', 'BTC', None, None)) cursor.execute('''INSERT INTO assets VALUES (?,?,?,?)''', ('1', 'XCP', None, None)) # Leaving this here because in the future this could work for other things besides broadcast cursor.execute('''CREATE TABLE IF NOT EXISTS addresses( address TEXT UNIQUE, options INTEGER, block_index INTEGER) ''') cursor.execute('''CREATEM INDEX IF NOT EXISTS addresses_idx ON addresses (address) ''') send.initialise(db) destroy.initialise(db) order.initialise(db) btcpay.initialise(db) issuance.initialise(db) broadcast.initialise(db) bet.initialise(db) dividend.initialise(db) burn.initialise(db) cancel.initialise(db) rps.initialise(db) rpsresolve.initialise(db) sweep.initialise(db) dispenser.initialise(db) ecute('''CREATE TABLE IF NOT EXISTS messages( message_index INTEGER PRIMARY KEY, block_index INTEGER, command TEXT, category TEXT, bindings TEXT, timestamp INTEGER) ''') # TODO: FOREIGN KEY (block_index) REFERENCES blocks(block_index) DEFERRABLE INITIALLY DEFERRED) cursor.execute('''CREATE INDEX IF NOT EXISTS block_indexM _idx ON messages (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_message_index_idx ON messages (block_index, message_index) ''') # Create undolog tables cursor.execute('''CREATE TABLE IF NOT EXISTS undolog( undo_index INTEGER PRIMARY KEY AUTOINCREMENT, sql TEXT) ''') cursor.execute('''CREATE TABLE IF NOT EXISTS undolog_block( M block_index INTEGER PRIMARY KEY, first_undo_index INTEGER) ''') # Create undolog triggers for all tables in TABLES list, plus the 'balances' table for table in UNDOLOG_TABLES: columns = [column['name'] for column in cursor.execute('''PRAGMA table_info({})'''.format(table))] cursor.execute('''CREATE TRIGGER IF NOT EXISTS _{}_insert AFTER INSERT ON {} BEGIN INSERT INTO undolog VALUES(NULL, 'DELETE FROM {} WHERE rowid='|M END; '''.format(table, table, table)) columns_parts = ["{}='||quote(old.{})||'".format(c, c) for c in columns] cursor.execute('''CREATE TRIGGER IF NOT EXISTS _{}_update AFTER UPDATE ON {} BEGIN INSERT INTO undolog VALUES(NULL, 'UPDATE {} SET {} WHERE rowid='||old.rowid); END; '''.format(table, table, table, ','.join(columns_parts))) columns_parts = M ["'||quote(old.{})||'".format(c) for c in columns] cursor.execute('''CREATE TRIGGER IF NOT EXISTS _{}_delete BEFORE DELETE ON {} BEGIN INSERT INTO undolog VALUES(NULL, 'INSERT INTO {}(rowid,{}) VALUES('||old.rowid||',{})'); END; '''.format(table, table, table, ','.join(columns), ','.join(columns_parts))) # Drop undolog tables on messages table if they exist (fix for adding them in 9.52.0) for trigger_type in ('insert', 'uM cursor.execute("DROP TRIGGER IF EXISTS _messages_{}".format(trigger_type)) # Mempool messages # NOTE: `status`, 'block_index` are removed from bindings. cursor.execute('''DROP TABLE IF EXISTS mempool''') cursor.execute('''CREATE TABLE mempool( tx_hash TEXT, command TEXT, category TEXT, bindings TEXT, timestamp INTEGER) ''') ef get_tx_info(tx_hex, block_parser=None, block_index=None, db=None): """Get the transaction info. Returns normalized None data for DecodeError and BTCOnlyError.""" return _get_tx_info(tx_hex, block_parser, block_index) except DecodeError as e: return b'', None, None, None, None, None except BTCOnlyError as e: # NOTE: For debugging, logger.debug('Could not decode: ' + str(e)) if util.enabled('dispensers', block_index): return b'',M None, None, None, None, _get_swap_tx(e.decodedTx, block_parser, block_index, db=db) except: # (DecodeError, backend.indexd.BackendRPCError) as e: return b'', None, None, None, None, None return b'', None, None, None, None, None def _get_swap_tx(decoded_tx, block_parser=None, block_index=None, db=None): def get_pubkeyhash(scriptpubkey): asm = script.get_asm(scriptpubkey) if len(asm) > 0: if asm[0] == "OP_DUP": if len(asm) != 5 or asm[1] != 'OP_HASH160' or asm[3] != 'OP_EQUALVERIFY' or asm[4] != 'OP_CHECKSIG': return False else: return {"pubkeyhash":asm[2],"address_version":config.ADDRESSVERSION} elif (asm[0] == "OP_HASH160") and util.enabled('p2sh_dispensers_support'): if len(asm) != 3 or asm[-1] != 'OP_EQUAL': return False else: return {"pubkeyhash":asm[1M ],"address_version":config.P2SH_ADDRESSVERSION} return False def get_address(scriptpubkey): if util.enabled('correct_segwit_txids') and scriptpubkey.is_witness_v0_keyhash(): pubkey = scriptpubkey[2:] address = str(bitcoinlib.bech32.CBech32Data.from_bytes(0, pubkey)) return address pubkeyhashdict = get_pubkeyhash(scriptpubkey) if not pubkeyhashdict: return False pubkeyhash = pubM keyhashdict["pubkeyhash"] address_version = pubkeyhashdict["address_version"] pubkeyhash = binascii.hexlify(pubkeyhash).decode('utf-8') address = script.base58_check_encode(pubkeyhash, address_version) # Test decoding of address. if address != config.UNSPENDABLE and binascii.unhexlify(bytes(pubkeyhash, 'utf-8')) != script.base58_check_decode(address, address_version): return False return address check_sources = db == None # If we didn't get passed a database cursor, assume we have to check for dispenser for vout in decoded_tx.vout: address = get_address(vout.scriptPubKey) destination = None btc_amount = None destination = address btc_amount = vout.nValue elif util.enabled('hotfix_dispensers_with_non_p2pkh'): asm = script.get_asm(vout.scriptPubKey) if asm[-1] == 'OP_CHECKSIG': destinatiM on, new_data = decode_checksig(asm, decoded_tx) elif asm[-1] == 'OP_CHECKMULTISIG': destination, new_data = decode_checkmultisig(asm, decoded_tx) elif asm[0] == 'OP_HASH160' and asm[-1] == 'OP_EQUAL' and len(asm) == 3: destination, new_data = decode_scripthash(asm) elif asm[0] == 'OP_RETURN': pass #Just ignore. elif util.enabled('segwit_support') and asm[0] == 0: # Segwit output destinatioM n, new_data = decode_p2w(vout.scriptPubKey) logger.error('unrecognised scriptPubkey. Just ignore this: ' + str(asm)) if destination and not new_data: amount = vout.nValue logger.error('cannot parse destination address or new_data found: ' + str(asm)) if db != None and dispenser.is_dispensable(db, destination, btc_amount): check_sources = True outputs.append((destination, btc_amount)) # Collect all (unique) source addresses. # if we haven't found them yet if check_sources: for vin in decoded_tx.vin[:]: # Loop through inputs. if block_parser: vin_tx = block_parser.read_raw_transaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx['__data__']) vin_tx = backend.getrawtransaction(ib2h(vin.prevout.hash)) # TODO: Biggest penalty on parsing is here vin_ctx = backend.deserialize(vin_tx) vout = vin_ctx.vout[vin.prevout.n] asm = script.get_asm(vout.scriptPubKey) if asm[-1] == 'OP_CHECKSIG': new_source, new_data = decode_checksig(asm, decoded_tx) if new_data or not new_source: raise DecodeError('data in source') elif asm[-1] == 'OP_CHECKMULTISIG': new_source, new_data = decode_checkmultisig(asm, decoded_tx) if new_data or noM raise DecodeError('data in source') elif asm[0] == 'OP_HASH160' and asm[-1] == 'OP_EQUAL' and len(asm) == 3: new_source, new_data = decode_scripthash(asm) if new_data or not new_source: raise DecodeError('data in source') elif util.enabled('segwit_support') and asm[0] == 0: # Segwit output # Get the full transaction data for this input transaction. new_source,M new_data = decode_p2w(vout.scriptPubKey) raise DecodeError('unrecognised source type') # old; append to sources, results in invalid addresses # new; first found source is source, the rest can be anything (to fund the TX for example) if not (util.enabled('first_input_is_source') and len(sources)): # Collect unique sources. if new_source not in sources: sources.append(new_source) def _get_tx_info(tx_hex, block_parser=None, block_index=None, p2sh_is_segwit=False): """Get the transaction info. Calls one of two subfunctions depending on signature type.""" if not block_index: block_index = util.CURRENT_BLOCK_INDEX if util.enabled('p2sh_addresses', block_index=block_index): # Protocol change. return get_tx_info3(tx_hex, block_parser=block_parser, p2sh_is_segwit=p2sh_is_segwit) elif util.enabled('multisig_addresses', block_index=block_index): M return get_tx_info2(tx_hex, block_parser=block_parser) return get_tx_info1(tx_hex, block_index, block_parser=block_parser) def get_tx_info1(tx_hex, block_index, block_parser=None): """Get singlesig transaction info. The destination, if it exists, always comes before the data output; the change, if it exists, always comes after. ctx = backend.deserialize(tx_hex) def get_pubkeyhash(scriptpubkey): asm = script.get_asm(scriptpubkey) if len(asm) != 5 or asm[0] != 'OP_DUP' or asm[1] != 'OP_HASH160' or asm[3] != 'OP_EQUALVERIFY' or asm[4] != 'OP_CHECKSIG': return False return asm[2] def get_address(scriptpubkey): pubkeyhash = get_pubkeyhash(scriptpubkey) if not pubkeyhash: return False pubkeyhash = binascii.hexlify(pubkeyhash).decode('utf-8') address = script.base58_check_encode(pubkeyhash, config.ADDRESSVERSION) # Test decoding of address. if address != confM ig.UNSPENDABLE and binascii.unhexlify(bytes(pubkeyhash, 'utf-8')) != script.base58_check_decode(address, config.ADDRESSVERSION): return False return address # Fee is the input values minus output values. # Get destination output and data output. destination, btc_amount, data = None, None, b'' pubkeyhash_encoding = False for vout in ctx.vout: fee -= vout.nValue # Sum data chunks to get data. (Can mix OP_RETURN and multi-sig.) asm = scripM t.get_asm(vout.scriptPubKey) if len(asm) == 2 and asm[0] == 'OP_RETURN': # OP_RETURN if type(asm[1]) != bytes: continue data_chunk = asm[1] data += data_chunk elif len(asm) == 5 and asm[0] == 1 and asm[3] == 2 and asm[4] == 'OP_CHECKMULTISIG': # Multi-sig if type(asm[2]) != bytes: continue data_pubkey = asm[2] data_chunk_length = data_pubkey[0] # No M data_chunk = data_pubkey[1:data_chunk_length + 1] data += data_chunk elif len(asm) == 5 and (block_index >= 293000 or config.TESTNET or config.REGTEST): # Protocol change. # Be strict. pubkeyhash = get_pubkeyhash(vout.scriptPubKey) if not pubkeyhash: continue if ctx.is_coinbase(): raise DecodeError('coinbase transaction') obj1 = arc4.init_arc4(ctx.vin[0].prevout.hash[::-1]M data_pubkey = obj1.decrypt(pubkeyhash) if data_pubkey[1:9] == config.PREFIX or pubkeyhash_encoding: pubkeyhash_encoding = True data_chunk_length = data_pubkey[0] # No ord() necessary. data_chunk = data_pubkey[1:data_chunk_length + 1] if data_chunk[-8:] == config.PREFIX: data += data_chunk[:-8] break else: data += data_chunk # Destination is tM he first output before the data. if not destination and not btc_amount and not data: address = get_address(vout.scriptPubKey) if address: destination = address btc_amount = vout.nValue # Check for, and strip away, prefix (except for burns). if destination == config.UNSPENDABLE: elif data[:len(config.PREFIX)] == config.PREFIX: data = data[len(config.PREFIX):] raise DecodeError('no prefix') look for source if data were found or destination is UNSPENDABLE, for speed. if not data and destination != config.UNSPENDABLE: raise BTCOnlyError('no data and not unspendable') # Collect all possible source addresses; ignore coinbase transactions and anything but the simplest Pay source_list = [] for vin in ctx.vin[:]: # Loop through input transactions. if vin.prevout.is_null(): raise DecodeError('cM oinbase transaction') # Get the full transaction data for this input transaction. if block_parser: vin_tx = block_parser.read_raw_transaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx['__data__']) vin_tx = backend.getrawtransaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx) vout = vin_ctx.vout[vin.prevout.n] fee += vout.nValue address = get_address(vout.scriptPubKey) raise DecodeError('invalid scriptpubkey') source_list.append(address) # Require that all possible source addresses be the same. if all(x == source_list[0] for x in source_list): source = source_list[0] source = None return source, destination, btc_amount, fee, data, None def get_tx_info3(tx_hex, block_parser=None, p2sh_is_segwit=False): return get_tx_info2(tx_hex, block_parser=block_parser, p2sh_support=True, p2sh_is_M segwit=p2sh_is_segwit) def arc4_decrypt(cyphertext, ctx): obfuscate. Initialise key once per attempt.''' key = arc4.init_arc4(ctx.vin[0].prevout.hash[::-1]) return key.decrypt(cyphertext) def get_opreturn(asm): if len(asm) == 2 and asm[0] == 'OP_RETURN': pubkeyhash = asm[1] if type(pubkeyhash) == bytes: return pubkeyhash raise DecodeError('invalid OP_RETURN') def decode_opreturn(asm, ctx): chunk = get_opreturn(asm) chunk = arc4_decrypt(chunk, ctx) if chunk[:len(config.PREFIX)] == config.PREFIX: # Data destination, data = None, chunk[len(config.PREFIX):] raise DecodeError('unrecognised OP_RETURN output') return destination, data def decode_checksig(asm, ctx): pubkeyhash = script.get_checksig(asm) chunk = arc4_decrypt(pubkeyhash, ctx) if chunk[1:len(config.PREFIX) + 1] == config.PREFIX: # Data # Padding byte in each output (instead of just in the last one) so that encoding methods may beM s just not very much data. chunk_length = chunk[0] chunk = chunk[1:chunk_length + 1] destination, data = None, chunk[len(config.PREFIX):] else: # Destination pubkeyhash = binascii.hexlify(pubkeyhash).decode('utf-8') destination, data = script.base58_check_encode(pubkeyhash, config.ADDRESSVERSION), None return destination, data def decode_scripthash(asm): destination = script.base58_check_enM code(binascii.hexlify(asm[1]).decode('utf-8'), config.P2SH_ADDRESSVERSION) return destination, None def decode_checkmultisig(asm, ctx): pubkeys, signatures_required = script.get_checkmultisig(asm) for pubkey in pubkeys[:-1]: # (No data in last pubkey.) chunk += pubkey[1:-1] # Skip sign byte and nonce byte. chunk = arc4_decrypt(chunk, ctx) if chunk[1:len(config.PREFIX) + 1] == config.PREFIX: # Data # Padding byte in each output (instead of just iM n the last one) so that encoding methods may be mixed. Also, it s just not very much data. chunk_length = chunk[0] chunk = chunk[1:chunk_length + 1] destination, data = None, chunk[len(config.PREFIX):] else: # Destination pubkeyhashes = [script.pubkey_to_pubkeyhash(pubkey) for pubkey in pubkeys] destination, data = script.construct_array(signatures_required, pubkeyhashes, len(pubkeyhashes)), None return destinatM def decode_p2w(script_pubkey): bech32 = bitcoinlib.bech32.CBech32Data.from_bytes(0, script_pubkey[2:22]) return str(bech32), None except TypeError as e: raise DecodeError('bech32 decoding error') def get_tx_info2(tx_hex, block_parser=None, p2sh_support=False, p2sh_is_segwit=False): """Get multisig transaction info. The destinations, if they exists, always comes before the data output; the change, if it exists, always comes after. ctx = backend.deserialize(tx_hex) # Ignore coinbase transactions. if ctx.is_coinbase(): raise DecodeError('coinbase transaction') # Get destinations and data outputs. destinations, btc_amount, fee, data = [], 0, 0, b'' for vout in ctx.vout: # Fee is the input values minus output values. output_value = vout.nValue fee -= output_value # Ignore transactions with invalid script. asm = script.get_asm(vout.scriptPM except CScriptInvalidError as e: raise DecodeError(e) if asm[0] == 'OP_RETURN': new_destination, new_data = decode_opreturn(asm, ctx) elif asm[-1] == 'OP_CHECKSIG': new_destination, new_data = decode_checksig(asm, ctx) elif asm[-1] == 'OP_CHECKMULTISIG': new_destination, new_data = decode_checkmultisig(asm, ctx) raise DecodeError('unrecognised output type') sh_support and asm[0] == 'OP_HASH160' and asm[-1] == 'OP_EQUAL' and len(asm) == 3: new_destination, new_data = decode_scripthash(asm) elif util.enabled('segwit_support') and asm[0] == 0: # Segwit Vout, second param is redeemScript #redeemScript = asm[1] new_destination, new_data = decode_p2w(vout.scriptPubKey) raise DecodeError('unrecognised output type') assert not (new_destination and new_data) assert new_destinationM != None or new_data != None # `decode_*()` should never return `None, None`. if util.enabled('null_data_check'): if new_data == []: raise DecodeError('new destination is `None`') # All destinations come before all data. if not data and not new_data and destinations != [config.UNSPENDABLE,]: destinations.append(new_destination) btc_amount += output_value if new_destination: # Change. break else: # Data. data += new_data # source can be determined by parsing the p2sh_data transaction # or from the first spent output # P2SH encoding signalling p2sh_encoding_source = None if util.enabled('p2sh_encoding') and data == b'P2SH': for vin in ctx.vin: if util.enabled("prevout_segwit_fix"): vin_tx = backend.getrawtransaction(ib2h(vin.prevout.hash)) vin_ctx =M backend.deserialize(vin_tx) prevout_is_segwit = vin_ctx.has_witness() prevout_is_segwit = p2sh_is_segwit # Ignore transactions with invalid script. asm = script.get_asm(vin.scriptSig) except CScriptInvalidError as e: raise DecodeError(e) new_source, new_destination, new_data = p2sh_encoding.decode_p2sh_input(asm, p2sh_is_segwit=prevout_is_segwit) s could be a p2sh source address with no encoded data if new_data is None: continue; if new_source is not None: if p2sh_encoding_source is not None and new_source != p2sh_encoding_source: # this p2sh data input has a bad source address raise DecodeError('inconsistent p2sh inputs') p2sh_encoding_source = new_source assert not new_destination data += new_data # Only look for soM urce if data were found or destination is `UNSPENDABLE`, if not data and destinations != [config.UNSPENDABLE,]: raise BTCOnlyError('no data and not unspendable', ctx) # Collect all (unique) source addresses. # if we haven't found them yet for vin in ctx.vin[:]: # Loop through inputs. # Get the full transaction data for this input transaction. if block_parser: vin_tx = block_parser.read_raw_transaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx['__data__']) vin_tx = backend.getrawtransaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx) vout = vin_ctx.vout[vin.prevout.n] fee += vout.nValue asm = script.get_asm(vout.scriptPubKey) if asm[-1] == 'OP_CHECKSIG': new_source, new_data = decode_checksig(asm, ctx) if new_data or not new_source: raise DecodeError('data in source') asm[-1] == 'OP_CHECKMULTISIG': new_source, new_data = decode_checkmultisig(asm, ctx) if new_data or not new_source: raise DecodeError('data in source') elif p2sh_support and asm[0] == 'OP_HASH160' and asm[-1] == 'OP_EQUAL' and len(asm) == 3: new_source, new_data = decode_scripthash(asm) if new_data or not new_source: raise DecodeError('data in source') elif util.enabled('segwit_support') and asm[0] == 0: new_source, new_data = decode_p2w(vout.scriptPubKey) raise DecodeError('unrecognised source type') # old; append to sources, results in invalid addresses # new; first found source is source, the rest can be anything (to fund the TX for example) if not (util.enabled('first_input_is_source') and len(sources)): # Collect unique sources. if new_source not in sources: sources.append(new_source) the source from the p2sh data source if p2sh_encoding_source is not None: sources = p2sh_encoding_source sources = '-'.join(sources) destinations = '-'.join(destinations) return sources, destinations, btc_amount, round(fee), data, None def reinitialise(db, block_index=None): """Drop all predefined tables and initialise the database once again.""" cursor = db.cursor() # Delete all of the results of parsing (including the undolog) for table in TABLES + ['balancM es', 'undolog', 'undolog_block']: cursor.execute('''DROP TABLE IF EXISTS {}'''.format(table)) # Create missing tables # clean consensus hashes if first block hash doesn't match with checkpoint. if config.TESTNET: checkpoints = check.CHECKPOINTS_TESTNET elif config.REGTEST: checkpoints = check.CHECKPOINTS_REGTEST checkpoints = check.CHECKPOINTS_MAINNET columns = [column['name'] for column in cursor.execute('''PRAGMA table_info(blocksM for field in ['ledger_hash', 'txlist_hash']: if field in columns: sql = '''SELECT {} FROM blocks WHERE block_index = ?'''.format(field) first_block = list(cursor.execute(sql, (config.BLOCK_FIRST,))) if first_block: first_hash = first_block[0][field] if first_hash != checkpoints[config.BLOCK_FIRST][field]: logger.info('First hash changed. Cleaning {}.'.format(field)) cursor.execute('''UPDATM E blocks SET {} = NULL'''.format(field)) # For rollbacks, just delete new blocks and then reparse what cursor.execute('''DELETE FROM transactions WHERE block_index > ?''', (block_index,)) cursor.execute('''DELETE FROM blocks WHERE block_index > ?''', (block_index,)) elif config.TESTNET or config.REGTEST: # block_index NOT specified and we are running testnet # just blow away the consensus hashes with a full testnet reparse, as we could activate # new features retroactively, which could otherwise lead to ConsensusError exceptions being raised. logger.info("Testnet/regtest full reparse detected: Clearing all consensus hashes before performing reparse.") cursor.execute('''UPDATE blocks SET ledger_hash = NULL, txlist_hash = NULL, messages_hash = NULL''') def reparse(db, block_index=None, quiet=False): """Reparse all transactions (atomically). If block_index is set, rollback to the end of that block. f reparse_from_undolog(db, block_index, quiet): """speedy reparse method that utilizes the undolog. if fails, fallback to the full reparse method""" if not block_index: return False # Can't reparse from undolog undolog_cursor = db.cursor() undolog_cursor.setexectrace(None) undolog_cursor.setrowtrace(None) def get_block_index_for_undo_index(undo_indexes, undo_index): for block_index, first_undo_index in undo_indexes.items(): #in order if undo_index < first_undo_index: return block_index - 1 return next(reversed(undo_indexes)) #the last inserted block_index # Check if we can reparse from the undolog results = list(undolog_cursor.execute( '''SELECT block_index, first_undo_index FROM undolog_block WHERE block_index >= ? ORDER BY block_index ASC''', (block_index,))) undo_indexes = collections.OrderedDict() for result in results: undo_indexes[result[0]] = result[1] undo_start_block_index = block_index + 1 if undo_start_block_index not in undo_indexes: if block_index in undo_indexes: # Edge case, should only happen if we're "rolling back" to latest block (e.g. via cmd line) return True #skip undo else: return False # Undolog doesn't go that far back, full reparse required... # Grab the undolog... undolog = list(undolog_cursor.execute( '''SELECT undo_index, sql FROM undolog WHERE undo_index >= ? ORDER BY undo_index DESC''', (undo_indexes[undo_start_block_index],))) # Replay the undolog backwards, from the last entry to first_undo_index... for entry in undolog: logger.info("Undolog: Block {} (undo_index {}): {}".format( get_block_index_for_undo_index(undo_indexes, entry[0]), eM undolog_cursor.execute(entry[1]) # Trim back tx and blocks undolog_cursor.execute('''DELETE FROM transactions WHERE block_index > ?''', (block_index,)) undolog_cursor.execute('''DELETE FROM blocks WHERE block_index > ?''', (block_index,)) # As well as undolog entries... undolog_cursor.execute('''DELETE FROM undolog WHERE undo_index >= ?''', (undo_indexes[undo_start_block_index],)) undolog_cursor.execute('''DM ELETE FROM undolog_block WHERE block_index >= ?''', (undo_start_block_index,)) undolog_cursor.close() logger.info('Rolling back transactions to block {}.'.format(block_index)) logger.info('Reparsing all transactions.') check.software_version() reparse_start = time.time() # Reparse from the undolog if possible reparsed = reparse_from_undolog(db, block_index, quiet) cursor = db.cursor() if not reparsed: logger.info("Could not roll back from undolog. Performing full reparse instead...") root_logger = logging.getLogger() root_level = logger.getEffectiveLevel() reinitialise(db, block_index) # Reparse all blocks, transactions. if quiet: root_logger.setLevel(logging.WARNING) previous_ledger_hash, previous_txlist_hash, previous_messages_hash = None, None, None cursor.execute('''SELECT * FROM blocks ORDER BY block_index''') for block in cursor.fetchall(): util.CURRENT_BLOCK_INDEX = block['block_index'] previous_ledger_hash, previous_txlist_hash, previous_messages_hash, previous_found_messages_hash = parse_block( db, block['block_index'], block['block_time'], previous_ledger_hash=prevM previous_txlist_hash=previous_txlist_hash, previous_messages_hash=previous_messages_hash) if quiet and block['block_index'] % 10 == 0: # every 10 blocks print status root_logger.setLevel(logging.INFO) logger.info('Block (re-parse): %s (hashes: L:%s / TX:%s / M:%s%s)' % ( block['blocM k_index'], previous_ledger_hash[-5:], previous_txlist_hash[-5:], previous_messages_hash[-5:], (' [overwrote %s]' % previous_found_messages_hash) if previous_found_messages_hash and previous_found_messages_hash != previous_messages_hash else '')) if quiet and block['block_index'] % 10 == 0: root_logger.setLevel(logging.WARNING) root_logger.setLevel(root_level) # Check for conservation of assets. .asset_conservation(db) # Update database version number. database.update_version(db) reparse_end = time.time() logger.info("Reparse took {:.3f} minutes.".format((reparse_end - reparse_start) / 60.0)) # on full reparse - vacuum the DB afterwards for better subsequent performance (especially on non-SSDs) if not block_index: database.vacuum(db) def list_tx(db, block_hash, block_index, block_time, tx_hash, tx_index, tx_hex=None): assert type(tx_hash) =M cursor = db.cursor() # Edge case: confirmed tx_hash also in mempool cursor.execute('''SELECT * FROM transactions WHERE tx_hash = ?''', (tx_hash,)) transactions = list(cursor) if transactions: return tx_index # Get the important details about each transaction. if tx_hex is None: tx_hex = backend.getrawtransaction(tx_hash) # TODO: This is the call that is stalling the process the most source, destination, btc_amount, fee, data, decoded_tx = get_tx_info(tx_hex, M if not source and decoded_tx and util.enabled('dispensers', block_index): outputs = decoded_tx[1] for out in outputs: if out[0] != decoded_tx[0][0] and dispenser.is_dispensable(db, out[0], out[1]): source = decoded_tx[0][0] destination = out[0] btc_amount = out[1] fee = 0 data = struct.pack(config.SHORT_TXTYPE_FORMAT, dispenser.DISPENSE_ID) data += b'\x00' break # PM revent inspection of further dispenses (only first one is valid) if block_hash == None: block_hash = config.MEMPOOL_BLOCK_HASH block_index = config.MEMPOOL_BLOCK_INDEX assert block_index == util.CURRENT_BLOCK_INDEX if source and (data or destination == config.UNSPENDABLE or decoded_tx): logger.debug('Saving transaction: {}'.format(tx_hash)) cursor.execute('''INSERT INTO transactions( tx_index, block_index, block_hash, block_time, source, destination, btc_amount, fee, data) VALUES(?,?,?,?,?,?,?,?,?,?)''', (tx_index, tx_hash, block_index, block_hash, block_time, source, destination, btc_amount, fee, data) ) cursor.close() return tx_index + 1 logger.getChild('list_tx.skip').debug('Skipping transaction: {}'.format(tx_hash)) def kickstart(db, bitcoind_dir): if bitcoind_dir is None: if platform.system() == 'DM bitcoind_dir = os.path.expanduser('~/Library/Application Support/Bitcoin/') elif platform.system() == 'Windows': bitcoind_dir = os.path.join(os.environ['APPDATA'], 'Bitcoin') bitcoind_dir = os.path.expanduser('~/.bitcoin') if not os.path.isdir(bitcoind_dir): raise Exception('Bitcoin Core data directory not found at {}. Use --bitcoind-dir parameter.'.format(bitcoind_dir)) cursor = db.cursor() logger.warning('''Warning: hat bitcoind is stopped. - You must reindex bitcoind after the initialization is complete (restart with `-reindex=1`) - The initialization may take a while.''') if input('Proceed with the initialization? (y/N) : ') != 'y': if config.TESTNET: first_hash = config.BLOCK_FIRST_TESTNET_HASH elif config.REGTEST: first_hash = config.BLOCK_FIRST_REGTEST_HASH first_hash = config.BLOCK_FIRST_MAINNET_HASH start_time_total = time.time() # Get hash of last M chain_parser = ChainstateParser(os.path.join(bitcoind_dir, 'chainstate')) last_hash = chain_parser.get_last_block_hash() chain_parser.close() # Start block parser. block_parser = BlockchainParser(os.path.join(bitcoind_dir, 'blocks'), os.path.join(bitcoind_dir, 'blocks/index')) current_hash = last_hash # Prepare SQLite database. # TODO: Be more specific! logger.info('Preparing database.') start_time = time.time() nitialise(db, block_index=config.BLOCK_FIRST - 1) logger.info('Prepared database in {:.3f}s'.format(time.time() - start_time)) # Get blocks and transactions, moving backwards in time. while current_hash != None: start_time = time.time() transactions = [] # Get `tx_info`s for transactions in this block. block = block_parser.read_raw_block(current_hash) for tx in block['transactions']: source, destination, btc_amountM , fee, data = get_tx_info(tx['__data__'], block_parser=block_parser, block_index=block['block_index']) if source and (data or destination == config.UNSPENDABLE): transactions.append(( tx['tx_hash'], block['block_index'], block['block_hash'], block['block_time'], source, destination, btc_amount, fee, data )) logger.info('Valid transaction: {}'.format(tx['tx_hash'])) # Insert block M and transactions into database. cursor.execute('''INSERT INTO blocks( block_index, block_hash, block_time) VALUES(?,?,?)''', (block['block_index'], block['block_hash'], block['block_time'])) if len(transactions): transactions = list(reversed(transactions)) tx_chunks = [transactions[i:i+90] for i in range(0, len(transactions), 90)] for tx_chunk in tx_chunks: sql = '''INSERT INTO transactions (tx_index, tx_hash, block_index, block_hash, block_time, source, destination, btc_amount, fee, data) VALUES ''' bindings = () bindings_place = [] # negative tx_index from -1 and inverse order for fast reordering # TM ODO: Can this be clearer? for tx in tx_chunk: bindings += (-(tx_index + 1),) + tx bindings_place.append('''(?,?,?,?,?,?,?,?,?,?)''') tx_index += 1 sql += ', '.join(bindings_place) cursor.execute(sql, bindings) logger.info('Block {} ({}): {}/{} saved in {:.3f}s'.format( block['block_index'], block['block_hash'], len(transaM ctions), len(block['transactions']), time.time() - start_time)) # Get hash of next block. current_hash = block['hash_prev'] if current_hash != first_hash else None block_parser.close() # Reorder all transactions in database. logger.info('Reordering transactions.') start_time = time.time() cursor.execute('''UPDATE transactions SET tx_index = tx_index + ?''', (tx_index,)) logger.info('Reordered transactions in {:.3f}sM .'.format(time.time() - start_time)) # Parse all transactions in database. logger.info('Total duration: {:.3f}s'.format(time.time() - start_time_total)) def last_db_index(db): cursor = db.cursor() blocks = list(cursor.execute('''SELECT * FROM blocks WHERE block_index = (SELECT MAX(block_index) from blocks)''')) return blocks[0]['block_index'] except IndexError: return 0 except apsw.SQLError: def get_next_tx_index(db): """Return index of next transaction.""" cursor = db.cursor() txes = list(cursor.execute('''SELECT * FROM transactions WHERE tx_index = (SELECT MAX(tx_index) from transactions)''')) assert len(txes) == 1 tx_index = txes[0]['tx_index'] + 1 tx_index = 0 class MempoolError(Exception): # Check software version. check.software_version() # Get index of last block. if util.CURRENT_BLOCK_INDEX == 0: logger.warning('New database.') block_index = config.BLOCK_FIRST block_index = util.CURRENT_BLOCK_INDEX + 1 # Check database version. check.database_version(db) except check.DatabaseVersionError as e: logger.info(str(e)) # no need to reparse or rollback a new database if block_index != config.BLOCK_FIRST: reparse(db, block_index=e.reparse_block_index, quiet=False) else: #version update was included in reparse(), so don't do it twice database.update_version(db) logger.info('Resuming parsing.') # Get index of last transaction. tx_index = get_next_tx_index(db) not_supported = {} # No false positives. Use a dict to allow for O(1) lookups not_supported_sorted = collections.deque() # ^ Entries in form of (block_index, tx_hash), oldest first. Allows for easy reM moval of past, unncessary entries cursor = db.cursor() # a reorg can happen without the block count increasing, or even for that # matter, with the block count decreasing. This should only delay # processing of the new blocks a bit. start_time = time.time() # Get block count. # If the backend is unreachable and `config.FORCE` is set, just sleep # and try again repeatedly. block_count = backend.getblockcount() onnectionRefusedError, http.client.CannotSendRequest, backend.addrindexrs.BackendRPCError) as e: if config.FORCE: time.sleep(config.BACKEND_POLL_INTERVAL) continue raise e # Get new blocks. if block_index <= block_count: current_index = block_index # Backwards check for incorrect blocks due to chain reorganisation, and stop when a common parent is found. if block_count - block_index < M 100: # Undolog only saves last 100 blocks, if there's a reorg deeper than that manual reparse should be done requires_rollback = False while True: if current_index == config.BLOCK_FIRST: break logger.debug('Checking that block {} is not an orphan.'.format(current_index)) # Backend parent hash. current_hash = backend.getblockhash(current_index) current_cblock = M backend.getblock(current_hash) backend_parent = bitcoinlib.core.b2lx(current_cblock.hashPrevBlock) # DB parent hash. blocks = list(cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (current_index - 1,))) if len(blocks) != 1: # For empty DB. break db_parent = blocks[0]['block_hash'] # Compare. assert type(db_parent) == str assert type(backend_parent) == str if db_parent == backend_parent: break else: current_index -= 1 requires_rollback = True # Rollback for reorganisation. if requires_rollback: # Record reorganisation. logger.warning('Blockchain reorganisation at block {}.'.format(current_indexM log.message(db, block_index, 'reorg', None, {'block_index': current_index}) # Rollback the DB. reparse(db, block_index=current_index-1, quiet=True) block_index = current_index tx_index = get_next_tx_index(db) continue # Check version. (Don t add any blocks to the database while # running an out check.software_version() Get and parse transactions in this block (atomically). block_hash = backend.getblockhash(current_index) block = backend.getblock(block_hash) previous_block_hash = bitcoinlib.core.b2lx(block.hashPrevBlock) block_time = block.nTime txhash_list, raw_transactions = backend.get_tx_list(block) with db: util.CURRENT_BLOCK_INDEX = block_index # List the block. cursor.execute('''INSERT INTO blocks( block_index, block_hash, block_time, previous_block_hash, difficulty) VALUES(?,?,?,?,?)''', (block_index, block_hash, block_time, previous_block_hash, block.difficuM ) # List the transactions in the block. for tx_hash in txhash_list: tx_hex = raw_transactions[tx_hash] tx_index = list_tx(db, block_hash, block_index, block_time, tx_hash, tx_index, tx_hex) # Parse the transactions in the block. new_ledger_hash, new_txlist_hash, new_messages_hash, found_messages_hash = parse_block(db, block_index, block_time) # When newly caught uM p, check for conservation of assets. if block_index == block_count: if config.CHECK_ASSET_CONSERVATION: check.asset_conservation(db) # Remove any non supported transactions older than ten blocks. while len(not_supported_sorted) and not_supported_sorted[0][0] <= block_index - 10: tx_h = not_supported_sorted.popleft()[1] del not_supported[tx_h] logger.info('Block: %s (%ss, hashes: L:%s / TX:%s / M:%M str(block_index), "{:.2f}".format(time.time() - start_time, 3), new_ledger_hash[-5:], new_txlist_hash[-5:], new_messages_hash[-5:], (' [overwrote %s]' % found_messages_hash) if found_messages_hash and found_messages_hash != new_messages_hash else '')) # Increment block index. block_count = backend.getblockcount() block_index += 1 # TODO: add zeromq support here to await TXs and Blocks instead ofM # Get old mempool. old_mempool = list(cursor.execute('''SELECT * FROM mempool''')) old_mempool_hashes = [message['tx_hash'] for message in old_mempool] if backend.MEMPOOL_CACHE_INITIALIZED is False: backend.init_mempool_cache() logger.info("Ready for queries.") # Fake values for fake block. curr_time = int(time.time()) mempool_tx_index = tx_index xcp_mempool = [] raw_mempool = backend.getrawmempool() # this is a quick fix to make counterparty usable on high mempool situations # however, this makes the mempool unreliable on counterparty, a better, larger # fix must be done by changing this whole function into a zmq driven loop if len(raw_mempool) > config.MEMPOOL_TXCOUNT_UPDATE_LIMIT: continue # For each transaction in Bitcoin Core mempool, if it # a fake block,M a fake transaction, capture the generated messages, # and then save those messages. # Every transaction in mempool is parsed independently. (DB is rolled back after each one.) # We first filter out which transactions we've already parsed before so we can batch fetch their raw data parse_txs = [] for tx_hash in raw_mempool: # If already in mempool, copy to new one. if tx_hash in old_mempool_hashes: for meM ssage in old_mempool: if message['tx_hash'] == tx_hash: xcp_mempool.append((tx_hash, message)) # If not a supported XCP transaction, skip. elif tx_hash in not_supported: pass # Else: list, parse and save it. else: parse_txs.append(tx_hash) # fetch raw for all transactions that need to be parsed # Sometimes the transactions can ound: `{'code': -5, 'message': 'No information available about transaction'}` # - is txindex enabled in Bitcoind? # - or was there a block found while batch feting the raw txs # - or was there a double spend for w/e reason accepted into the mempool (replace-by-fee?) raw_transactions = backend.getrawtransaction_batch(parse_txs) except Exception as e: logger.warning('Failed to fetch raw for mempool TXs, restarting loopM continue # restart the follow loop for tx_hash in parse_txs: try: with db: # List the fake block. cursor.execute('''INSERT INTO blocks( block_index, block_hash, block_time) VALUES(?,?,?)''', (config.MEMPOOL_BLOCK_INDEX, config.MEMPOOL_BLOCK_HASH, curr_time) ) tx_hex = raw_transactions[tx_hash] if tx_hex is None: logger.debug('tx_hash %s not found in backend. Not adding to mempool.', (tx_hash, )) raise MempoolError mempool_tx_index = list_tx(db, None, block_index, curr_time, tx_hash, tx_index=M mempool_tx_index, tx_hex=tx_hex) # Parse transaction. cursor.execute('''SELECT * FROM transactions WHERE tx_hash = ?''', (tx_hash,)) transactions = list(cursor) if transactions: assert len(transactions) == 1 transaction = transactions[0] supported = parse_tx(db, transaction) if not supported: not_supported[tx_hash] = '' not_supported_sorted.append((block_index, tx_hash)) else: # If a transaction hasn # table `transactions`, then it # Counterparty transaction. not_supported[tx_hash] = '' not_supported_sorted.append((block_index, tx_hash)) M # Save transaction and side cursor.execute('''SELECT * FROM messages WHERE block_index = ?''', (config.MEMPOOL_BLOCK_INDEX,)) for message in list(cursor): xcp_mempool.append((tx_hash, message)) # Rollback. raise MempoolError except exceptions.ParseTransactionError as e: logger.warn('ParseTranM sactionError for tx %s: %s' % (tx_hash, e)) except MempoolError: pass write mempool messages to database. with db: cursor.execute('''DELETE FROM mempool''') for message in xcp_mempool: tx_hash, new_message = message new_message['tx_hash'] = tx_hash cursor.execute('''INSERT INTO mempool VALUES(:tx_hash, :command, :category, :bindings, :timestamp)''', new_messM elapsed_time = time.time() - start_time sleep_time = config.BACKEND_POLL_INTERVAL - elapsed_time if elapsed_time <= config.BACKEND_POLL_INTERVAL else 0 logger.getChild('mempool').debug('Refresh mempool: %s XCP txs seen, out of %s total entries (took %ss, next refresh in %ss)' % ( len(xcp_mempool), len(raw_mempool), "{:.2f}".format(elapsed_time, 3), "{:.2f}".format(sleep_time, 3))) db.wal_checkpM oint(mode=apsw.SQLITE_CHECKPOINT_PASSIVE) time.sleep(sleep_time) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 logger = logging.getLogger(__name__) from counterpartylib.lib import config from counterpartylib.lib import util from counterpartylib.lib import exceptions from counterpartylib.lib import backend from counterpartylib.lib import database CONSENSUS_HASH_SEED = M 'We can only see a short distance ahead, but we can see plenty there that needs to be done.' CONSENSUS_HASH_VERSION_MAINNET = 2 CHECKPOINTS_MAINNET = { config.BLOCK_FIRST_MAINNET: {'ledger_hash': '766ff0a9039521e3628a79fa669477ade241fc4c0ae541c3eae97f34b547b0b7', 'txlist_hash': '766ff0a9039521e3628a79fa669477ade241fc4c0ae541c3eae97f34b547b0b7'}, 280000: {'ledger_hash': '265719e2770d5a6994f6fe49839069183cd842ee14f56c2b870e56641e8a8725', 'txlist_hash': 'a59b33b4633649db4f14586af47e258ed9b8884dbb7aa308fb1f49aM 290000: {'ledger_hash': '4612ed7034474b4ff1727eb0e216d533ebe7ac755fb015e0f9a170c063f3e84c', 'txlist_hash': 'c15423c849fd360d38cbd6c6c3ea37a07fece723da92353f3056facc2676d9e7'}, 300000: {'ledger_hash': '9a3dd4949780404d61e5ca1929f94a43f08eb0fa19ccb4b5d6a61cafd7943199', 'txlist_hash': 'efa02dbdcc4158a598e3b476ece5ba9cc8d26f3abc8ac3777ac6dde0f0afc7e6'}, 310000: {'ledger_hash': '45e43d5cc77ea01129df01d7f55b0c89b2d4e18cd3d626fd92f30bfb37a85f4d', 'txlist_hash': '83cdcf75833d828ded09979b601fde87e2fM db0f5eb1cc6ab5d2042b7ec85f90e'}, 320000: {'ledger_hash': '91c1d33626669e8098bc762b1a9e3f616884e4d1cadda4881062c92b0d3d3e98', 'txlist_hash': '761793042d8e7c80e14a16c15bb9d40e237c468a87c207a59730b616bdfde7d4'}, 330000: {'ledger_hash': 'dd56aa97e5ca15841407f383ce1d7814536a594d7cfffcb4cf60bee8b362065a', 'txlist_hash': '3c45b4377a99e020550a198daa45c378c488a72ba199b53deb90b320d55a897b'}, 334000: {'ledger_hash': '24c4fa4097106031267439eb9fbe8ce2a18560169c67726652b608908c1ca9bb', 'txlist_hash': '764ca9e8d3b9546M d1c4ff441a39594548989f60daefc6f28e046996e76a273bf'}, 335000: {'ledger_hash': 'e57c9d606a615e7e09bf99148596dd28e64b25cd8b081e226d535a64c1ed08d1', 'txlist_hash': '437d9507185b5e193627edf4998aad2264755af8d13dd3948ce119b32dd50ce2'}, 336000: {'ledger_hash': '1329ff5b80d034b64f6ea3481b7c7176437a8837b2a7cb7b8a265fdd1397572d', 'txlist_hash': '33eb8cacd4c750f8132d81e8e43ca13bd565f1734d7d182346364847414da52f'}, 337000: {'ledger_hash': '607e6a93e8d97cefea9bd55384898ee90c8477ded8a46017f2294feedbc83409', 'txlist_hasM h': '20b535a55abcc902ca70c19dd648cbe5149af8b4a4157b94f41b71fc422d428e'}, 338000: {'ledger_hash': 'f043914c71e4b711abb1c1002767b9a4e7d605e249facaaf7a2046b0e9741204', 'txlist_hash': 'fa2c3f7f76345278271ed5ec391d582858e10b1f154d9b44e5a1f4896400ee46'}, 339000: {'ledger_hash': '49f7240bc90ebc2f242dd599c7d2c427b9d2ac844992131e6e862b638ae4393a', 'txlist_hash': 'c1e3b497c054dcf67ddd0dc223e8b8a6e09a1a05bacb9fef5c03e48bd01e64e7'}, 340000: {'ledger_hash': '255760e2abfb79fdd76b65759f1590f582c1747f3eeccc4b2ae37d23e3M 0e0729', 'txlist_hash': '8502004bb63e699b243ac8af072d704c69b817905e74787c2031af971e8cd87c'}, 341000: {'ledger_hash': '1369cba3909e564d2e725879a8b2cd987df075db121d1d421c8ce16b65f4bf04', 'txlist_hash': 'd217d0bed190cb27f58fcb96b255f8006bc4b9ed739e1bb08507201c49c426c8'}, 342000: {'ledger_hash': '9e7e9b8620717189ccea697ff2f84fe71bc4ae8d991481ff235164d72a9e6e4f', 'txlist_hash': 'adf75d023760101b2b337f6359dd811b12521c83837eb3f7db3bbfd0b095aa54'}, 343000: {'ledger_hash': 'aa47312ebe94b35504bec6c74713e404e5f368M 54e0836839344d13debe50558c', 'txlist_hash': '6bdbbc96364b3c92cea132fe66a0925f9445a249f7062326bdcc4ad4711f0c01'}, 344000: {'ledger_hash': '40187263aa96d1362bf7b19c8ba0fff7f0c0f3eb132a40fc90601b5926c7e6e3', 'txlist_hash': '98da8efe705c4b54275bfd25f816a7e7a4ff1f67647e17d7a0aaa2a3fef8bda0'}, 345000: {'ledger_hash': 'e4a1e1be4beea63d9740ca166b75bb4e3ffa2af33e1fe282e5b09c4952a7448c', 'txlist_hash': '777f163eaa5ad79dcb738871d4318a0699defec469d8afe91ab6277ff8d3e8b8'}, 350000: {'ledger_hash': '6a67e9f2e9d07e7bb3M 277cf9c24f84c857ed1b8fff4a37e589cd56ade276dd95', 'txlist_hash': '96bcbdbce74b782a845d4fda699846d2d3744044c2870a413c018642b8c7c3bf'}, 355000: {'ledger_hash': 'a84b17992217c7845e133a8597dac84eba1ee8c48bcc7f74bcf512837120f463', 'txlist_hash': '210d96b42644432b9e1a3433a29af9acb3bad212b67a7ae1dbc011a11b04bc24'}, 360000: {'ledger_hash': 'ddca07ea43b336b703fb8ebab6c0dc30582eb360d6f0eb0446e1fe58b53dee0a', 'txlist_hash': '31d0ff3e3782cf9464081829c5595b3de5ac477290dc069d98672f3f552767f8'}, 365000: {'ledger_hash':M '2d55b126cca3eca15c07b5da683988f9e01d7346d2ca430e940fd7c07ce84fd7', 'txlist_hash': '7988a823cc1e3234953cc87d261d3c1fede8493d0a31b103357eb23cc7dc2eda'}, 366000: {'ledger_hash': '64ce274df2784f9ca88a8d7071613ec6527e506ec31cd434eca64c6a3345a6b7', 'txlist_hash': '0d4374da6100e279b24f4ba4a2d6afbfc4fb0fc2d312330a515806e8c5f49404'}, 370000: {'ledger_hash': 'fabb2a2e91fad3fe7734169d554cca396c1030243044cef42fcf65717cf0fa61', 'txlist_hash': '41d1732868c9ac25951ace5ca9f311a15d5eca9bf8d548e0d988c050bd2aff87'}, 000: {'ledger_hash': 'a7ac4e2948cea0c426c8fc201cf57d9c313027ea7bff2b32a25ed28d3dbaa581', 'txlist_hash': '96118a7aa2ca753488755b7419a0f44a7fbc371bc58dcc7ab083c70fc14ef8b3'}, 380000: {'ledger_hash': '70453ba04c1c0198c4771e7964cffa25f9456c2f71456a8b05dfe935d5fcdc88', 'txlist_hash': '8bf2070103cca6f0bde507b7d20b0ba0630da6349beb560fa64c926d08dbcaef'}, 385000: {'ledger_hash': '93eb0a6e820bee197e7591edbc5ead7bfa38f32c88aabf4785f080fd6ae96c4c', 'txlist_hash': '1f8f17fd5766382a8c10a2a0e995a7d5a5d1bcd5fc0220d1e2691b2M 390000: {'ledger_hash': '7d42b98eecbc910a67a5f4ac8dc7d6d9b6995ebc5bdf53663b414965fe7d2c5e', 'txlist_hash': 'b50efc4a4241bf3ec33a38c3b5f34756a9f305fe5fa9a80f7f9b70d5d7b2a780'}, 395000: {'ledger_hash': '89f9ac390b35e69dd75d6c34854ba501dce2f662fc707aee63cad5822c7660f2', 'txlist_hash': '2151dd2f0aa14685f3d041727a689d5d242578072a049123b317724fc4f1100c'}, 400000: {'ledger_hash': 'eb681a305125e04b6f044b36045e23ee248ce4eb68433cea2b36d15e7e74d5f1', 'txlist_hash': 'b48e9501e8d6f1f1b4127d868860885d3dbM 76698c2c31a567777257df101cf61'}, 405000: {'ledger_hash': '3725055b37a8958ade6ca1c277cf50fee6036b4a92befb8da2f7c32f0b210881', 'txlist_hash': '871b2adfd246e3fe69f0fe9098e3251045ed6e9712c4cf90ea8dfdd1eb330ed6'}, 410000: {'ledger_hash': '1fa9a34f233695ebd7ebb08703bf8d99812fa099f297efc5d307d1ebef902ffd', 'txlist_hash': 'ee3bd84c728a37e2bbe061c1539c9ee6d71db18733b1ed53ee8d320481f55030'}, 415000: {'ledger_hash': '6772a8a1c784db14c0bf111e415919c9da4e5ca142be0b9e323c82c1b13c74e0', 'txlist_hash': 'cfb81785cd48e9bM a0e54fee4d62f49b347489da82139fd5e1555ae0bc11a33d5'}, 420000: {'ledger_hash': '42167117e16943f44bb8117aa0a39bed2d863a454cd694d0bc5006a7aab23b06', 'txlist_hash': 'a1139870bef8eb9bbe60856029a4f01fce5432eb7aeacd088ba2e033757b86e3'}, CONSENSUS_HASH_VERSION_TESTNET = 7 CHECKPOINTS_TESTNET = { config.BLOCK_FIRST_TESTNET: {'ledger_hash': '63f0fef31d02da85fa779e9a0e1b585b1a6a4e59e14564249e288e074e91c223', 'txlist_hash': '63f0fef31d02da85fa779e9a0e1b585b1a6a4e59e14564249e288e074e91c223'}, 316000: {'ledger_hashM ': 'f645e6877da416b8b91670ac927df686c5ea6fc1158c150ae49d594222ed504c', 'txlist_hash': '3e29bcbf3873326097024cc26e9296f0164f552dd79c2ee7cfc344e6d64fa87d'}, 319000: {'ledger_hash': '384ca28ac56976bc24a6ab7572b41bc61474e6b87fdee814135701d6a8f5c8a2', 'txlist_hash': '6c05c98418a6daa6de82dd59e000d3f3f5407c5432d4ab7d76047873a38e4d4b'}, 322000: {'ledger_hash': 'f4015c37eb4f31ac42083fd0389cde4868acb5353d3f3abfe2f3a88aba8cae72', 'txlist_hash': '18f278154e9bc3bbcc39da905ab4ad3023742ab7723b55b0fd1c58c36cd3e9bf'}, 25000: {'ledger_hash': 'd7f70a927f5aeed38e559ddc0bc4697601477ea43cde928ad228fefc195b02da', 'txlist_hash': '1a60e38664b39e0f501b3e5a60c6fc0bd4ed311b74872922c2dde4cb2267fd3e'}, 329000: {'ledger_hash': '96637b4400cbe084c2c4f139f59b5bc16770815e96306423aaeb2b2677a9a657', 'txlist_hash': '79d577d8fbba0ad6ae67829dfa5824f758286ccd429d65b7d1d42989134d5b57'}, 350000: {'ledger_hash': 'cae8fec787bba3d5c968a8f4b6fb22a54c96d5acbeadd0425f6b20c3a8813ea3', 'txlist_hash': '097df9c3079df4d96f59518df72492dfd7a79716462e3a4a30d62M 400000: {'ledger_hash': '94abfd9c00c8462c155f64011e71af141b7d524e17de5aeda26b7469fe79b5f0', 'txlist_hash': 'a9fc42b69f80ec69f3f98e8a3cd81f4f946544fd0561a62a0891254c16970a87'}, 450000: {'ledger_hash': '09eb9f2aa605ce77225362b4b556284acdd9f6d3bc273372dfae4a5be9e9b035', 'txlist_hash': '05af651c1de49d0728834991e50000fbf2286d7928961b71917f682a0f2b7171'}, 500000: {'ledger_hash': '85f3bca8c88246ddfa1a5ec327e71f0696c182ed2a5fedf3712cd2e87e2661ac', 'txlist_hash': '663b34955116a96501e0c1c27f27d24baM d7d45995913367553c5cfe4b8b9d0a9'}, 550000: {'ledger_hash': 'c143026133af2d83bc49ef205b4623194466ca3e7c79f95da2ad565359ccb5ad', 'txlist_hash': '097b8bca7a243e0b9bdf089f34de15bd2dcd4727fb4e88aae7bfd96302250326'}, 600000: {'ledger_hash': '82caf720967d0e43a1c49a6c75f255d9056ed1bffe3f96d962478faccdaba8ff', 'txlist_hash': '0d99f42184233426d70102d5ac3c80aaecf804d441a8a0d0ef26038d333ab7a7'}, 650000: {'ledger_hash': 'bef100ae7d5027a8b3f32416c4f26e1f16b21cee2a986c57be1466a3ba338051', 'txlist_hash': '409ed86e4274bM 511193d187df92e433c734dcc890bf93496e7a7dee770e7035e'}, 700000: {'ledger_hash': 'afe5e9c3f3a8c6f19c4f9feaf09df051c28202c6bae64f3563a09ffea9e79a6e', 'txlist_hash': '4f9765158855d24950c7e076615b0ad5b72738d4d579decfd3b93c998edf4fcb'}, 750000: {'ledger_hash': 'e7c7969a6156facb193b77ef71b5e3fac49c6998e5a94ec3b90292be10ece9cc', 'txlist_hash': '6e511790656d3ffec0c912d697e5d1c2a4e401a1606203c77ab5a5855891bc2c'}, 800000: {'ledger_hash': '42a7c679e51e5e8d38df26b67673b4850e8e6f72723aa19673b3219fcc02b77b', 'txlist_hM ash': '885ae1e6c21f5fb3645231aaa6bb6910fc21a0ae0ca5dbe9a4011f3b5295b3e7'}, 850000: {'ledger_hash': '35b2a2ab4a8bfbc321d4545292887b4ccaea73415c7674f795aefa6e240890eb', 'txlist_hash': '72d5cfe1e729a22da9eacd4d7752c881c43a191904556b65a0fae82b770dcdf3'}, 900000: {'ledger_hash': 'a5552b4998d2e5a516b9310d6592e7368771c1ad3b6e6330f6bc0baa3db31643', 'txlist_hash': '5a2e9fbd9b52ee32b8e8bfff993ed92dc22510aa7448277a704176cf01e55b04'}, 950000: {'ledger_hash': '5a5e78b55ac294690229abff7ff8f74f390f3a47dc4d08a0bac40e2eM 89a5bed2', 'txlist_hash': 'f4fa9838fb38d3e5beffb760fae022dcc59c61c506dd28ac83ee48ba814d04b2'}, 1000000: {'ledger_hash': 'eafca6700b9fd8f3992f8a18316e9ad59480ef74a4e7737793c101878aba8e1a', 'txlist_hash': '03deb626e031f30acd394bf49c35e11a487cb11e55dff5ba9a3f6d04b460c7de'}, 1050000: {'ledger_hash': '8012ebaf4c6638173e88ecd3e7bb2242ab88a9bdf877fc32c42dbcd7d2d3bab1', 'txlist_hash': '896274fdba957961083b07b80634126bc9f0434b67d723ed1fa83157ce5cd9a7'}, 1100000: {'ledger_hash': '76357f917235daa180c904cdf5c44366eM ef3e33539b7b0ba6a38f89582e82d22', 'txlist_hash': '36ecfd4b07f23176cd6960bc0adef97472c13793e53ac3df0eea0dd2e718a570'}, 1150000: {'ledger_hash': '5924f004bfdc3be449401c764808ebced542d2e06ba30c5984830292d1a926aa', 'txlist_hash': '9ff139dacf4b04293074e962153b972d25fa16d862dae05f7f3acc15e83c4fe8'}, 1200000: {'ledger_hash': 'a3d009bd2e0b838c185b8866233d7b4edaff87e5ec4cc4719578d1a8f9f8fe34', 'txlist_hash': '11dcf3a0ab714f05004a4e6c77fe425eb2a6427e4c98b7032412ab29363ffbb2'}, 1250000: {'ledger_hash': '37244453b4M eac67d1dbfc0f60116cac90dab7b814d756653ad3d9a072fbac61a', 'txlist_hash': 'c01ed3113f8fd3a6b54f5cefafd842ebf7c314ce82922e36236414d820c5277a'}, 1300000: {'ledger_hash': 'a83c1cd582604130fd46f1304560caf0f4e3300f3ce7c3a89824b8901f13027f', 'txlist_hash': '67e663b75a80940941b8370ada4985be583edaa7ba454d49db9a864a7bb7979c'}, 1350000: {'ledger_hash': 'f96e6aff578896a4568fb69f72aa0a8b52eb9ebffefca4bd7368790341cd821d', 'txlist_hash': '83e7d31217af274b13889bd8b9f8f61afcd7996c2c8913e9b53b1d575f54b7c1'}, dger_hash': '85a23f6fee9ce9c80fa335729312183ff014920bbf297095ac77c4105fb67e17', 'txlist_hash': 'eee762f34a3f82e6332c58e0c256757d97ca308719323af78bf5924f08463e12'}, CONSENSUS_HASH_VERSION_REGTEST = 1 CHECKPOINTS_REGTEST = { config.BLOCK_FIRST_REGTEST: {'ledger_hash': '33cf0669a0d309d7e6b1bf79494613b69262b58c0ea03c9c221d955eb4c84fe5', 'txlist_hash': '33cf0669a0d309d7e6b1bf79494613b69262b58c0ea03c9c221d955eb4c84fe5'}, class ConsensusError(Exception): def consensus_hash(db, field, previous_consensusM cursor = db.cursor() block_index = util.CURRENT_BLOCK_INDEX # Initialise previous hash on first block. if block_index <= config.BLOCK_FIRST: assert not previous_consensus_hash previous_consensus_hash = util.dhash_string(CONSENSUS_HASH_SEED) # Get previous hash. if not previous_consensus_hash: previous_consensus_hash = list(cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index - 1,)))[0][field] previous_consensus_hash = None if not previous_consensus_hash: raise ConsensusError('Empty previous {} for block {}. Please launch a `reparse`.'.format(field, block_index)) # Calculate current hash. if config.TESTNET: consensus_hash_version = CONSENSUS_HASH_VERSION_TESTNET elif config.REGTEST: consensus_hash_version = CONSENSUS_HASH_VERSION_REGTEST consensus_hash_version = CONSENSUS_HASH_VERSION_MAINNET calculated_hash =M util.dhash_string(previous_consensus_hash + '{}{}'.format(consensus_hash_version, ''.join(content))) # Verify hash (if already in database) or save hash (if not). # NOTE: do not enforce this for messages_hashes, those are more informational (for now at least) found_hash = list(cursor.execute('''SELECT * FROM blocks WHERE block_index = ?''', (block_index,)))[0][field] or None if found_hash and field != 'messages_hash': # Check against existing value. if calculated_hash != found_hashM raise ConsensusError('Inconsistent {} for block {} (calculated {}, vs {} in database).'.format( field, block_index, calculated_hash, found_hash)) # Save new hash. cursor.execute('''UPDATE blocks SET {} = ? WHERE block_index = ?'''.format(field), (calculated_hash, block_index)) # Check against checkpoints. if config.TESTNET: checkpoints = CHECKPOINTS_TESTNET elif config.REGTEST: checkpoints = CHECKPOINTS_REGTEST eckpoints = CHECKPOINTS_MAINNET if field != 'messages_hash' and block_index in checkpoints and checkpoints[block_index][field] != calculated_hash: raise ConsensusError('Incorrect {} hash for block {}. Calculated {} but expected {}'.format(field, block_index, calculated_hash, checkpoints[block_index][field],)) return calculated_hash, found_hash class SanityError(Exception): def asset_conservation(db): logger.debug('Checking for conservation of assets.') supplies = util.supplies(M held = util.held(db) for asset in supplies.keys(): asset_issued = supplies[asset] asset_held = held[asset] if asset in held and held[asset] != None else 0 if asset_issued != asset_held: raise SanityError('{} {} issued {} {} held'.format(util.value_out(db, asset_issued, asset), asset, util.value_out(db, asset_held, asset), asset)) logger.debug('{} has been conserved ({} {} both issued and held)'.format(asset, util.value_out(db, asset_issued, asset), asset))M class VersionError(Exception): class VersionUpdateRequiredError(VersionError): def check_change(protocol_change, change_name): # Check client version. if config.VERSION_MAJOR < protocol_change['minimum_version_major']: passed = False elif config.VERSION_MAJOR == protocol_change['minimum_version_major']: if config.VERSION_MINOR < protocol_change['minimum_version_minor']: passed = False elif config.VERSION_MINOR == protocol_changeM ['minimum_version_minor']: if config.VERSION_REVISION < protocol_change['minimum_version_revision']: passed = False explanation = 'Your version of {} is v{}, but, as of block {}, the minimum version is v{}.{}.{}. Reason: . Please upgrade to the latest version and restart the server.'.format( config.APP_NAME, config.VERSION_STRING, protocol_change['block_index'], protocol_change['minimum_version_major'], protocol_change['minimum_version_minoM protocol_change['minimum_version_revision'], change_name) if util.CURRENT_BLOCK_INDEX >= protocol_change['block_index']: raise VersionUpdateRequiredError(explanation) warnings.warn(explanation) def software_version(): if config.FORCE: logger.debug('Checking version.') host = 'https://counterpartyxcp.github.io/counterparty-lib/counterpartylib/protocol_changes.json' response = requests.get(host, headers={'M cache-control': 'no-cache'}) versions = json.loads(response.text) except (requests.exceptions.ConnectionError, ConnectionRefusedError, ValueError) as e: logger.warning('Unable to check version! ' + str(sys.exc_info()[1])) for change_name in versions: protocol_change = versions[change_name] check_change(protocol_change, change_name) except VersionUpdateRequiredError as e: logger.error("Version Update Required", exc_info=sys.M sys.exit(config.EXITCODE_UPDATE_REQUIRED) logger.debug('Version check passed.') class DatabaseVersionError(Exception): def __init__(self, message, reparse_block_index): super(DatabaseVersionError, self).__init__(message) self.reparse_block_index = reparse_block_index def database_version(db): if config.FORCE: logger.debug('Checking database version.') version_major, version_minor = database.version(db) if version_major != config.VERM # Rollback database if major version has changed. raise DatabaseVersionError('Client major version number mismatch ({} {}).'.format(version_major, config.VERSION_MAJOR), config.BLOCK_FIRST) elif version_minor != config.VERSION_MINOR: # Reparse all transactions if minor version has changed. raise DatabaseVersionError('Client minor version number mismatch ({} {}).'.format(version_minor, config.VERSION_MINOR), None) # vim: tabstop=8 expandtab shiftwidth=4 softtaM """Variables prefixed with `DEFAULT` should be able to be overridden by configuration file and command UNIT = 100000000 # The same across assets. VERSION_REVISION = 1 VERSION_STRING = str(VERSION_MAJOR) + '.' + str(VERSION_MINOR) + '.' + str(VERSION_REVISION) # Counterparty protocol TXTYPE_FORMAT = '>I' SHORT_TXTYPE_FORMAT = 'B' TWO_WEEKS = 2 * 7 * 24 * 3600 MAX_EXPIRATION = 4 * 2016 # TM MEMPOOL_BLOCK_HASH = 'mempool' MEMPOOL_BLOCK_INDEX = 9999999 OP_RETURN_MAX_SIZE = 80 # bytes # Currency agnosticism BTC_NAME = 'Bitcoin' XCP_NAME = 'Counterparty' APP_NAME = XCP_NAME.lower() DEFAULT_RPC_PORT_REGTEST = 24000 DEFAULT_RPC_PORT_TESTNET = 14000 DEFAULT_RPC_PORT = 4000 DEFAULT_BACKEND_PORT_REGTEST = 28332 DEFAULT_BACKEND_PORT_TESTNET = 18332 DEFAULT_BACKEND_PORT = 8332 DEFAULT_INDEXD_PORT_REGTEST = 28432 DEXD_PORT_TESTNET = 18432 DEFAULT_INDEXD_PORT = 8432 UNSPENDABLE_REGTEST = 'mvCounterpartyXXXXXXXXXXXXXXW24Hef' UNSPENDABLE_TESTNET = 'mvCounterpartyXXXXXXXXXXXXXXW24Hef' UNSPENDABLE_MAINNET = '1CounterpartyXXXXXXXXXXXXXXXUWLpVr' ADDRESSVERSION_TESTNET = b'\x6f' P2SH_ADDRESSVERSION_TESTNET = b'\xc4' PRIVATEKEY_VERSION_TESTNET = b'\xef' ADDRESSVERSION_MAINNET = b'\x00' P2SH_ADDRESSVERSION_MAINNET = b'\x05' PRIVATEKEY_VERSION_MAINNET = b'\x80' ADDRESSVERSION_REGTEST = b'\x6f' P2SH_ADDRESSVERSION_REGTEST = b'\xc4' RIVATEKEY_VERSION_REGTEST = b'\xef' MAGIC_BYTES_TESTNET = b'\xfa\xbf\xb5\xda' # For bip-0010 MAGIC_BYTES_MAINNET = b'\xf9\xbe\xb4\xd9' # For bip-0010 MAGIC_BYTES_REGTEST = b'\xda\xb5\xbf\xfa' BLOCK_FIRST_TESTNET_TESTCOIN = 310000 BURN_START_TESTNET_TESTCOIN = 310000 BURN_END_TESTNET_TESTCOIN = 4017708 # Fifty years, at ten minutes per block. BLOCK_FIRST_TESTNET = 310000 BLOCK_FIRST_TESTNET_HASH = '000000001f605ec6ee8d2c0d21bf3d3ded0a31ca837acc98893876213828989d' BURN_START_TESTNET = 310000 T = 4017708 # Fifty years, at ten minutes per block. BLOCK_FIRST_MAINNET_TESTCOIN = 278270 BURN_START_MAINNET_TESTCOIN = 278310 BURN_END_MAINNET_TESTCOIN = 2500000 # A long time. BLOCK_FIRST_MAINNET = 278270 BLOCK_FIRST_MAINNET_HASH = '00000000000000017bac9a8e85660ad348050c789922d5f8fe544d473368be1a' BURN_START_MAINNET = 278310 BURN_END_MAINNET = 283810 BLOCK_FIRST_REGTEST = 0 BLOCK_FIRST_REGTEST_HASH = '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206' BURN_START_REGTEST = 101 URN_END_REGTEST = 150000000 BLOCK_FIRST_REGTEST_TESTCOIN = 0 BURN_START_REGTEST_TESTCOIN = 101 BURN_END_REGTEST_TESTCOIN = 150 # NOTE: If the DUST_SIZE constants are changed, they MUST also be changed in counterblockd/lib/config.py as well DEFAULT_REGULAR_DUST_SIZE = 546 # TODO: Revisit when dust size is adjusted in bitcoin core DEFAULT_MULTISIG_DUST_SIZE = 7800 # <https://bitcointalk.org/index.php?topic=528023.msg7469941#msg7469941> DEFAULT_OP_RETURN_VALUE = 0 KB_ESTIMATE_SMART = 1024 DEFAULT_FEE_PER_KB = 25000 # sane/low default, also used as minimum when estimated fee is used ESTIMATE_FEE_PER_KB = True # when True will use `estimatesmartfee` from bitcoind instead of DEFAULT_FEE_PER_KB ESTIMATE_FEE_CONF_TARGET = 3 ESTIMATE_FEE_MODE = 'CONSERVATIVE' DEFAULT_FEE_FRACTION_REQUIRED = .009 # 0.90% DEFAULT_FEE_FRACTION_PROVIDED = .01 # 1.00% DEFAULT_REQUESTS_TIMEOUT = 20 # 20 seconds DEFAULT_RPC_BATCH_SIZE = 20 # A 1 MB M block can hold about 4200 transactions. EXITCODE_UPDATE_REQUIRED = 5 DEFAULT_CHECK_ASSET_CONSERVATION = True BACKEND_RAW_TRANSACTIONS_CACHE_SIZE = 20000 BACKEND_RPC_BATCH_NUM_WORKERS = 6 UNDOLOG_MAX_PAST_BLOCKS = 100 #the number of past blocks that we store undolog history DEFAULT_UTXO_LOCKS_MAX_ADDRESSES = 1000 DEFAULT_UTXO_LOCKS_MAX_AGE = 3.0 #in seconds ADDRESS_OPTION_REQUIRE_MEMO = 1 ADDRESS_OPTION_MAX_VALUE = ADDRESS_OPTION_REQUIRE_MEMO # Or list of all the address options API_LIMIT_ROWS = 1000 MEMPOOL_TXCOUNT_UPDATE_LIMIT=60000 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 logger = logging.getLogger(__name__) from counterpartylib.lib import config from counterpartylib.lib import util from counterpartylib.lib import exceptions from counterpartylib.lib import log def rowtracer(cursor, sql): """Converts fetched SQL data intM for index, (name, type_) in enumerate(cursor.getdescription()): dictionary[name] = sql[index] return dictionary def exectracer(cursor, sql, bindings): # This means that all changes to database must use a very simple syntax. # TODO: Need sanity checks here. sql = sql.lower() if sql.startswith('create trigger') or sql.startswith('drop trigger'): #CREATE TRIGGER stmts may include an "insert" or "update" as part of them array = sql.split('(')[0].split(' ') command = array[0] if 'insert' in sql: category = array[2] elif 'update' in sql: category = array[1] #CREATE TABLE, etc db = cursor.getconnection() dictionary = {'command': command, 'category': category, 'bindings': bindings} 'blocks', 'transactions', 'balances', 'messages', 'mempool', 'assets', 'new_sends', 'new_issuances' # interim table fM skip_tables_block_messages = copy.copy(skip_tables) if command == 'update': # List message manually. skip_tables += ['orders', 'bets', 'rps', 'order_matches', 'bet_matches', 'rps_matches'] # Record alteration in database. if category not in skip_tables: log.message(db, bindings['block_index'], command, category, bindings) # Record alteration in computation of message feed hash for the block if category not in skip_tables_block_messages: # don't include asset_longname as part of the messages hash # until subassets are enabled if category == 'issuances' and not util.enabled('subassets'): if isinstance(bindings, dict) and 'asset_longname' in bindings: del bindings['asset_longname'] # don't include memo as part of the messages hash # until enhanced_sends are enabled if category == 'sends' and not util.enabled('enhanced_sends'): if isinstance(bindings, dict) and 'memo' in bindingsM : del bindings['memo'] sorted_bindings = sorted(bindings.items()) if isinstance(bindings, dict) else [bindings,] BLOCK_MESSAGES.append('{}{}{}'.format(command, category, sorted_bindings)) class DatabaseIntegrityError(exceptions.DatabaseError): def get_connection(read_only=True, foreign_keys=True, integrity_check=True): """Connects to the SQLite database, returning a db `Connection` object""" logger.debug('Creating connection to `{}`.'.format(config.DATABASE)) db = apsw.Connection(config.DATABASE, flags=apsw.SQLITE_OPEN_READONLY) db = apsw.Connection(config.DATABASE) cursor = db.cursor() # For integrity, security. if foreign_keys and not read_only: logger.info('Checking database foreign keys...') cursor.execute('''PRAGMA foreign_keys = ON''') cursor.execute('''PRAGMA defer_foreign_keys = ON''') rows = list(cursor.execute('''PRAGMA foreign_key_check''')) logger.debug('Foreign Key Error: {}'.format(row)) raise exceptions.DatabaseError('Foreign key check failed.') # So that writers don cursor.execute('''PRAGMA journal_mode = WAL''') logger.info('Foreign key check completed.') # Make case sensitive the `LIKE` operator. # For insensitive queries use 'UPPER(fieldname) LIKE value.upper()'' cursor.execute('''PRAGMA case_sensitive_like = ON''') if integrity_check: logger.info('Checking database integrity...') integral = False for i in range(10): # DUPE cursor.execute('''PRAGMA integrity_check''') rows = cursor.fetchall() if not (len(rows) == 1 and rows[0][0] == 'ok'): raise exceptions.DatabaseError('Integrity check failed.') integral = True break except DatabaseIntegrityError: time.sleep(1) continue if not integral: raise exceptions.DatabaseError('Could not perform integrity check.') logger.info('Integrity check completed.') db.setrowtrace(rowtracer) db.setexectrace(exectracer) cursor = db.cursor() user_version = cursor.execute('PRAGMA user_version').fetchall()[0]['user_version'] # manage old user_version if user_version == config.VERSION_MINOR: version_minor = user_version version_major = cM user_version = (config.VERSION_MAJOR * 1000) + version_minor cursor.execute('PRAGMA user_version = {}'.format(user_version)) version_minor = user_version % 1000 version_major = user_version // 1000 return version_major, version_minor def update_version(db): cursor = db.cursor() user_version = (config.VERSION_MAJOR * 1000) + config.VERSION_MINOR cursor.execute('PRAGMA user_version = {}'.format(user_version)) # Syntax?! logger.info('DaM tabase version number updated.') logger.info('Starting database VACUUM. This may take awhile...') cursor = db.cursor() cursor.execute('VACUUM') logger.info('Database VACUUM completed.') # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 class DatabaseError(Exception): class TransactionError(Exception): class ParseTransactionError(Exception): class AssetError (Exception): class AssetIDError(AssetError): class MessageError(Exception): class ComposeError(MessageError): class UnpackError(MessageError): class ValidateError(MessageError): class DecodeError(MessageError): class PushDataDecodeError(DecodeError): class BTCOnlyError(MessageError): def __init__(self, msg, decodedTx=None): super(BTCOnlyError, self).__init__(msg) self.decodedTx = decodedTx class BalanceError(ExceM class EncodingError(Exception): class OptionsError(Exception): # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 logger = logging.getLogger(__name__) from datetime import datetime from dateutil.tz import tzlocal from colorlog import ColoredFormatter from counterpartylib.lib import config from counterpartylib.lib import exceptions m counterpartylib.lib import util class ModuleLoggingFilter(logging.Filter): module level logging filter (NodeJS-style), ie: filters="*,-counterpartylib.lib,counterpartylib.lib.api" - counterpartycli.server - counterpartylib.lib.api but will not log: - counterpartylib.lib - counterpartylib.lib.backend.indexd def __init__(self, filters): self.filters = str(filters).split(",") self.catchall = "*" in selfM if self.catchall: self.filters.remove("*") def filter(self, record): Determine if specified record should be logged or not result = None for filter in self.filters: if filter[:1] == "-": if result is None and ModuleLoggingFilter.ismatch(record, filter[1:]): result = False if ModuleLoggingFilter.ismatch(record, filter): result = True if result is None: return self.catchall return result def ismatch(cls, record, name): Determine if the specified record matches the name, in the same way as original logging.Filter does, ie: 'counterpartylib.lib' will match 'counterpartylib.lib.check' nlen = len(name) if nlen == 0: return True elif name == record.name: return True elif record.name.find(name, 0, nlenM return False return record.name[nlen] == "." def set_logger(logger): global ROOT_LOGGER if ROOT_LOGGER is None: ROOT_LOGGER = logger LOGGING_SETUP = False LOGGING_TOFILE_SETUP = False def set_up(logger, verbose=False, logfile=None, console_logfilter=None): global LOGGING_SETUP global LOGGING_TOFILE_SETUP def set_up_file_logging(): assert logfile max_log_size = 20 * 1024 * 1024 # 20 MB if os.name == 'nt': from counterpartylib.lib import util_windows fileh = util_windows.SanitizedRotatingFileHandler(logfile, maxBytes=max_log_size, backupCount=5) fileh = logging.handlers.RotatingFileHandler(logfile, maxBytes=max_log_size, backupCount=5) fileh.setLevel(logging.DEBUG) LOGFORMAT = '%(asctime)s [%(levelname)s] %(message)s' formatter = logging.Formatter(LOGFORMAT, '%Y-%m-%d-T%H:%M:%S%z') fileh.setFormatter(formatter) logger.addHandler(fileh) if LOGGING_SETUP: if logfile and not LOGGING_TOFILE_SETUP: set_up_file_logging() LOGGING_TOFILE_SETUP = True logger.getChild('log.set_up').debug('logging already setup') LOGGING_SETUP = True log_level = logging.DEBUG if verbose else logging.INFO logger.setLevel(log_level) # Console Logging console = logging.StreamHandler() console.setLevel(log_level) # only add [%(name)s] to LOGFORMAT if we're using console_logfilter GFORMAT = '%(log_color)s[%(asctime)s][%(levelname)s]' + ('' if console_logfilter is None else '[%(name)s]') + ' %(message)s%(reset)s' LOGCOLORS = {'WARNING': 'yellow', 'ERROR': 'red', 'CRITICAL': 'red'} formatter = ColoredFormatter(LOGFORMAT, "%Y-%m-%d %H:%M:%S", log_colors=LOGCOLORS) console.setFormatter(formatter) logger.addHandler(console) if console_logfilter: console.addFilter(ModuleLoggingFilter(console_logfilter)) set_up_file_logging() LOGGING_TOFILE_SETUP = True # Quieten noisy libraries. requests_log = logging.getLogger("requests") requests_log.setLevel(log_level) requests_log.propagate = False urllib3_log = logging.getLogger('urllib3') urllib3_log.setLevel(log_level) urllib3_log.propagate = False # Disable InsecureRequestWarning requests.packages.urllib3.disable_warnings() return int(time.time()) def isodt (epoch_time): return datetime.froM mtimestamp(epoch_time, tzlocal()).isoformat() return '<datetime>' def message(db, block_index, command, category, bindings, tx_hash=None): cursor = db.cursor() # Get last message index. messages = list(cursor.execute('''SELECT * FROM messages WHERE message_index = (SELECT MAX(message_index) from messages)''')) assert len(messages) == 1 message_index = messages[0]['message_index'] + 1 # Not to be misleading if block_index == config.MEMPOOL_BLOCK_INDEX: del bindings['status'] del bindings['block_index'] del bindings['tx_index'] except KeyError: # Handle binary data. for item in sorted(bindings.items()): if type(item[1]) == bytes: items.append((item[0], binascii.hexlify(item[1]).decode('ascii'))) items.append(item) _string = json.dumps(collections.OrderedDict(items)) cursor.execute('insert into messages values(:message_index, :block_index, :command, :category, :bindings, :timestamp)', (message_index, block_index, command, category, bindings_string, curr_time())) # Log only real transactions. if block_index != config.MEMPOOL_BLOCK_INDEX: log(db, command, category, bindings) def log (db, command, category, bindings): cursor = db.cursor() for element in bindM str(bindings[element]) except KeyError: bindings[element] = '<Error>' def output (quantity, asset): if asset not in ('fraction', 'leverage'): return str(util.value_out(db, quantity, asset)) + ' ' + asset return str(util.value_out(db, quantity, asset)) except exceptions.AssetError: return '<AssetError>' except decimal.DivisionByZero: return '<DivisionByZero>' except TypeError: return '<None>' if command == 'update': if category == 'order': logger.debug('Database: set status of order {} to {}.'.format(bindings['tx_hash'], bindings['status'])) elif category == 'bet': logger.debug('Database: set status of bet {} to {}.'.format(bindings['tx_hash'], bindings['status'])) elif category == 'order_matches': logger.debug('Database: set status of order_match {} to {M }.'.format(bindings['order_match_id'], bindings['status'])) elif category == 'bet_matches': logger.debug('Database: set status of bet_match {} to {}.'.format(bindings['bet_match_id'], bindings['status'])) elif category == 'dispensers': escrow_quantity = '' divisible = get_asset_info(cursor, bindings['asset'])['divisible'] if divisible: if "escrow_quantity" in bindings: escrow_quantity = "{:.8f}".format(bM indings["escrow_quantity"]/config.UNIT) if ("action" in bindings) and bindings["action"] == 'refill dispenser': logger.info("Dispenser: {} refilled a dispenser with {} {}".format(bindings["source"],escrow_quantity,bindings["asset"])) elif "prev_status" in bindings: #There was a dispense if bindings["prev_status"] == 0: if bindings["status"] == 10: logger.info("Dispenser: {} closed dispenser M for {} (dispenser empty)".format(bindings["source"],bindings["asset"])) elif bindings["status"] == 10: #Address closed the dispenser logger.info("Dispenser: {} closed dispenser for {} (operator closed)".format(bindings["source"],bindings["asset"])) # TODO: elif category == 'balances': # logger.debug('Database: set balance of {} in {} to {}.'.format(bindings['address'], bindings['asset'], output(bindings['quantity'], bindings['asset']).split(' ')[0])) if category == 'credits': logger.debug('Credit: {} to {} #{}# <{}>'.format(output(bindings['quantity'], bindings['asset']), bindings['address'], bindings['action'], bindings['event'])) elif category == 'debits': logger.debug('Debit: {} from {} #{}# <{}>'.format(output(bindings['quantity'], bindings['asset']), bindings['address'], bindings['action'], bindings['event'])) elif category == 'sends': logger.info('Send: {} from {} to {} ({}) [{}]M '.format(output(bindings['quantity'], bindings['asset']), bindings['source'], bindings['destination'], bindings['tx_hash'], bindings['status'])) elif category == 'orders': logger.info('Order: {} ordered {} for {} in {} blocks, with a provided fee of {:.8f} {} and a required fee of {:.8f} {} ({}) [{}]'.format(bindings['source'], output(bindings['give_quantity'], bindings['give_asset']), output(bindings['get_quantity'], bindings['get_asset']), bindings['expiration'], bindings['fee_provided'] / coM nfig.UNIT, config.BTC, bindings['fee_required'] / config.UNIT, config.BTC, bindings['tx_hash'], bindings['status'])) elif category == 'order_matches': logger.info('Order Match: {} for {} ({}) [{}]'.format(output(bindings['forward_quantity'], bindings['forward_asset']), output(bindings['backward_quantity'], bindings['backward_asset']), bindings['id'], bindings['status'])) elif category == 'btcpays': logger.info('{} Payment: {} paid {} to {} for order match {} ({}) [{}]'.formM at(config.BTC, bindings['source'], output(bindings['btc_amount'], config.BTC), bindings['destination'], bindings['order_match_id'], bindings['tx_hash'], bindings['status'])) elif category == 'issuances': if (get_asset_issuances_quantity(cursor, bindings["asset"]) == 0) or (bindings['quantity'] > 0): #This is the first issuance or the creation of more supply, so we have to log the creation of the token if bindings['divisible']: divisibility = 'divisible' unit = config.UNIT else: divisibility = 'indivisible' unit = 1 try: quantity = util.value_out(cursor, bindings['quantity'], None, divisible=bindings['divisible']) except Exception as e: quantity = '?' if 'asset_longname' in bindings and bindings['asset_longname'] is not None: logger.info('Subasset Issuance: {} created {} of {}M subasset {} as numeric asset {} ({}) [{}]'.format(bindings['source'], quantity, divisibility, bindings['asset_longname'], bindings['asset'], bindings['tx_hash'], bindings['status'])) else: logger.info('Issuance: {} created {} of {} asset {} ({}) [{}]'.format(bindings['source'], quantity, divisibility, bindings['asset'], bindings['tx_hash'], bindings['status'])) if bindings['locked']: lock_issuance = get_lock_issuance(cursor, bindings["assM if (lock_issuance == None) or (lock_issuance['tx_hash'] == bindings['tx_hash']): logger.info('Issuance: {} locked asset {} ({}) [{}]'.format(bindings['source'], bindings['asset'], bindings['tx_hash'], bindings['status'])) if bindings['transfer']: logger.info('Issuance: {} transfered asset {} to {} ({}) [{}]'.format(bindings['source'], bindings['asset'], bindings['issuer'], bindings['tx_hash'], bindings['status'])) elif category == 'broadcasts': if bindings['locked']: logger.info('Broadcast: {} locked his feed ({}) [{}]'.format(bindings['source'], bindings['tx_hash'], bindings['status'])) logger.info('Broadcast: ' + bindings['source'] + ' at ' + isodt(bindings['timestamp']) + ' with a fee of {}%'.format(output(D(bindings['fee_fraction_int'] / 1e8), 'fraction')) + ' (' + bindings['tx_hash'] + ')' + ' [{}]'.format(bindings['status'])) category == 'bets': logger.info('Bet: {} against {}, by {}, on {}'.format(output(bindings['wager_quantity'], config.XCP), output(bindings['counterwager_quantity'], config.XCP), bindings['source'], bindings['feed_address'])) elif category == 'bet_matches': placeholder = '' if bindings['target_value'] >= 0: # Only non negative values are valid. placeholder = ' that ' + str(output(bindings['target_value'], 'value')) if bindings['leverage']:M placeholder += ', leveraged {}x'.format(output(bindings['leverage'] / 5040, 'leverage')) logger.info('Bet Match: {} for {} against {} for {} on {} at {}{} ({}) [{}]'.format(util.BET_TYPE_NAME[bindings['tx0_bet_type']], output(bindings['forward_quantity'], config.XCP), util.BET_TYPE_NAME[bindings['tx1_bet_type']], output(bindings['backward_quantity'], config.XCP), bindings['feed_address'], isodt(bindings['deadline']), placeholder, bindings['id'], bindings['status'])) elif categoM logger.info('Dividend: {} paid {} per unit of {} ({}) [{}]'.format(bindings['source'], output(bindings['quantity_per_unit'], bindings['dividend_asset']), bindings['asset'], bindings['tx_hash'], bindings['status'])) elif category == 'burns': logger.info('Burn: {} burned {} for {} ({}) [{}]'.format(bindings['source'], output(bindings['burned'], config.BTC), output(bindings['earned'], config.XCP), bindings['tx_hash'], bindings['status'])) elif category == 'cM logger.info('Cancel: {} ({}) [{}]'.format(bindings['offer_hash'], bindings['tx_hash'], bindings['status'])) elif category == 'rps': log_message = 'RPS: {} opens game with {} possible moves and a wager of {}'.format(bindings['source'], bindings['possible_moves'], output(bindings['wager'], 'XCP')) logger.info(log_message) elif category == 'rps_matches': log_message = 'RPS Match: {} is playing a {}-moves game with {} with a wager of {} ({}) [{}M ]'.format(bindings['tx0_address'], bindings['possible_moves'], bindings['tx1_address'], output(bindings['wager'], 'XCP'), bindings['id'], bindings['status']) logger.info(log_message) elif category == 'rpsresolves': if bindings['status'] == 'valid': rps_matches = list(cursor.execute('''SELECT * FROM rps_matches WHERE id = ?''', (bindings['rps_match_id'],))) assert len(rps_matches) == 1 rps_match = rps_matches[0] log_mesM sage = 'RPS Resolved: {} is playing {} on a {}-moves game with {} with a wager of {} ({}) [{}]'.format(rps_match['tx0_address'], bindings['move'], rps_match['possible_moves'], rps_match['tx1_address'], output(rps_match['wager'], 'XCP'), rps_match['id'], rps_match['status']) log_message = 'RPS Resolved: {} [{}]'.format(bindings['tx_hash'], bindings['status']) logger.info(log_message) elif category == 'order_expirations': logger.info('Expired order: {M }'.format(bindings['order_hash'])) elif category == 'order_match_expirations': logger.info('Expired Order Match awaiting payment: {}'.format(bindings['order_match_id'])) elif category == 'bet_expirations': logger.info('Expired bet: {}'.format(bindings['bet_hash'])) elif category == 'bet_match_expirations': logger.info('Expired Bet Match: {}'.format(bindings['bet_match_id'])) elif category == 'bet_match_resolutions': cfd_type_id = util.BET_TYPE_ID['BullCFD'] + util.BET_TYPE_ID['BearCFD'] equal_type_id = util.BET_TYPE_ID['Equal'] + util.BET_TYPE_ID['NotEqual'] if bindings['bet_match_type_id'] == cfd_type_id: if bindings['settled']: logger.info('Bet Match Settled: {} credited to the bull, {} credited to the bear, and {} credited to the feed address ({})'.format(output(bindings['bull_credit'], config.XCP), output(bindings['bear_credit'], config.XCP), output(bindings['M fee'], config.XCP), bindings['bet_match_id'])) else: logger.info('Bet Match Force Liquidated: {} credited to the bull, {} credited to the bear, and {} credited to the feed address ({})'.format(output(bindings['bull_credit'], config.XCP), output(bindings['bear_credit'], config.XCP), output(bindings['fee'], config.XCP), bindings['bet_match_id'])) elif bindings['bet_match_type_id'] == equal_type_id: logger.info('Bet Match Settled: {} won the pot of {};M {} credited to the feed address ({})'.format(bindings['winner'], output(bindings['escrow_less_fee'], config.XCP), output(bindings['fee'], config.XCP), bindings['bet_match_id'])) elif category == 'rps_expirations': logger.info('Expired RPS: {}'.format(bindings['rps_hash'])) elif category == 'rps_match_expirations': logger.info('Expired RPS Match: {}'.format(bindings['rps_match_id'])) elif category == 'destructions': asset_info = get_asset_info(cursor, bM quantity = bindings['quantity'] if asset_info['divisible']: quantity = "{:.8f}".format(quantity/config.UNIT) logger.info('Destruction: {} destroyed {} {} with tag ({}) [{}]'.format(bindings['source'], quantity, bindings['asset'], bindings['tag'], bindings['tx_hash'], bindings['status'])) elif category == 'dispensers': each_price = bindings['satoshirate'] currency = config.BTC dispenser_label =M escrow_quantity = bindings['escrow_quantity'] give_quantity = bindings['give_quantity'] if (bindings['oracle_address'] != None) and util.enabled('oracle_dispensers'): each_price = "{:.2f}".format(each_price/100.0) oracle_last_price, oracle_fee, currency, oracle_last_updated = util.get_oracle_last_price(db, bindings['oracle_address'], bindings['block_index']) dispenser_label = 'oracle dispenser using {}'.forM mat(bindings['oracle_address']) each_price = "{:.8f}".format(each_price/config.UNIT) divisible = get_asset_info(cursor, bindings['asset'])['divisible'] if divisible: escrow_quantity = "{:.8f}".format(escrow_quantity/config.UNIT) give_quantity = "{:.8f}".format(give_quantity/config.UNIT) if bindings['status'] == 0: logger.info('Dispenser: {} opened a {} for aM sset {} with {} balance, giving {} {} for each {} {}'.format(bindings['source'], dispenser_label, bindings['asset'], escrow_quantity, give_quantity, bindings['asset'], each_price, currency)) elif bindings['status'] == 1: logger.info('Dispenser: {} (empty address) opened a {} for asset {} with {} balance, giving {} {} for each {} {}'.format(bindings['source'], dispenser_label, bindings['asset'], escrow_quantity, give_quantity, bindings['asset'], each_price, currency)) elif binM dings['status'] == 10: logger.info('Dispenser: {} closed a {} for asset {}'.format(bindings['source'], dispenser_label, bindings['asset'])) elif category == 'dispenses': cursor.execute('SELECT * FROM dispensers WHERE tx_hash=:tx_hash', { 'tx_hash': bindings['dispenser_tx_hash'] dispensers = cursor.fetchall() dispenser = dispensers[0] if (dispenser["oracle_address"] != None) and util.enabled('oracle_dispeM tx_btc_amount = get_tx_info(cursor, bindings['tx_hash'])/config.UNIT oracle_last_price, oracle_fee, oracle_fiat_label, oracle_last_price_updated = util.get_oracle_last_price(db, dispenser["oracle_address"], bindings['block_index']) fiatpaid = round(tx_btc_amount*oracle_last_price,2) logger.info('Dispense: {} from {} to {} for {:.8f} {} ({} {}) ({})'.format(output(bindings['dispense_quantity'], bindings['asset']), bindings['souM rce'], bindings['destination'], tx_btc_amount, config.BTC, fiatpaid, oracle_fiat_label, bindings['tx_hash'])) logger.info('Dispense: {} from {} to {} ({})'.format(output(bindings['dispense_quantity'], bindings['asset']), bindings['source'], bindings['destination'], bindings['tx_hash'])) def get_lock_issuance(cursor, asset): cursor.execute('''SELECT * FROM issuances \ WHERE (status = ? AND asset = ? AND locked = ?) ORDER BY tx_index ASC''', (M 'valid', asset, True)) issuances = cursor.fetchall() if len(issuances) > 0: return issuances[0] def get_asset_issuances_quantity(cursor, asset): cursor.execute('''SELECT COUNT(*) AS issuances_count FROM issuances \ WHERE (status = ? AND asset = ?) ORDER BY tx_index DESC''', ('valid', asset)) issuances = cursor.fetchall() return issuances[0]['issuances_count'] def get_asset_info(cursor, asset): if asset == config.BTC or asset == config.XCPM return {'divisible':True} cursor.execute('''SELECT * FROM issuances \ WHERE (status = ? AND asset = ?) ORDER BY tx_index DESC''', ('valid', asset)) issuances = cursor.fetchall() return issuances[0] def get_tx_info(cursor, tx_hash): cursor.execute('SELECT * FROM transactions WHERE tx_hash=:tx_hash', { 'tx_hash': tx_hash transactions = cursor.fetchall() transaction = transactions[0] return transaction["btc_amount"] # vim: tabstop=8 expanM dtab shiftwidth=4 softtabstop=4 #### message_type.py logger = logging.getLogger(__name__) from counterpartylib.lib import config from counterpartylib.lib import util def pack(message_type_id, block_index=None): # pack message ID into 1 byte if not zero if util.enabled('short_tx_type_id', block_index) and message_type_id > 0 and message_type_id < 256: return struct.pack(config.SHORT_TXTYPE_FORMAT, message_type_id) # pack into 4 bytes truct.pack(config.TXTYPE_FORMAT, message_type_id) # retuns both the message type id and the remainder of the message data def unpack(packed_data, block_index=None): message_type_id = None message_remainder = None if len(packed_data) > 1: # try to read 1 byte first if util.enabled('short_tx_type_id', block_index): message_type_id = struct.unpack(config.SHORT_TXTYPE_FORMAT, packed_data[:1])[0] if message_type_id > 0: message_remainder = packed_dataM return (message_type_id, message_remainder) # First message byte was 0. We will read 4 bytes if len(packed_data) > 4: message_type_id = struct.unpack(config.TXTYPE_FORMAT, packed_data[:4])[0] message_remainder = packed_data[4:] return (message_type_id, message_remainder) None of the functions/objects in this module need be passed `db`. Naming convention: a `pub` is either a pubkey or a pubkeyhash from bitcoin.core.key import CPubKey from bitcoin.bech32 import CBech32Data from counterpartylib.lib import util from counterpartylib.lib import config from counterpartylib.lib import exceptions b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' class InputError (Exception): class AddressError(Exception): class MultiSigAddressError(AddressError): class VersionByteError (AddressError): class Base58Error (AddressError): class Base58ChecksumError (Base58Error): def validate(address, allow_p2sh=True): """Make sure the address is valid. May throw `AddressError`. # Get array of pubkeyhashes to check. if is_multisig(address): pubkeyhashes = pubkeyhash_array(address) pubkeyhashes = [address] # Check validity by attempting to decode. for pubkeyhash in pubkeyhashes: if util.enabled('segwit_support'): if not is_bech32(pubkeyhasM base58_check_decode(pubkeyhash, config.ADDRESSVERSION) base58_check_decode(pubkeyhash, config.ADDRESSVERSION) except VersionByteError as e: if not allow_p2sh: raise e base58_check_decode(pubkeyhash, config.P2SH_ADDRESSVERSION) except Base58Error as e: if not util.enabled('segwit_support') or not is_bech32(pubkeyhash): raise e def base58_encode(binary): """Encode the adM endian bytes to integer n = int('0x0' + util.hexlify(binary), 16) # Divide that integer into base58 n, r = divmod(n, 58) res.append(b58_digits[r]) res = ''.join(res[::-1]) def base58_check_encode(original, version): """Check if base58 encoding is valid.""" b = binascii.unhexlify(bytes(original, 'utf-8')) binary = d + util.dhash(d)[:4] res = base58_encode(binary) # Encode leading zeros as base58 zeros if c == czero: pad += 1 address = b58_digits[0] * pad + res if original != util.hexlify(base58_check_decode(address, version)): raise AddressError('encoded address does not decode properly') def base58_decode(s): # Convert the string to an integer if c not in b58_digits: Base58Error('Not a valid Base58 character: digit = b58_digits.index(c) # Convert the integer to bytes res = binascii.unhexlify(h.encode('utf8')) # Add padding back. for c in s[:-1]: if c == b58_digits[0]: pad += 1 k = b'\x00' * pad + res def base58_check_decode_parts(s): """Decode from base58 and return partM k = base58_decode(s) addrbyte, data, chk0 = k[0:1], k[1:-4], k[-4:] return addrbyte, data, chk0 def base58_check_decode(s, version): """Decode from base58 and return data part.""" addrbyte, data, chk0 = base58_check_decode_parts(s) if addrbyte != version: raise VersionByteError('incorrect version byte') chk1 = util.dhash(addrbyte + data)[:4] if chk0 != chk1: raise Base58ChecksumError('Checksum mismatch: 0x{} 0x{}'.format(util.hexlify(chk0), util.hexlM def is_multisig(address): """Check if the address is multi array = address.split('_') return len(array) > 1 def is_p2sh(address): if is_multisig(address): return False base58_check_decode(address, config.P2SH_ADDRESSVERSION) except (VersionByteError, Base58Error): return False def is_bech32(address): b32data = CBech32Data(address) def is_fully_valid(pubkey_bin): """Check if the public key is valid.""" cpubkey = CPubKey(pubkey_bin) return cpubkey.is_fullyvalid def make_canonical(address): """Return canonical version of the address.""" if is_multisig(address): signatures_required, pubkeyhashes, signatures_possible = extract_array(address) [base58_check_decode(pubkeyhash, config.ADDRESSVERSION) for pubkeyhash in pubkeyhashes] except Base58Error: raise MultiSigAddreM signature address must use PubKeyHashes, not public keys.') return construct_array(signatures_required, pubkeyhashes, signatures_possible) return address def test_array(signatures_required, pubs, signatures_possible): """Check if multi signature data is valid.""" signatures_required, signatures_possible = int(signatures_required), int(signatures_possible) except (ValueError, TypeError): raise MultiSigAddressError('Signature values not inM if signatures_required < 1 or signatures_required > 3: raise MultiSigAddressError('Invalid signatures_required.') if signatures_possible < 2 or signatures_possible > 3: raise MultiSigAddressError('Invalid signatures_possible.') for pubkey in pubs: if '_' in pubkey: raise MultiSigAddressError('Invalid characters in pubkeys/pubkeyhashes.') if signatures_possible != len(pubs): raise InputError('Incorrect number of pubkeys/pubkeyhashes in multi def construct_array(signatures_required, pubs, signatures_possible): """Create a multi signature address.""" test_array(signatures_required, pubs, signatures_possible) address = '_'.join([str(signatures_required)] + sorted(pubs) + [str(signatures_possible)]) def extract_array(address): """Extract data from multi signature address.""" assert is_multisig(address) array = address.split('_') signatures_required, pubs, signatures_possible = array[0],M sorted(array[1:-1]), array[-1] test_array(signatures_required, pubs, signatures_possible) return int(signatures_required), pubs, int(signatures_possible) def pubkeyhash_array(address): """Return PubKeyHashes from an address.""" signatures_required, pubs, signatures_possible = extract_array(address) if not all([is_pubkeyhash(pub) for pub in pubs]): raise MultiSigAddressError('Invalid PubKeyHashes. Multi signature address must use PubKeyHashes, not public keys.') pubkeyhashes = pubM return pubkeyhashes x = hashlib.sha256(x).digest() m = hashlib.new('ripemd160') return m.digest() def pubkey_to_pubkeyhash(pubkey): """Convert public key to PubKeyHash.""" pubkeyhash = hash160(pubkey) pubkey = base58_check_encode(binascii.hexlify(pubkeyhash).decode('utf-8'), config.ADDRESSVERSION) def pubkey_to_p2whash(pubkey): """Convert public key to PayToWitness.""" pubkeyhash = hash160(pubkey) pubkey = CBech32Data.froM m_bytes(0, pubkeyhash) return str(pubkey) def bech32_to_scripthash(address): bech32 = CBech32Data(address) return bytes(bech32) def get_asm(scriptpubkey): # TODO: When is an exception thrown here? Can this `try` block be tighter? Can it be replaced by a conditional? # TODO: This should be `for element in scriptpubkey`. for op in scriptpubkey: if type(op) == bitcoinlib.core.script.CScriptOp: # TODO: `op = element` asm.append(str(op)) # TODO: `data = element` (?) asm.append(op) except bitcoinlib.core.script.CScriptTruncatedPushDataError: raise exceptions.PushDataDecodeError('invalid pushdata due to truncation') raise exceptions.DecodeError('empty output') def get_checksig(asm): if len(asm) == 5 and asm[0] == 'OP_DUP' and asm[1] == 'OP_HASH160' and asm[3] == 'OP_EQUALVERIFY' and asm[4] == 'OP_CHECKSIG': if type(pubkeyhash) == bytes: return pubkeyhash raise exceptions.DecodeError('invalid OP_CHECKSIG') def get_checkmultisig(asm): if len(asm) == 5 and asm[3] == 2 and asm[4] == 'OP_CHECKMULTISIG': pubkeys, signatures_required = asm[1:3], asm[0] if all([type(pubkey) == bytes for pubkey in pubkeys]): return pubkeys, signatures_required if len(asm) == 6 and asm[4] == 3 and asm[5] == 'OP_CHECKMULTISIG': s, signatures_required = asm[1:4], asm[0] if all([type(pubkey) == bytes for pubkey in pubkeys]): return pubkeys, signatures_required raise exceptions.DecodeError('invalid OP_CHECKMULTISIG') def scriptpubkey_to_address(scriptpubkey): asm = get_asm(scriptpubkey) if asm[-1] == 'OP_CHECKSIG': checksig = get_checksig(asm) except exceptions.DecodeError: # coinbase return None return base58_check_encode(binascii.hexlify(checksig).decoM de('utf-8'), config.ADDRESSVERSION) elif asm[-1] == 'OP_CHECKMULTISIG': pubkeys, signatures_required = get_checkmultisig(asm) pubkeyhashes = [pubkey_to_pubkeyhash(pubkey) for pubkey in pubkeys] return construct_array(signatures_required, pubkeyhashes, len(pubkeyhashes)) elif len(asm) == 3 and asm[0] == 'OP_HASH160' and asm[2] == 'OP_EQUAL': return base58_check_encode(binascii.hexlify(asm[1]).decode('utf-8'), config.P2SH_ADDRESSVERSION) # TODO: Use `pythonM -bitcointools` instead. (Get rid of `pycoin` dependency.) from pycoin.encoding import wif_to_tuple_of_secret_exponent_compressed, public_pair_to_sec, EncodingError from pycoin.ecdsa import generator_secp256k1, public_pair_for_secret_exponent class AltcoinSupportError (Exception): pass def private_key_to_public_key(private_key_wif): """Convert private key to public key.""" if config.TESTNET: allowable_wif_prefixes = [config.PRIVATEKEY_VERSION_TESTNET] elif config.REGTEST: allowable_wif_pM refixes = [config.PRIVATEKEY_VERSION_REGTEST] allowable_wif_prefixes = [config.PRIVATEKEY_VERSION_MAINNET] secret_exponent, compressed = wif_to_tuple_of_secret_exponent_compressed( private_key_wif, allowable_wif_prefixes=allowable_wif_prefixes) except EncodingError: raise AltcoinSupportError('pycoin: unsupported WIF prefix') public_pair = public_pair_for_secret_exponent(generator_secp256k1, secret_exponent) public_key = public_pair_to_sec(publicM _pair, compressed=compressed) public_key_hex = binascii.hexlify(public_key).decode('utf-8') return public_key_hex def is_pubkeyhash(monosig_address): """Check if PubKeyHash is valid P2PKH address. """ assert not is_multisig(monosig_address) base58_check_decode(monosig_address, config.ADDRESSVERSION) except (Base58Error, VersionByteError): return False def make_pubkeyhash(address): """Create a new PubKeyHash.""" if is_multisig(address): signatures_required, pubs, signatures_possible = extract_array(address) pubkeyhashes = [] for pub in pubs: if is_pubkeyhash(pub): pubkeyhash = pub pubkeyhash = pubkey_to_pubkeyhash(binascii.unhexlify(bytes(pub, 'utf-8'))) pubkeyhashes.append(pubkeyhash) pubkeyhash_address = construct_array(signatures_required, pubkeyhashes, signatures_possible) if util.enabled('segwit_support') and is_bech32(addresM pubkeyhash_address = address # Some bech32 addresses are valid base58 data elif is_pubkeyhash(address): pubkeyhash_address = address elif is_p2sh(address): pubkeyhash_address = address pubkeyhash_address = pubkey_to_pubkeyhash(binascii.unhexlify(bytes(address, 'utf-8'))) return pubkeyhash_address def extract_pubkeys(pub): """Assume pubkey if not pubkeyhash. (Check validity later.)""" if is_multisig(pub): _, pubs, _ = extract_array(pub) for pub in pubs: if not is_pubkeyhash(pub): pubkeys.append(pub) elif is_p2sh(pub): elif util.enabled('segwit_support') and is_bech32(pub): if not is_pubkeyhash(pub): pubkeys.append(pub) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 #### transaction.py Construct and serialize the Bitcoin transactions that are Counterparty transactions. This module contains no consensus logger = logging.getLogger(__name__) import bitcoin as bitcoinlib from bitcoin.core.script import CScript from bitcoin.core import x, CTransaction from bitcoin.core import b2lx from counterpartylib.lib import blocks from counterpartylib.lib import config from counterpartylib.lib import excM from counterpartylib.lib import util from counterpartylib.lib import script from counterpartylib.lib import backend from counterpartylib.lib import arc4 from counterpartylib.lib.transaction_helper import serializer, p2sh_encoding OP_PUSHDATA1 = b'\x4c' OP_HASH160 = b'\xa9' OP_EQUALVERIFY = b'\x88' OP_CHECKSIG = b'\xac' OP_CHECKMULTISIG = b'\xae' is None or DictCache per address # set higher than the max number of UTXOs we should expect to # manage in an aging cache for any one source address, at any one period UTXO_LOCKS_PER_ADDRESS_MAXSIZE = 5000 # UTXO_P2SH_ENCODING_LOCKS is TTLCache for UTXOs that are used for chaining p2sh encoding # instead of a simple (txid, vout) key we use [(vin.prevout.hash, vin.prevout.n) for vin tx1.vin] UTXO_P2SH_ENCODING_LOCKS = None # we cache the make_outkey_vin to avoid having to fetch raw txs too oftenM UTXO_P2SH_ENCODING_LOCKS_CACHE = None global UTXO_LOCKS, UTXO_P2SH_ENCODING_LOCKS, UTXO_P2SH_ENCODING_LOCKS_CACHE if config.UTXO_LOCKS_MAX_ADDRESSES > 0: # initialize if configured UTXO_LOCKS = util.DictCache(size=config.UTXO_LOCKS_MAX_ADDRESSES) UTXO_P2SH_ENCODING_LOCKS = cachetools.TTLCache(10000, 180) UTXO_P2SH_ENCODING_LOCKS_CACHE = cachetools.TTLCache(1000, 600) def print_coin(coin): return 'amount: {:.8f}; txid: {}; vout: {}; confirmations: {}'.format(coinM ['amount'], coin['txid'], coin['vout'], coin.get('confirmations', '?')) # simplify and make deterministic """ Yield successive n sized chunks from l. for i in range(0, len(l), n): yield l[i:i+n] def make_outkey(output): return '{}{}'.format(output['txid'], output['vout']) def make_outkey_vin_txid(txid, vout): global UTXO_P2SH_ENCODING_LOCKS_CACHE if (txid, vout) not in UTXO_P2SH_ENCODING_LOCKS_CACHE: txhex = backend.getrawtransaction(txid, verboM UTXO_P2SH_ENCODING_LOCKS_CACHE[(txid, vout)] = make_outkey_vin(txhex, vout) return UTXO_P2SH_ENCODING_LOCKS_CACHE[(txid, vout)] def make_outkey_vin(txhex, vout): txbin = binascii.unhexlify(txhex) if isinstance(txhex, str) else txhex assert isinstance(vout, int) tx = bitcoinlib.core.CTransaction.deserialize(txbin) outkey = [(vin.prevout.hash, vin.prevout.n) for vin in tx.vin] outkey = hashlib.sha256(("%s%s" % (outkey, vout)).encode('ascii')).digest() def get_dust_return_pubkey(source, provided_pubkeys, encoding): """Return the pubkey to which dust from data outputs will be sent. This pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. It is derived from the source address, so that the dust is spendable by the creator of the transaction. # Get hex dust return pubkey. if script.is_multisig(source): a, self_pubkeys, b = script.extract_array(backend.multisig_pubkeyhashes_tM o_pubkeys(source, provided_pubkeys)) dust_return_pubkey_hex = self_pubkeys[0] dust_return_pubkey_hex = backend.pubkeyhash_to_pubkey(source, provided_pubkeys) # Convert hex public key into the (binary) dust return pubkey. dust_return_pubkey = binascii.unhexlify(dust_return_pubkey_hex) except binascii.Error: raise script.InputError('Invalid private key.') return dust_return_pubkey def construct_coin_selection(encoding, data_array, source, allow_unconfM irmed_inputs, unspent_tx_hash, custom_inputs, fee_per_kb, estimate_fee_per_kb, estimate_fee_per_kb_nblocks, exact_fee, size_for_fee, fee_provided, destination_btc_out, data_btc_out, regular_dust_size, disable_utxo_locks): global UTXO_LOCKS, UTXO_P2SH_ENCODING_LOCKS # Array of UTXOs, as retrieved by listunspent function from bitcoind if custom_inputs: use_inputs = unspent = custom_inputs if unspent_tx_hash is not None: unspent = backend.get_unspent_txouts(source, unconfirmed=allow_unconfirmed_inputs, unspent_tx_hash=unspent_tx_hash) unspent = backend.get_unspent_txouts(source, unconfirmed=allow_unconfirmed_inputs) filter_unspents_utxo_locks = [] if UTXO_LOCKS is not None and source in UTXO_LOCKS: filter_unspents_utxo_locks = UTXO_LOCKS[source].keys() filter_unspents_p2sh_locks = UTXO_P2SH_ENCODING_LOCKS.keys() # filter out any locked UTXOs to prevM ent creating transactions that spend the same UTXO when they're created at the same time filtered_unspent = [] for output in unspent: if make_outkey(output) not in filter_unspents_utxo_locks and make_outkey_vin_txid(output['txid'], output['vout']) not in filter_unspents_p2sh_locks: filtered_unspent.append(output) unspent = filtered_unspent unspent = backend.sort_unspent_txouts(unspent) logger.debug('Sorted candidate UTXOs: {}'.format([print_coin(cM oin) for coin in unspent])) use_inputs = unspent # use backend estimated fee_per_kb if estimate_fee_per_kb: estimated_fee_per_kb = backend.fee_per_kb(estimate_fee_per_kb_nblocks, config.ESTIMATE_FEE_MODE) if estimated_fee_per_kb is not None: fee_per_kb = max(estimated_fee_per_kb, fee_per_kb) # never drop below the default fee_per_kb logger.debug('Fee/KB {:.8f}'.format(fee_per_kb / config.UNIT)) change_quantity = 0 final_fee = fee_per_kb desired_input_count = 1 if encoding == 'multisig' and data_array and util.enabled('bytespersigop'): desired_input_count = len(data_array) * 2 # pop inputs until we can pay for the fee for coin in use_inputs: logger.debug('New input: {}'.format(print_coin(coin))) inputs.append(coin) btc_in += round(coin['amount'] * config.UNIT) # If exact fee is specified, use that. Otherwise, calculate size of tx # and base M fee on that (plus provide a minimum fee for selling BTC). size = 181 * len(inputs) + size_for_fee + 10 if exact_fee: final_fee = exact_fee necessary_fee = int(size / 1000 * fee_per_kb) final_fee = max(fee_provided, necessary_fee) logger.getChild('p2shdebug').debug('final_fee inputs: %d size: %d final_fee %s' % (len(inputs), size, final_fee)) # Check if good. btc_out = destination_btc_out + data_btc_out change_quanM tity = btc_in - (btc_out + final_fee) logger.debug('Size: {} Fee: {:.8f} Change quantity: {:.8f} BTC'.format(size, final_fee / config.UNIT, change_quantity / config.UNIT)) # If change is necessary, must not be a dust output. if change_quantity == 0 or change_quantity >= regular_dust_size: sufficient_funds = True if len(inputs) >= desired_input_count: break if not sufficient_funds: # Approximate needed change, fee by with most recently calcM # quantities. btc_out = destination_btc_out + data_btc_out total_btc_out = btc_out + max(change_quantity, 0) + final_fee raise exceptions.BalanceError('Insufficient {} at address {}. (Need approximately {} {}.) To spend unconfirmed coins, use the flag `--unconfirmed`. (Unconfirmed coins cannot be spent from multi sig addresses.)'.format(config.BTC, source, total_btc_out / config.UNIT, config.BTC)) # Lock the source's inputs (UTXOs) chosen for this transaction O_LOCKS is not None and not disable_utxo_locks: if source not in UTXO_LOCKS: UTXO_LOCKS[source] = cachetools.TTLCache( UTXO_LOCKS_PER_ADDRESS_MAXSIZE, config.UTXO_LOCKS_MAX_AGE) for input in inputs: UTXO_LOCKS[source][make_outkey(input)] = input logger.debug("UTXO locks: Potentials ({}): {}, Used: {}, locked UTXOs: {}".format( len(unspent), [make_outkey(coin) for coin in unspent], [make_outkey(input) for input in inputs], listM (UTXO_LOCKS[source].keys()))) # ensure inputs have scriptPubKey # this is not provided by indexd inputs = backend.ensure_script_pub_key_for_inputs(inputs) return inputs, change_quantity, btc_in, final_fee def select_any_coin_from_source(source, allow_unconfirmed_inputs=True, disable_utxo_locks=False): ''' Get the first (biggest) input from the source address ''' global UTXO_LOCKS # Array of UTXOs, as retrieved by listunspent function from bitcoind unspent = backend.get_unspent_M txouts(source, unconfirmed=allow_unconfirmed_inputs) filter_unspents_utxo_locks = [] if UTXO_LOCKS is not None and source in UTXO_LOCKS: filter_unspents_utxo_locks = UTXO_LOCKS[source].keys() # filter out any locked UTXOs to prevent creating transactions that spend the same UTXO when they're created at the same time filtered_unspent = [] for output in unspent: if make_outkey(output) not in filter_unspents_utxo_locks: filtered_unspent.append(output) unspent = backend.sort_unspent_txouts(unspent) # use the first input input = unspent[0] if input is None: # Lock the source's inputs (UTXOs) chosen for this transaction if UTXO_LOCKS is not None and not disable_utxo_locks: if source not in UTXO_LOCKS: UTXO_LOCKS[source] = cachetools.TTLCache( UTXO_LOCKS_PER_ADDRESS_MAXSIZE, config.UTXO_LOCKS_MAX_AGE) UTXO_LOCKS[source][make_outkey(input)] = input def return_result(tx_hexes, old_style_api): tx_hexes = list(filter(None, tx_hexes)) # filter out None if old_style_api: if len(tx_hexes) != 1: raise Exception("Can't do 2 TXs with old_style_api") return tx_hexes[0] if len(tx_hexes) == 1: return tx_hexes[0] return tx_hexes def construct (db, tx_info, encoding='auto', fee_per_kb=config.DEFAULT_FEE_PER_M estimate_fee_per_kb=None, estimate_fee_per_kb_conf_target=config.ESTIMATE_FEE_CONF_TARGET, estimate_fee_per_kb_mode=config.ESTIMATE_FEE_MODE, estimate_fee_per_kb_nblocks=config.ESTIMATE_FEE_CONF_TARGET, regular_dust_size=config.DEFAULT_REGULAR_DUST_SIZE, multisig_dust_size=config.DEFAULT_MULTISIG_DUST_SIZE, op_return_value=config.DEFAULT_OP_RETURN_VALUE, exact_fee=None, fee_provided=0, provided_pubkeys=None, dust_return_puM allow_unconfirmed_inputs=False, unspent_tx_hash=None, custom_inputs=None, disable_utxo_locks=False, extended_tx_info=False, old_style_api=None, segwit=False, p2sh_source_multisig_pubkeys=None, p2sh_source_multisig_pubkeys_required=None, p2sh_pretx_txid=None,): if estimate_fee_per_kb is None: estimate_fee_per_kb = config.ESTIMATE_FEE_PER_KB global UTXO_LOCKS, UTXO_P2SH_ENCODING_LOCKS # lazy assign from config, because when set as default M it's evaluated before it's configured if old_style_api is None: old_style_api = config.OLD_STYLE_API (source, destination_outputs, data) = tx_info if dust_return_pubkey: dust_return_pubkey = binascii.unhexlify(dust_return_pubkey) if p2sh_source_multisig_pubkeys: p2sh_source_multisig_pubkeys = [binascii.unhexlify(p) for p in p2sh_source_multisig_pubkeys] # If public key is necessary for construction of (unsigned) # transaction, use the public M key provided, or find it from the # blockchain. script.validate(source) source_is_p2sh = script.is_p2sh(source) # Normalize source if script.is_multisig(source): source_address = backend.multisig_pubkeyhashes_to_pubkeys(source, provided_pubkeys) source_address = source # Sanity checks. if exact_fee and not isinstance(exact_fee, int): raise exceptions.TransactionError('Exact fees must be in satoshis.') if not isinstance(fee_pM raise exceptions.TransactionError('Fee provided must be in satoshis.') '''Determine encoding method''' desired_encoding = encoding # Data encoding methods (choose and validate). if desired_encoding == 'auto': if len(data) + len(config.PREFIX) <= config.OP_RETURN_MAX_SIZE: encoding = 'opreturn' encoding = 'p2sh' if not old_style_api and util.enabled('p2sh_encoding') else 'multisig' # p2sh M is not possible with old_style_api elif desired_encoding == 'p2sh' and not util.enabled('p2sh_encoding'): raise exceptions.TransactionError('P2SH encoding not enabled yet') elif encoding not in ('pubkeyhash', 'multisig', 'opreturn', 'p2sh'): raise exceptions.TransactionError('Unknown encoding encoding = None '''Destinations''' # Destination outputs. # Replace multi sig addresses with multi # destination output isn t a dust output. Set null values to dust size. destination_outputs_new = [] if encoding != 'p2sh': for (address, value) in destination_outputs: # Value. if script.is_multisig(address): dust_size = multisig_dust_size dust_size = regular_dust_size if value == None: value = dust_size elif value < dust_size: raise exceptions.TrM ansactionError('Destination output is dust.') # Address. script.validate(address) if script.is_multisig(address): destination_outputs_new.append((backend.multisig_pubkeyhashes_to_pubkeys(address, provided_pubkeys), value)) destination_outputs_new.append((address, value)) destination_outputs = destination_outputs_new destination_btc_out = sum([value for address, value in destination_outputs]) # @TODO: p2sh encoding require signable dust key if encoding == 'multisig': # dust_return_pubkey should be set or explicitly set to False to use the default configured for the node # the default for the node is optional so could fail if (source_is_p2sh and dust_return_pubkey is None) or (dust_return_pubkey is False and config.P2SH_DUST_RETURN_PUBKEY is None): raise exceptions.TransactionError("Can't use multisig encoding when source is P2SH and M no dust_return_pubkey is provided.") elif dust_return_pubkey is False: dust_return_pubkey = binascii.unhexlify(config.P2SH_DUST_RETURN_PUBKEY) if not dust_return_pubkey: if encoding == 'multisig' or encoding == 'p2sh' and not source_is_p2sh: dust_return_pubkey = get_dust_return_pubkey(source, provided_pubkeys, encoding) dust_return_pubkey = None # Divide data into chunks. if encoding == 'pubkeyhash': # Prefix is also a suffix here. chunk_size = 20 - 1 - 8 elif encoding == 'multisig': # Two pubkeys, minus length byte, minus prefix, minus two nonces, # minus two sign bytes. chunk_size = (33 * 2) - 1 - 8 - 2 - 2 elif encoding == 'p2sh': pubkeylength = -1 if dust_return_pubkey is not None: pubkeylength = len(dust_return_pubkey) chunk_size = p2sh_encoding.maximum_data_chunk_size(pubkeylengM elif encoding == 'opreturn': chunk_size = config.OP_RETURN_MAX_SIZE if len(data) + len(config.PREFIX) > chunk_size: raise exceptions.TransactionError('One `OP_RETURN` output per transaction.') data_array = list(chunks(data, chunk_size)) # Data outputs. if encoding == 'multisig': data_value = multisig_dust_size elif encoding == 'p2sh': data_value = 0 # this will be calculated later elif encoding == M data_value = op_return_value data_value = regular_dust_size data_output = (data_array, data_value) data_value = 0 data_array = [] data_output = None dust_return_pubkey = None data_btc_out = data_value * len(data_array) logger.getChild('p2shdebug').debug('data_btc_out=%s (data_value=%d len(data_array)=%d)' % (data_btc_out, data_value, len(data_array))) # Calculate collective size of outputs, for fee calculation. p2pkhsize = 25 + 9 if encoding == 'multisig': data_output_size = 81 # 71 for the data elif encoding == 'opreturn': # prefix + data + 10 bytes script overhead data_output_size = len(config.PREFIX) + 10 if data is not None: data_output_size = data_output_size + len(data) data_output_size = p2pkhsize # Pay PubKeyHash (25 for the dM outputs_size = (p2pkhsize * len(destination_outputs)) + (len(data_array) * data_output_size) if encoding == 'p2sh': # calculate all the p2sh outputs size_for_fee, datatx_necessary_fee, data_value, data_btc_out = p2sh_encoding.calculate_outputs(destination_outputs, data_array, fee_per_kb, exact_fee) # replace the data value data_output = (data_array, data_value) sum_data_output_size = len(data_array) * data_output_size size_for_fee = ((25 + 9) M * len(destination_outputs)) + sum_data_output_size if not (encoding == 'p2sh' and p2sh_pretx_txid): inputs, change_quantity, n_btc_in, n_final_fee = construct_coin_selection( encoding, data_array, source, allow_unconfirmed_inputs, unspent_tx_hash, custom_inputs, fee_per_kb, estimate_fee_per_kb, estimate_fee_per_kb_nblocks, exact_fee, size_for_fee, fee_provided, destination_btc_out, data_btc_out, regular_dust_size, disable_utxo_locks btc_in = n_btc_in final_fee = n_final_fee # when encoding is P2SH and the pretx txid is passed we can skip coinselection inputs, change_quantity = None, None if change_quantity: change_output = (source_address, change_quantity) change_output = None unsigned_pretx_hex = None unsigned_tx_hex = None pretx_txid = None if encoding == 'p2sh': assert not (segwit and p2sh_pretx_txid) # shouldn't do old stylM e with segwit enabled if p2sh_pretx_txid: pretx_txid = p2sh_pretx_txid if isinstance(p2sh_pretx_txid, bytes) else binascii.unhexlify(p2sh_pretx_txid) unsigned_pretx = None destination_value_sum = sum([value for (destination, value) in destination_outputs]) source_value = destination_value_sum if change_output: # add the difference between source and destination to the change change_value = change_outpM ut[1] + (destination_value_sum - source_value) change_output = (change_output[0], change_value) unsigned_pretx = serializer.serialise_p2sh_pretx(inputs, source=source_address, source_value=source_value, data_output=data_output, change_output=changM pubkey=dust_return_pubkey, multisig_pubkeys=p2sh_source_multisig_pubkeys, multisig_pubkeys_required=p2sh_source_multisig_pubkeys_required) unsigned_pretx_hex = binascii.hexlify(unsigned_pretx).decode('utf-8') # with segwit we already know the txid and can return both #pretx_M txid = hashlib.sha256(unsigned_pretx).digest() # this should be segwit txid ptx = CTransaction.stream_deserialize(io.BytesIO(unsigned_pretx)) # could be a non-segwit tx anyways txid_ba = bytearray(ptx.GetTxid()) txid_ba.reverse() pretx_txid = bytes(txid_ba) # gonna leave the malleability problem to upstream logger.getChild('p2shdebug').debug('pretx_txid %s' % pretx_txid) print('pretx txid:', binascii.hexlify(pretx_txid)) if unsigned_pM # we set a long lock on this, don't want other TXs to spend from it UTXO_P2SH_ENCODING_LOCKS[make_outkey_vin(unsigned_pretx, 0)] = True # only generate the data TX if we have the pretx txId if pretx_txid: source_input = None if script.is_p2sh(source): source_input = select_any_coin_from_source(source) if not source_input: raise exceptions.TransactionError('Unable to select source input for p2shM unsigned_datatx = serializer.serialise_p2sh_datatx(pretx_txid, source=source_address, source_input=source_input, destination_outputs=destination_outputs, data_output=data_output, M pubkey=dust_return_pubkey, multisig_pubkeys=p2sh_source_multisig_pubkeys, multisig_pubkeys_required=p2sh_source_multisig_pubkeys_required) unsigned_datatx_hex = binascii.hexlify(unsigned_datatx).decode('utf-8') # let the rest of the code work it's magic on the data tx unsigned_tx_hex = unsigned_datatx_hex # we're just gonna M return the pretx, it doesn't require any of the further checks logger.warn('old_style_api = %s' % old_style_api) return return_result([unsigned_pretx_hex], old_style_api=old_style_api) # Serialise inputs and outputs. unsigned_tx = serializer.serialise(encoding, inputs, destination_outputs, data_output, change_output, dust_return_pubkey=dust_return_pubkey) unsigned_tx_hex = binascii.hexlify(unsiM gned_tx).decode('utf-8') '''Sanity Check''' # Desired transaction info. (desired_source, desired_destination_outputs, desired_data) = tx_info desired_source = script.make_canonical(desired_source) desired_destination = script.make_canonical(desired_destination_outputs[0][0]) if desired_destination_outputs else '' # NOTE: Include change in destinations for BTC transactions. # if change_output and not desired_data and desired_destination != config.UNSPENDABLE: # if desired_destinM # desired_destination = desired_source # desired_destination += '-{}'.format(desired_source) if desired_data == None: desired_data = b'' # Parsed transaction info. if pretx_txid and unsigned_pretx: backend.cache_pretx(pretx_txid, unsigned_pretx) parsed_source, parsed_destination, x, y, parsed_data, extra = blocks._get_tx_info(unsigned_tx_hex, p2sh_is_segwit=script.is_bech32(desired_source)) encoding == 'p2sh': # make_canonical can't determine the address, so we blindly change the desired to the parsed desired_source = parsed_source if pretx_txid and unsigned_pretx: backend.clear_pretx(pretx_txid) except exceptions.BTCOnlyError: if extended_tx_info: return { 'btc_in': btc_in, 'btc_out': destination_btc_out + data_btc_out, 'btc_change': change_quanM 'btc_fee': final_fee, 'tx_hex': unsigned_tx_hex, logger.getChild('p2shdebug').debug('BTC-ONLY') return return_result([unsigned_pretx_hex, unsigned_tx_hex], old_style_api=old_style_api) desired_source = script.make_canonical(desired_source) # Check desired info against parsed info. desired = (desired_source, desired_destination, desired_data) parsed = (parsed_source, parsed_destination, parsed_data) if desired != parsed: # Unlock (revert) UTXO locks if UTXO_LOCKS is not None and inputs: for input in inputs: UTXO_LOCKS[source].pop(make_outkey(input), None) raise exceptions.TransactionError('Constructed transaction does not parse correctly: {} {}'.format(desired, parsed)) if extended_tx_info: 'btc_in': btc_in, 'btc_out': destination_btc_out + data_btc_out, 'btc_change': change_quantity, 'btc_fee': final_fee, 'tx_hex': unsigned_tx_hex, return return_result([unsigned_pretx_hex, unsigned_tx_hex], old_style_api=old_style_api) def normalize_custom_inputs(raw_custom_inputs): custom_inputs = [] for custom_input in raw_custom_inputs: if 'value' not in custom_input: custom_input['value'] = int(custom_input['amount'] * config.UNIT) custom_inputs.append(custom_input) return custom_inputs # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 logger = logging.getLogger(__name__) from datetime import datetime from dateutil.tz import tzlocal from operator import itemgetter import bitcoin as bitcoinlib from counterpartylib.lib import exceptions from counterpartylib.lib.exceptions import DecodeEM from counterpartylib.lib import config B26_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' # subasset contain only characters a-zA-Z0-9.-_@! SUBASSET_DIGITS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_@!' SUBASSET_REVERSE = {'a':1,'b':2,'c':3,'d':4,'e':5,'f':6,'g':7,'h':8,'i':9,'j':10,'k':11,'l':12,'m':13,'n':14, 'o':15,'p':16,'q':17,'r':18,'s':19,'t':20,'u':21,'v':22,'w':23,'x':24,'y':25,'z':26, 'A':27,'B':28,'C':29,'D':30,'E':31,'F':M 32,'G':33,'H':34,'I':35,'J':36,'K':37,'L':38,'M':39, 'N':40,'O':41,'P':42,'Q':43,'R':44,'S':45,'T':46,'U':47,'V':48,'W':49,'X':50,'Y':51,'Z':52, '0':53,'1':54,'2':55,'3':56,'4':57,'5':58,'6':59,'7':60,'8':61,'9':62,'.':63,'-':64,'_':65,'@':66,'!':67} # Obsolete in Python 3.4, with enum module. BET_TYPE_NAME = {0: 'BullCFD', 1: 'BearCFD', 2: 'Equal', 3: 'NotEqual'} BET_TYPE_ID = {'BullCFD': 0, 'BearCFD': 1, 'Equal': 2, 'NotEqual': 3} json_dump = lambda x: json.dumps(x, sortM _keys=True, indent=4) json_print = lambda x: print(json_dump(x)) CURRENT_BLOCK_INDEX = None CURR_DIR = os.path.dirname(os.path.realpath(__file__)) with open(CURR_DIR + '/../protocol_changes.json') as f: PROTOCOL_CHANGES = json.load(f) class RPCError (Exception): pass # TODO: Move to `util_test.py`. t timeout properly. (If server hangs, then unhangs, no result.) def api(method, params): """Poll API via JSON-RPC.""" headers = {'content-type': 'application/json'}M "method": method, "params": params, "jsonrpc": "2.0", response = requests.post(config.RPC, data=json.dumps(payload), headers=headers) if response == None: raise RPCError('Cannot communicate with {} server.'.format(config.XCP_NAME)) elif response.status_code != 200: if response.status_code == 500: raise RPCError('Malformed API call.') raise RPCError(str(response.status_code) + ' ' + respM response_json = response.json() if 'error' not in response_json.keys() or response_json['error'] == None: return response_json['result'] except KeyError: raise RPCError(response_json) raise RPCError('{} ({})'.format(response_json['error']['message'], response_json['error']['code'])) return [l[i:i + n] for i in range(0, len(l), n)] return [x for x in z] # Had to do it this way to support python 3.4, if we start # using the 3.5 runtime this can be replaced by: # (first_elem, *t) l.insert(0, first_elem) it = itertools.groupby(l, itemgetter(0)) for key, subiter in it: yield key, sum(item[1] for item in subiter) def date_passed(date): """Check if the date has already passed.""" return date <= int(time.time()) def price (numerator, denominator): eturn price as Fraction or Decimal.""" if CURRENT_BLOCK_INDEX >= 294500 or config.TESTNET or config.REGTEST: # Protocol change. return fractions.Fraction(numerator, denominator) numerator = D(numerator) denominator = D(denominator) return D(numerator / denominator) def last_message(db): """Return latest message from the db.""" cursor = db.cursor() messages = list(cursor.execute('''SELECT * FROM messages WHERE message_index = (SELECT MAX(message_index) from M assert len(messages) == 1 last_message = messages[0] raise exceptions.DatabaseError('No messages found.') return last_message def generate_asset_id(asset_name, block_index): """Create asset_id from asset_name.""" if asset_name == config.BTC: return 0 elif asset_name == config.XCP: return 1 if len(asset_name) < 4: raise exceptions.AssetNameError('too short') # Numeric asset names. umeric_asset_names'): # Protocol change. if asset_name[0] == 'A': # Must be numeric. asset_id = int(asset_name[1:]) except ValueError: raise exceptions.AssetNameError('non numeric asset name starts with # Number must be in range. if not (26**12 + 1 <= asset_id <= 2**64 - 1): raise exceptions.AssetNameError('numeric asset name not in range') return asset_id len(asset_name) >= 13: raise exceptions.AssetNameError('long asset names must be numeric') if asset_name[0] == 'A': raise exceptions.AssetNameError('non numeric asset name starts with # Convert the Base 26 string to an integer. for c in asset_name: if c not in B26_DIGITS: raise exceptions.AssetNameError('invalid character:', c) digit = B26_DIGITS.index(c) if asset_id < 26**3: raise exceptions.AssetNameError('too short') def generate_asset_name (asset_id, block_index): """Create asset_name from asset_id.""" if asset_id == 0: return config.BTC elif asset_id == 1: return config.XCP if asset_id < 26**3: raise exceptions.AssetIDError('too low') if enabled('numeric_asset_names'): # Protocol change. if asset_id <= 2**64 - 1: if 26**12 + 1 <= asset_id: asset_name = 'A' + str(asset_id) returnM raise exceptions.AssetIDError('too high') # Divide that integer into Base 26 string. n, r = divmod (n, 26) res.append(B26_DIGITS[r]) asset_name = ''.join(res[::-1]) return asset_name + checksum.compute(asset_name) return asset_name def get_asset_id (db, asset_name, block_index): """Return asset_id from asset_name.""" if not enabled('hotfix_numeric_assets'): generate_asset_id(asset_name, block_index) cursor = db.cursor() cursor.execute('''SELECT * FROM assets WHERE asset_name = ?''', (asset_name,)) assets = list(cursor) if len(assets) == 1: return int(assets[0]['asset_id']) raise exceptions.AssetError('No such asset: {}'.format(asset_name)) def get_asset_name (db, asset_id, block_index): """Return asset_name from asset_id.""" if not enabled('hotfix_numeric_assets'): return generate_asset_name(asset_id, block_inM cursor = db.cursor() cursor.execute('''SELECT * FROM assets WHERE asset_id = ?''', (str(asset_id),)) assets = list(cursor) if len(assets) == 1: return assets[0]['asset_name'] elif not assets: return 0 # Strange, I know # If asset_name is an existing subasset (PARENT.child) then return the corresponding numeric asset name (A12345) # If asset_name is not an existing subasset, then return the unmodified asset_name def resolve_subasset_longname(db, asset_name): nabled('subassets'): subasset_longname = None subasset_parent, subasset_longname = parse_subasset_from_asset_name(asset_name) except Exception as e: logger.warn("Invalid subasset {}".format(asset_name)) subasset_longname = None if subasset_longname is not None: cursor = db.cursor() cursor.execute('''SELECT asset_name FROM assets WHERE asset_longname = ?''', (subasset_longname,)) assets = list(cursor) cursor.close() if len(assets) == 1: return assets[0]['asset_name'] return asset_name # checks and validates subassets (PARENT.SUBASSET) # throws exceptions for assset or subasset names with invalid syntax # returns (None, None) if the asset is not a subasset name def parse_subasset_from_asset_name(asset): subasset_parent = None subasset_child = None subasset_longname = None chunks = asset.split('.', 1) if (len(chunks) == 2): subasset_parent = M subasset_child = chunks[1] subasset_longname = asset # validate parent asset validate_subasset_parent_name(subasset_parent) # validate child asset validate_subasset_longname(subasset_longname, subasset_child) return (subasset_parent, subasset_longname) # throws exceptions for invalid subasset names def validate_subasset_longname(subasset_longname, subasset_child=None): if subasset_child is None: chunks = subasset_longname.split('.', 1) if (len(chunks) == 2): subasset_child = chunks[1] subasset_child = '' if len(subasset_child) < 1: raise exceptions.AssetNameError('subasset name too short') if len(subasset_longname) > 250: raise exceptions.AssetNameError('subasset name too long') # can't start with period, can't have consecutive periods, can't contain anything not in SUBASSET_DIGITS previous_digit = '.' for c in subasset_child: if c not in SUBASSET_DIGITS: raise exceptions.AssetNameError('subasset name contains invalid character:', c) if c == '.' and previous_digit == '.': raise exceptions.AssetNameError('subasset name contains consecutive periods') previous_digit = c if previous_digit == '.': raise exceptions.AssetNameError('subasset name ends with a period') # throws exceptions for invalid subasset names def validate_subasset_parent_name(asset_name): if asset_name == config.BTC: xceptions.AssetNameError('parent asset cannot be {}'.format(config.BTC)) if asset_name == config.XCP: raise exceptions.AssetNameError('parent asset cannot be {}'.format(config.XCP)) if len(asset_name) < 4: raise exceptions.AssetNameError('parent asset name too short') if len(asset_name) >= 13: raise exceptions.AssetNameError('parent asset name too long') if asset_name[0] == 'A': raise exceptions.AssetNameError('parent asset name starts with if c not in B26_DIGITS: raise exceptions.AssetNameError('parent asset name contains invalid character:', c) def compact_subasset_longname(string): """Compacts a subasset name string into an array of bytes to save space using a base68 encoding scheme. Assumes all characters provided belong to SUBASSET_DIGITS. for i, c in enumerate(string[::-1]): name_int += (68 ** i) * SUBASSET_REVERSE[c] return name_int.to_bytes((name_int.M bit_length() + 7) // 8, byteorder='big') def expand_subasset_longname(raw_bytes): """Expands an array of bytes into a subasset name string.""" integer = int.from_bytes(raw_bytes, byteorder='big') if integer == 0: while integer != 0: ret = SUBASSET_DIGITS[integer % 68 - 1] + ret integer //= 68 def generate_random_asset (): return 'A' + str(random.randint(26**12 + 1, 2**64 - 1)) def parse_options_from_string(string): ions integer from string, if exists.""" string_list = string.split(" ") if len(string_list) == 2: options = int(string_list.pop()) raise exceptions.OptionsError('options not an integer') return options return False def validate_address_options(options): """Ensure the options are all valid and in range.""" if (options > config.MAX_INT) or (options < 0): raise exceptions.OptionsError('options integer overflow') elif options > config.ADDRESS_OPTION_MAX_VALUE: raise exceptions.OptionsError('options out of range') elif not active_options(config.ADDRESS_OPTION_MAX_VALUE, options): raise exceptions.OptionsError('options not possible') def active_options(config, options): """Checks if options active in some given config.""" return config & options == options class DebitError (Exception): pass def debit (db, address, asset, quantity, action=None, event=None): """Debit given address by quantity M block_index = CURRENT_BLOCK_INDEX if type(quantity) != int: raise DebitError('Quantity must be an integer.') if quantity < 0: raise DebitError('Negative quantity.') if quantity > config.MAX_INT: raise DebitError('Quantity can\'t be higher than MAX_INT.') if asset == config.BTC: raise DebitError('Cannot debit bitcoins.') debit_cursor = db.cursor() # Contracts can only hold XCP balances. if enabled('contracts_only_xcp_balances'): # ProtocM if len(address) == 40: assert asset == config.XCP if asset == config.BTC: raise exceptions.BalanceError('Cannot debit bitcoins from a {} address!'.format(config.XCP_NAME)) debit_cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (address, asset)) balances = debit_cursor.fetchall() if not len(balances) == 1: old_balance = 0 old_balance = balances[0]['quantity'] raise DebitError('Insufficient funds.') balance = round(old_balance - quantity) balance = min(balance, config.MAX_INT) assert balance >= 0 'quantity': balance, 'address': address, 'asset': asset sql='update balances set quantity = :quantity where (address = :address and asset = :asset)' debit_cursor.execute(sql, bindings) 'block_index': block_index, 'address': address, 'asset': asset, 'quantity': quantity, 'action': action, 'event': event sql='insert into debits values(:block_index, :address, :asset, :quantity, :action, :event)' debit_cursor.execute(sql, bindings) debit_cursor.close() BLOCK_LEDGER.append('{}{}{}{}'.format(block_index, address, asset, quantity)) class CreditError (Exception): pass def credit (db, address, asset, quantity, action=None, event=None): """Credit given address by quantity of asset.""" index = CURRENT_BLOCK_INDEX if type(quantity) != int: raise CreditError('Quantity must be an integer.') if quantity < 0: raise CreditError('Negative quantity.') if quantity > config.MAX_INT: raise CreditError('Quantity can\'t be higher than MAX_INT.') if asset == config.BTC: raise CreditError('Cannot debit bitcoins.') credit_cursor = db.cursor() # Contracts can only hold XCP balances. if enabled('contracts_only_xcp_balances'): # Protocol change. if len(address) == 40: assert asset == config.XCP credit_cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (address, asset)) balances = credit_cursor.fetchall() if len(balances) == 0: assert balances == [] #update balances table with new balance bindings = { 'address': address, 'asset': asset, 'quantity': quantity, sql='insert into balances values(:addrM ess, :asset, :quantity)' credit_cursor.execute(sql, bindings) elif len(balances) > 1: assert False old_balance = balances[0]['quantity'] assert type(old_balance) == int balance = round(old_balance + quantity) balance = min(balance, config.MAX_INT) bindings = { 'quantity': balance, 'address': address, 'asset': asset sql='update balances set quantity = :quantity where (address = :address and asM credit_cursor.execute(sql, bindings) # Record credit. 'block_index': block_index, 'address': address, 'asset': asset, 'quantity': quantity, 'action': action, 'event': event sql='insert into credits values(:block_index, :address, :asset, :quantity, :action, :event)' credit_cursor.execute(sql, bindings) credit_cursor.close() BLOCK_LEDGER.append('{}{}{}{}'.format(block_index, address, asset, quantity)) class QuantityError(Exception): pass def is_divisible(db, asset): """Check if the asset is divisible.""" if asset in (config.BTC, config.XCP): cursor = db.cursor() cursor.execute('''SELECT * FROM issuances \ WHERE (status = ? AND asset = ?) ORDER BY tx_index DESC''', ('valid', asset)) issuances = cursor.fetchall() if not issuances: raise exceptions.AssetError('No such asset: {}'.format(asset)) return issuances[M def value_input(quantity, asset, divisible): if asset == 'leverage': return round(quantity) if asset in ('value', 'fraction', 'price', 'odds'): return float(quantity) # TODO: Float?! quantity = D(quantity) * config.UNIT if quantity == quantity.to_integral(): return int(quantity) raise QuantityError('Divisible assets have only eight decimal places of precision.') quantity = D(quantityM if quantity != round(quantity): raise QuantityError('Fractional quantities of indivisible assets.') return round(quantity) def value_in(db, quantity, asset, divisible=None): if asset not in ['leverage', 'value', 'fraction', 'price', 'odds'] and divisible == None: divisible = is_divisible(db, asset) return value_input(quantity, asset, divisible) def value_output(quantity, asset, divisible): def norm(num, places): """Round only if necessary.""" = round(num, places) fmt = '{:.' + str(places) + 'f}' num = fmt.format(num) return num.rstrip('0')+'0' if num.rstrip('0')[-1] == '.' else num.rstrip('0') if asset == 'fraction': return str(norm(D(quantity) * D(100), 6)) + '%' if asset in ('leverage', 'value', 'price', 'odds'): return norm(quantity, 6) quantity = D(quantity) / D(config.UNIT) if quantity == quantity.to_integral(): return str(quantity) + '.0' # For divisiM ble assets, display the decimal point. return norm(quantity, 8) quantity = D(quantity) if quantity != round(quantity): raise QuantityError('Fractional quantities of indivisible assets.') return round(quantity) def value_out(db, quantity, asset, divisible=None): if asset not in ['leverage', 'value', 'fraction', 'price', 'odds'] and divisible == None: divisible = is_divisible(db, asset) return value_output(quantity, asset, divisiM def holders(db, asset, exclude_empty_holders=False): """Return holders of the asset.""" cursor = db.cursor() if exclude_empty_holders: cursor.execute('''SELECT * FROM balances \ WHERE asset = ? AND quantity > ?''', (asset, 0)) cursor.execute('''SELECT * FROM balances \ WHERE asset = ?''', (asset, )) for balance in list(cursor): holders.append({'address': balaM nce['address'], 'address_quantity': balance['quantity'], 'escrow': None}) # Funds escrowed in orders. (Protocol change.) cursor.execute('''SELECT * FROM orders \ WHERE give_asset = ? AND status = ?''', (asset, 'open')) for order in list(cursor): holders.append({'address': order['source'], 'address_quantity': order['give_remaining'], 'escrow': order['tx_hash']}) # Funds escrowed in pending order matches. (Protocol change.) cursor.execute('''SELECT * FROM order_matcheM WHERE (forward_asset = ? AND status = ?)''', (asset, 'pending')) for order_match in list(cursor): holders.append({'address': order_match['tx0_address'], 'address_quantity': order_match['forward_quantity'], 'escrow': order_match['id']}) cursor.execute('''SELECT * FROM order_matches \ WHERE (backward_asset = ? AND status = ?)''', (asset, 'pending')) for order_match in list(cursor): holders.append({'address': order_match['tx1_address'], 'addreM ss_quantity': order_match['backward_quantity'], 'escrow': order_match['id']}) # Bets and RPS (and bet/rps matches) only escrow XCP. if asset == config.XCP: cursor.execute('''SELECT * FROM bets \ WHERE status = ?''', ('open',)) for bet in list(cursor): holders.append({'address': bet['source'], 'address_quantity': bet['wager_remaining'], 'escrow': bet['tx_hash']}) cursor.execute('''SELECT * FROM bet_matches \ WHERE statusM = ?''', ('pending',)) for bet_match in list(cursor): holders.append({'address': bet_match['tx0_address'], 'address_quantity': bet_match['forward_quantity'], 'escrow': bet_match['id']}) holders.append({'address': bet_match['tx1_address'], 'address_quantity': bet_match['backward_quantity'], 'escrow': bet_match['id']}) cursor.execute('''SELECT * FROM rps \ WHERE status = ?''', ('open',)) for rps in list(cursor): holders.append({'adM dress': rps['source'], 'address_quantity': rps['wager'], 'escrow': rps['tx_hash']}) cursor.execute('''SELECT * FROM rps_matches \ WHERE status IN (?, ?, ?)''', ('pending', 'pending and resolved', 'resolved and pending')) for rps_match in list(cursor): holders.append({'address': rps_match['tx0_address'], 'address_quantity': rps_match['wager'], 'escrow': rps_match['id']}) holders.append({'address': rps_match['tx1_address'], 'address_quantity': rps_matcM h['wager'], 'escrow': rps_match['id']}) if enabled('dispensers_in_holders'): # Funds escrowed in dispensers. cursor.execute('''SELECT * FROM dispensers \ WHERE asset = ? AND status = ?''', (asset, 0)) for dispenser in list(cursor): holders.append({'address': dispenser['source'], 'address_quantity': dispenser['give_remaining'], 'escrow': None}) def xcp_created (db): """Return number of XCP created thus faM cursor = db.cursor() cursor.execute('''SELECT SUM(earned) AS total FROM burns \ WHERE (status = ?)''', ('valid',)) total = list(cursor)[0]['total'] or 0 def xcp_destroyed (db): """Return number of XCP destroyed thus far.""" cursor = db.cursor() cursor.execute('''SELECT SUM(quantity) AS total FROM destructions \ WHERE (status = ? AND asset = ?)''', ('valid', config.XCP)) al = list(cursor)[0]['total'] or 0 # Subtract issuance fees. cursor.execute('''SELECT SUM(fee_paid) AS total FROM issuances\ WHERE status = ?''', ('valid',)) issuance_fee_total = list(cursor)[0]['total'] or 0 # Subtract dividend fees. cursor.execute('''SELECT SUM(fee_paid) AS total FROM dividends\ WHERE status = ?''', ('valid',)) dividend_fee_total = list(cursor)[0]['total'] or 0 # Subtract sweep fees. cursor.execute('''SELECT SUM(fee_paid)M AS total FROM sweeps\ WHERE status = ?''', ('valid',)) sweeps_fee_total = list(cursor)[0]['total'] or 0 return destroyed_total + issuance_fee_total + dividend_fee_total + sweeps_fee_total def xcp_supply (db): """Return the XCP supply.""" return xcp_created(db) - xcp_destroyed(db) """Return creations.""" cursor = db.cursor() creations = {config.XCP: xcp_created(db)} cursor.execute('''SELECT asset, SUM(quantity) AS createdM WHERE status = ? GROUP BY asset''', ('valid',)) for issuance in cursor: asset = issuance['asset'] created = issuance['created'] creations[asset] = created return creations def destructions (db): """Return destructions.""" cursor = db.cursor() destructions = {config.XCP: xcp_destroyed(db)} cursor.execute('''SELECT asset, SUM(quantity) AS destroyed FROM destructions \ WHERE (status = ? AM ND asset != ?) GROUP BY asset''', ('valid', config.XCP)) for destruction in cursor: asset = destruction['asset'] destroyed = destruction['destroyed'] destructions[asset] = destroyed return destructions def asset_issued_total (db, asset): """Return asset total issued.""" cursor = db.cursor() cursor.execute('''SELECT SUM(quantity) AS total FROM issuances \ WHERE (status = ? AND asset = ?)''', ('valid', asset)) issued_total = lM ist(cursor)[0]['total'] or 0 return issued_total def asset_destroyed_total (db, asset): """Return asset total destroyed.""" cursor = db.cursor() cursor.execute('''SELECT SUM(quantity) AS total FROM destructions \ WHERE (status = ? AND asset = ?)''', ('valid', asset)) destroyed_total = list(cursor)[0]['total'] or 0 return destroyed_total def asset_supply (db, asset): """Return asset supply.""" return asset_issued_total(db, M asset) - asset_destroyed_total(db, asset) """Return supplies.""" d1 = creations(db) d2 = destructions(db) return {key: d1[key] - d2.get(key, 0) for key in d1.keys()} def held (db): #TODO: Rename ? "SELECT asset, SUM(quantity) AS total FROM balances GROUP BY asset", "SELECT give_asset AS asset, SUM(give_remaining) AS total FROM orders WHERE status = 'open' GROUP BY asset", "SELECT give_asset AS asset, SUM(give_remaining) AS total FROM orderM s WHERE status = 'filled' and give_asset = 'XCP' and get_asset = 'BTC' GROUP BY asset", "SELECT forward_asset AS asset, SUM(forward_quantity) AS total FROM order_matches WHERE status = 'pending' GROUP BY asset", "SELECT backward_asset AS asset, SUM(backward_quantity) AS total FROM order_matches WHERE status = 'pending' GROUP BY asset", "SELECT 'XCP' AS asset, SUM(wager_remaining) AS total FROM bets WHERE status = 'open'", "SELECT 'XCP' AS asset, SUM(forward_quantity) AS total FROM beM t_matches WHERE status = 'pending'", "SELECT 'XCP' AS asset, SUM(backward_quantity) AS total FROM bet_matches WHERE status = 'pending'", "SELECT 'XCP' AS asset, SUM(wager) AS total FROM rps WHERE status = 'open'", "SELECT 'XCP' AS asset, SUM(wager * 2) AS total FROM rps_matches WHERE status IN ('pending', 'pending and resolved', 'resolved and pending')", "SELECT asset, SUM(give_remaining) AS total FROM dispensers WHERE status=0 OR status=1 GROUP BY asset", sql = "SELECT asM set, SUM(total) AS total FROM (" + " UNION ALL ".join(queries) + ") GROUP BY asset;" cursor = db.cursor() cursor.execute(sql) for row in cursor: asset = row['asset'] total = row['total'] held[asset] = total class GetURLError (Exception): pass def get_url(url, abort_on_error=False, is_json=True, fetch_timeout=5): """Fetch URL using requests.get.""" r = requests.get(url, timeout=fetch_timeout) raise GetURLError("Got get_url request error: %s" % e) if r.status_code != 200 and abort_on_error: raise GetURLError("Bad status code returned: '%s'. result body: '%s'." % (r.status_code, r.text)) result = json.loads(r.text) if is_json else r.text if not isinstance(text, bytes): text = bytes(str(text), 'utf-8') return hashlib.sha256(hashlib.sha256(text).digest()).digest() def dhash_string(text): rn binascii.hexlify(dhash(text)).decode() def get_balance (db, address, asset): """Get balance of contract or address.""" cursor = db.cursor() balances = list(cursor.execute('''SELECT * FROM balances WHERE (address = ? AND asset = ?)''', (address, asset))) if not balances: return 0 else: return balances[0]['quantity'] # Why on Earth does `binascii.hexlify()` return bytes?! """Return the hexadecimal representation of the binary data. Decode from ASCII to M return binascii.hexlify(x).decode('ascii') def unhexlify(hex_string): return binascii.unhexlify(bytes(hex_string, 'utf-8')) ### Protocol Changes ### def enabled(change_name, block_index=None): """Return True if protocol change is enabled.""" if config.REGTEST: return True # All changes are always enabled on REGTEST if config.TESTNET: index_name = 'testnet_block_index' index_name = 'block_index' enable_block_index = PROTOCOL_CHANGES[change_name][M if not block_index: block_index = CURRENT_BLOCK_INDEX if block_index >= enable_block_index: return False def get_value_by_block_index(change_name, block_index=None): if not block_index: block_index = CURRENT_BLOCK_INDEX if config.REGTEST: max_block_index_testnet = -1 for key, value in PROTOCOL_CHANGES[change_name]["testnet"]: if int(key) > int(max_block_index): max_block_index = keyM return PROTOCOL_CHANGES[change_name]["testnet"][max_block_index]["value"] if config.TESTNET: index_name = 'testnet' index_name = 'mainnet' max_block_index = -1 for key in PROTOCOL_CHANGES[change_name][index_name]: if int(key) > int(max_block_index) and block_index >= int(key): max_block_index = key return PROTOCOL_CHANGES[change_name][index_name][max_block_index]["value"] def transfer(db, source, destinatM ion, asset, quantity, action, event): """Transfer quantity of asset from source to destination.""" debit(db, source, asset, quantity, action=action, event=event) credit(db, destination, asset, quantity, action=action, event=event) def make_id(hash_1, hash_2): return hash_1 + ID_SEPARATOR + hash_2 def parse_id(match_id): assert match_id[64] == ID_SEPARATOR return match_id[:64], match_id[65:] # UTF-8 encoding means that the indices are doubled. ance(v, dict) or isinstance(v, DictCache): for dk, dv in v.items(): s += sizeof(dk) s += sizeof(dv) return sys.getsizeof(v) """Threadsafe FIFO dict cache""" def __init__(self, size=100): if int(size) < 1 : raise AttributeError('size < 1 or not a number') self.size = size self.dict = collections.OrderedDict() self.lock = threading.Lock() def __getitem__(self,keyM with self.lock: return self.dict[key] def __setitem__(self,key,value): with self.lock: while len(self.dict) >= self.size: self.dict.popitem(last=False) self.dict[key] = value def __delitem__(self,key): with self.lock: del self.dict[key] def __len__(self): with self.lock: return len(self.dict) def __contains__(self, key): with self.lock: return key in self.dict ef refresh(self, key): with self.lock: self.dict.move_to_end(key, last=True) URL_USERNAMEPASS_REGEX = re.compile('.+://(.+)@') def clean_url_for_log(url): m = URL_USERNAMEPASS_REGEX.match(url) if m and m.group(1): url = url.replace(m.group(1), 'XXXXXXXX') # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 def satoshirate_to_fiat(satoshirate): return round(satoshirate/100.0,2) def get_oracle_last_price(db, oracle_address, block_index): cursor.execute('SELECT * FROM broadcasts WHERE source=:source AND status=:status AND block_index<:block_index ORDER by tx_index DESC LIMIT 1', { 'source': oracle_address, 'status': 'valid', 'block_index': block_index broadcasts = cursor.fetchall() if len(broadcasts) == 0: return None, None oracle_broadcast = broadcasts[0] oracle_label = oracle_broadcast["text"].split("-") if len(oracle_label) == 2: fiat_label = oracle_label[1] fiat_label = "" return oracle_broadcast['value'], oracle_broadcast['fee_fraction_int'], fiat_label, oracle_broadcast['block_index'] #### util_windows.py import logging.handlers logger = logging.getLogger(__name__) from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_int from ctypes.wintypes import BOOL, HANDLE, DWORD, LPWSTR, LPCWSTR, LPVOID class SanitizedRotatingFileHanM dler(logging.handlers.RotatingFileHandler): def emit(self, record): # If the message doesn't need to be rendered we take a shortcut. if record.levelno < self.level: # Make sure the message is a string. message = record.msg #Sanitize and clean up the message message = unicodedata.normalize('NFKD', message).encode('ascii', 'ignore').decode() # Copy the original record so we don't break other handlers. record = copy.copy(record) record.msg = message # Use the built-in stream handler to handle output. logging.handlers.RotatingFileHandler.emit(self, record) def fix_win32_unicode(): """Thanks to http://stackoverflow.com/a/3259271 ! (converted to python3)""" if sys.platform != "win32": original_stderr = sys.stderr # If any exception occurs in this code, we'll probably try to print it on stderr, # which makes for frustrating debugging if stderr is directed to our wrapper. ranoid about catching errors and reporting them to original_stderr, # so that we can at least see them. def _complain(message): print(message if isinstance(message, str) else repr(message), file=original_stderr) # Work around <http://bugs.python.org/issue6058>. codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) # Make Unicode console output work independently of the current code page. # This also fixes <http://bugs.python.org/issue1602>. t to Michael Kaplan <http://blogs.msdn.com/b/michkap/archive/2010/04/07/9989346.aspx> # and TZOmegaTZIOY # <http://stackoverflow.com/questions/878972/windows-cmd-encoding-change-causes-python-crash/1432462#1432462>. # <http://msdn.microsoft.com/en-us/library/ms683231(VS.85).aspx> # HANDLE WINAPI GetStdHandle(DWORD nStdHandle); # returns INVALID_HANDLE_VALUE, NULL, or a valid handle # <http://msdn.microsoft.com/en-us/library/aa364960(VS.85).aspx> WORD WINAPI GetFileType(DWORD hFile); # <http://msdn.microsoft.com/en-us/library/ms683167(VS.85).aspx> # BOOL WINAPI GetConsoleMode(HANDLE hConsole, LPDWORD lpMode); GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)(("GetStdHandle", windll.kernel32)) STD_OUTPUT_HANDLE = DWORD(-11) STD_ERROR_HANDLE = DWORD(-12) GetFileType = WINFUNCTYPE(DWORD, DWORD)(("GetFileType", windll.kernel32)) FILE_TYPE_CHAR = 0x0002 FILE_TYPE_REMOTE = 0x8000 eMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))(("GetConsoleMode", windll.kernel32)) INVALID_HANDLE_VALUE = DWORD(-1).value def not_a_console(handle): if handle == INVALID_HANDLE_VALUE or handle is None: return True return ((GetFileType(handle) & ~FILE_TYPE_REMOTE) != FILE_TYPE_CHAR or GetConsoleMode(handle, byref(DWORD())) == 0) old_stdout_fileno = None old_stderr_fileno = None if hasattr(sys.stdout, 'fileno'): old_stdout_fileno = sys.stdout.fileno() if hasattr(sys.stderr, 'fileno'): old_stderr_fileno = sys.stderr.fileno() STDOUT_FILENO = 1 STDERR_FILENO = 2 real_stdout = (old_stdout_fileno == STDOUT_FILENO) real_stderr = (old_stderr_fileno == STDERR_FILENO) if real_stdout: hStdout = GetStdHandle(STD_OUTPUT_HANDLE) if not_a_console(hStdout): real_stdout = False if real_stderr: hStderr = GeM tStdHandle(STD_ERROR_HANDLE) if not_a_console(hStderr): real_stderr = False if real_stdout or real_stderr: # BOOL WINAPI WriteConsoleW(HANDLE hOutput, LPWSTR lpBuffer, DWORD nChars, # LPDWORD lpCharsWritten, LPVOID lpReserved); WriteConsoleW = WINFUNCTYPE(BOOL, HANDLE, LPWSTR, DWORD, POINTER(DWORD), LPVOID)(("WriteConsoleW", windll.kernel32)) class UnicodeOutput: def __init__(self, hConsoM le, stream, fileno, name): self._hConsole = hConsole self._stream = stream self._fileno = fileno self.closed = False self.softspace = False self.mode = 'w' self.encoding = 'utf-8' self.name = name self.errors = '' self.flush() def isatty(self): return False def closM # don't really close the handle, that would only cause problems self.closed = True def fileno(self): return self._fileno def flush(self): if self._hConsole is None: try: self._stream.flush() except Exception as e: _complain("%s.flush: %r from %r" % (self.name, e, self._stream)) def write(self, text): try: if self._hConsole is None: if isinstance(text, str): text = text.encode('utf-8') self._stream.write(text) else: if not isinstance(text, str): text = str(text).decode('utf-8') remaining = len(text) while remaining: n = DWORD(0) # There is a shorter-than-documented limitation on the # length of the string passed to WriteConsoleW (see # <http://tahoe-lafs.org/trac/tahoe-lafs/ticket/1232>. retval = WriteConsoleW(self._hConsole, text, min(remaining, 10000), byref(n), None) if retval == 0 or n.value == 0: raise IOError("WriteConsoleW returned %r, n.value = %r" % (retval, n.value)) remaining -= n.value if not remaining: break text = text[n.value:] except Exception as e: _complain("%s.write: %r" % (self.name, e)) raise def writelines(self, lines): try: for line in lines: self.write(line) except Exception as e: _complain("%s.writelines: %r" % (self.name, e)) raise if real_stdout: sys.stdout = UnicodeOutput(hStdout, None, STDOUT_FILENO, '<Unicode console stdout>') sys.stdout = UnicodeOutput(None, sys.stdout, old_stdout_fileno, '<Unicode redirected stdout>') if real_stderr: sys.stderr = UnicodeOutput(hStderr, None, STDERR_FILENO, '<Unicode console stderr>') sys.stderr = UnicodeOutput(None, sys.stderr, old_stderr_fileno, '<Unicode redirected stderr>') except Exception as e: _complain("exception %r while fixing up sys.stdout and sys.stderr" % (e,)) # While we're at it, let's unmangle the command-line arguments: # This works around <http://bugs.python.org/issue2128>. GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommM andLineW", windll.kernel32)) CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(("CommandLineToArgvW", windll.shell32)) argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc)) argv = [argv_unicode[i].encode('utf-8').decode('utf-8') for i in range(0, argc.value)] if not hasattr(sys, 'frozen'): # If this is an executable produced by py2exe or bbfreeze, then it will # have been invoked directly. Otherwise, unicode_argv[0] is the PM # interpreter, so skip that. argv = argv[1:] # Also skip option arguments to the Python interpreter. while len(argv) > 0: arg = argv[0] if not arg.startswith("-") or arg == "-": break argv = argv[1:] if arg == '-m': # sys.argv[0] should really be the absolute path of the module source, # but never mind break if arg == '-c': argv[0] = '-c' break #### addrindexrs.py logger = logging.getLogger(__name__) from requests.exceptions import Timeout, ReadTimeout, ConnectionError import concurrent.futures import bitcoin.wallet from counterpartylib.lib import config, util, address SOCKET_TIMEOUT = 5.0 BACKEND_PING_TIME = 30.0 raw_transactions_cache = util.DictCache(size=config.BACKEND_RAW_TRANSACTIONS_CACHE_SIZE) # used in getrawtransaction_batch() class BackendRPCError(Exception): class AddrIndexRsRPCError(Exception): def rpc_call(payload): """Calls to bitcoin core and returns the response""" url = config.BACKEND_URL for i in range(TRIES): response = requests.post(url, data=json.dumps(payloaM d), headers={'content-type': 'application/json'}, verify=(not config.BACKEND_SSL_NO_VERIFY), timeout=config.REQUESTS_TIMEOUT) if i > 0: logger.debug('Successfully connected.') except (Timeout, ReadTimeout, ConnectionError): logger.debug('Could not connect to backend at `{}`. (Try {}/{})'.format(util.clean_url_for_log(url), i+1, TRIES)) time.sleep(5) if response == None: if config.TESTNET: network =M elif config.REGTEST: network = 'regtest' network = 'mainnet' raise BackendRPCError('Cannot communicate with backend at `{}`. (server is set to run on {}, is backend?)'.format(util.clean_url_for_log(url), network)) elif response.status_code in (401,): raise BackendRPCError('Authorization error connecting to {}: {} {}'.format(util.clean_url_for_log(url), response.status_code, response.reason)) elif response.status_code not in (200, 500):M raise BackendRPCError(str(response.status_code) + ' ' + response.reason) # Handle json decode errors response_json = response.json() except json.decoder.JSONDecodeError as e: raise BackendRPCError('Received invalid JSON from backend with a response of {}'.format(str(response.status_code) + ' ' + response.reason)) # Batch query returns a list if isinstance(response_json, list): return response_json if 'error' not in response_json.keys() or response_jsonM return response_json['result'] elif response_json['error']['code'] == -5: # RPC_INVALID_ADDRESS_OR_KEY raise BackendRPCError('{} Is `txindex` enabled in {} Core?'.format(response_json['error'], config.BTC_NAME)) elif response_json['error']['code'] in [-28, -8, -2]: Block height out of range The network does not appear to fully agree! logger.debug('Backend not ready. Sleeping for ten seconds.') coin Core takes more than `sys.getrecursionlimit() * 10 = 9970` # seconds to start, this ll hit the maximum recursion depth limit. time.sleep(10) return rpc_call(payload) raise BackendRPCError('Error connecting to {}: {}'.format(util.clean_url_for_log(url), response_json['error'])) def rpc(method, params): "method": method, "params": params, "jsonrpc": "2.0", return rpc_call(payload) def rpc_batch(requesM responses = collections.deque() def make_call(chunk): #send a list of requests to bitcoind to be executed #note that this is list executed serially, in the same thread in bitcoind #e.g. see: https://github.com/bitcoin/bitcoin/blob/master/src/rpcserver.cpp#L939 responses.extend(rpc_call(chunk)) chunks = util.chunkify(request_list, config.RPC_BATCH_SIZE) with concurrent.futures.ThreadPoolExecutor(max_workers=config.BACKEND_RPC_BATCH_NUM_WORKERS) as executor: for chunk in chunks: executor.submit(make_call, chunk) return list(responses) def extract_addresses(txhash_list): logger.debug('extract_addresses, txs: %d' % (len(txhash_list), )) tx_hashes_tx = getrawtransaction_batch(txhash_list, verbose=True) return extract_addresses_from_txlist(tx_hashes_tx, getrawtransaction_batch) def extract_addresses_from_txlist(tx_hashes_tx, _getrawtransaction_batch): helper for extract_addresses, seperated so we can pass in a mocked _getrM awtransaction_batch for test purposes logger.debug('extract_addresses_from_txlist, txs: %d' % (len(tx_hashes_tx.keys()), )) tx_hashes_addresses = {} tx_inputs_hashes = set() # use set to avoid duplicates for tx_hash, tx in tx_hashes_tx.items(): tx_hashes_addresses[tx_hash] = set() for vout in tx['vout']: if 'addresses' in vout['scriptPubKey']: tx_hashes_addresses[tx_hash].update(tuple(vout['scriptPubKey']['addresses'])) tx_inputs_hashesM .update([vin['txid'] for vin in tx['vin']]) logger.debug('extract_addresses, input TXs: %d' % (len(tx_inputs_hashes), )) # chunk txs to avoid huge memory spikes for tx_inputs_hashes_chunk in util.chunkify(list(tx_inputs_hashes), config.BACKEND_RAW_TRANSACTIONS_CACHE_SIZE): raw_transactions = _getrawtransaction_batch(tx_inputs_hashes_chunk, verbose=True) for tx_hash, tx in tx_hashes_tx.items(): for vin in tx['vin']: vin_tx = raw_transactions.get(vin['txid'], M if not vin_tx: continue vout = vin_tx['vout'][vin['vout']] if 'addresses' in vout['scriptPubKey']: tx_hashes_addresses[tx_hash].update(tuple(vout['scriptPubKey']['addresses'])) return tx_hashes_addresses, tx_hashes_tx def getblockcount(): return rpc('getblockcount', []) def getblockhash(blockcount): return rpc('getblockhash', [blockcount]) def getblock(block_hash): return rpc('getblock', [block_hash, M def getrawtransaction(tx_hash, verbose=False, skip_missing=False): return getrawtransaction_batch([tx_hash], verbose=verbose, skip_missing=skip_missing)[tx_hash] def getrawmempool(): return rpc('getrawmempool', []) def fee_per_kb(conf_target, mode, nblocks=None): :param conf_target: :return: fee_per_kb in satoshis, or None when unable to determine if nblocks is None and conf_target is None: conf_target = nblocks feeperkb = rpc('estimatesmaM rtfee', [conf_target, mode]) if 'errors' in feeperkb and feeperkb['errors'][0] == 'Insufficient data or no feerate found': return int(max(feeperkb['feerate'] * config.UNIT, config.DEFAULT_FEE_PER_KB_ESTIMATE_SMART)) def sendrawtransaction(tx_hex): return rpc('sendrawtransaction', [tx_hex]) GETRAWTRANSACTION_MAX_RETRIES=2 monotonic_call_id = 0 def getrawtransaction_batch(txhash_list, verbose=False, skip_missing=False, _retry=0): _logger = logger.getChild("getrawtransaction_batM if len(txhash_list) > config.BACKEND_RAW_TRANSACTIONS_CACHE_SIZE: #don't try to load in more than BACKEND_RAW_TRANSACTIONS_CACHE_SIZE entries in a single call txhash_list_chunks = util.chunkify(txhash_list, config.BACKEND_RAW_TRANSACTIONS_CACHE_SIZE) for txhash_list_chunk in txhash_list_chunks: txes.update(getrawtransaction_batch(txhash_list_chunk, verbose=verbose, skip_missing=skip_missing)) tx_hash_call_id = {} noncached_txhashes = set() txhash_list = set(txhash_list) # payload for transactions not in cache for tx_hash in txhash_list: if tx_hash not in raw_transactions_cache: #call_id = binascii.hexlify(os.urandom(5)).decode('utf8') # Don't drain urandom global monotonic_call_id monotonic_call_id = monotonic_call_id + 1 call_id = "{}".format(monotonic_call_id) payload.append({ "method": 'getrawtransaction', "params": [tx_hash, 1], "jsonrpc": "2.0", "id": call_id noncached_txhashes.add(tx_hash) tx_hash_call_id[call_id] = tx_hash #refresh any/all cache entries that already exist in the cache, # so they're not inadvertently removed by another thread before we can consult them #(this assumes that the size of the working set for any given workload doesn't exceed the max size of the cache) for tx_hash in txhash_list.difference(noncacM raw_transactions_cache.refresh(tx_hash) _logger.debug("getrawtransaction_batch: txhash_list size: {} / raw_transactions_cache size: {} / # getrawtransaction calls: {}".format( len(txhash_list), len(raw_transactions_cache), len(payload))) # populate cache if len(payload) > 0: batch_responses = rpc_batch(payload) for response in batch_responses: if 'error' not in response or response['error'] is None: tx_hex = response['result'] tx_hash = tx_hash_call_id[response['id']] raw_transactions_cache[tx_hash] = tx_hex elif skip_missing and 'error' in response and response['error']['code'] == -5: raw_transactions_cache[tx_hash] = None logging.debug('Missing TX with no raw info skipped (txhash: {}): {}'.format( tx_hash_call_id.get(response.get('id', '??'), '??'), response['error'])) #TODO: this seems to happen for bogus tM ransactions? Maybe handle it more gracefully than just erroring out? raise BackendRPCError('{} (txhash:: {})'.format(response['error'], tx_hash_call_id.get(response.get('id', '??'), '??'))) # get transactions from cache for tx_hash in txhash_list: if verbose: result[tx_hash] = raw_transactions_cache[tx_hash] result[tx_hash] = raw_transactions_cache[tx_hash]['hex'] if raw_transactions_cache[tx_hash] isM except KeyError as e: #shows up most likely due to finickyness with addrindex not always returning results that we need... print("Key error in addrindexrs still exists!!!!!") _logger.warning("tx missing in rawtx cache: {} -- txhash_list size: {}, hash: {} / raw_transactions_cache size: {} / # rpc_batch calls: {} / txhash in noncached_txhashes: {} / txhash in txhash_list: {} -- list {}".format( e, len(txhash_list), hashlib.md5(json.dumps(list(txhashM _list)).encode()).hexdigest(), len(raw_transactions_cache), len(payload), tx_hash in noncached_txhashes, tx_hash in txhash_list, list(txhash_list.difference(noncached_txhashes)) )) if _retry < GETRAWTRANSACTION_MAX_RETRIES: #try again time.sleep(0.05 * (_retry + 1)) # Wait a bit, hitting the index non-stop may cause it to just break down... TODO: Better handling r = getrawtransaction_batch([tx_hash], verbose=verbose, skip_missing=skip_missing, _retry=_retM result[tx_hash] = r[tx_hash] raise #already tried again, give up class AddrIndexRsThread (threading.Thread): def __init__(self, host, port): threading.Thread.__init__(self) self.host = host self.port = port self.sock = None self.lastId = 0 self.message_to_send = None self.message_result = None self.is_killed = False logging.debug('AddrIndexRs thM self.send({"kill": True}) def connect(self): self.lastId = 0 logging.info('AddrIndexRs connecting...') self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(SOCKET_TIMEOUT) self.sock.connect((self.host, self.port)) logging.info('Error connecting to AddrIndexRs! Retrying in a few seconds') time.sleep(5.0) logging.info('Connected to AddrIndexRs!') break self.locker = threading.Condition() self.locker.acquire() self.connect() while self.locker.wait(): if not(self.is_killed) and self.message_to_send != None: msg = self.message_to_send self.message_to_send = None retry_count = 15 while retry_count > 0: has_sent = False while not(has_sent) and msg: try: logging.debug('AddrIndexRs sending') self.sock.send(msg) has_sent = True except Exception as e: #try: logging.debug('AddrIndexRs error:' + e) self.connect() #except Exception as e2: #logging.debug('AddrIndexRM s fatal error:' + e2) self.message_to_send = None data = b"" parsed = False while not(parsed): try: data = data + self.sock.recv(READ_BUF_SIZE) self.message_result = json.loads(data.decode('utf-8')) retry_count = 0 parsed = True logging.debug('AddrIndexRs Recv complete!') except socket.timeout: logging.debug('AddrIndexRs Recv timeout error sending: '+str(msg)) if retry_count <= 0: self.connect() self.message_result = None retry_count -= -1 except socket.error as e: logging.debug('AddrIndexRs Recv error:' + str(e)+' with msg '+str(msg)) self.M except Exception as e: logging.debug('AddrIndexRs Recv error:' + str(e)+' with msg '+str(msg)) if retry_count <= 0: raise e self.message_result = None retry_count -= 1 finally: self.locker.notify() self.locker.notify() self.sock.close() ging.debug('AddrIndexRs socket closed normally') def send(self, msg): self.locker.acquire() if not("kill" in msg): msg["id"] = self.lastId self.lastId += 1 self.message_to_send = (json.dumps(msg) + "\n").encode('utf8') self.locker.notify() self.locker.wait() self.locker.release() return self.message_result def ensure_addrindexrs_connected(): max_backoff = 5000 le _backend == None: _backend = AddrIndexRsThread(config.INDEXD_CONNECT, config.INDEXD_PORT) _backend.daemon = True _backend.start() _backend.send({ "method": "server.version", "params": [] except Exception as e: logger.debug(e) time.sleep(backoff) backoff = min(backoff * 1.5, max_backoff) def _script_pubkey_to_hash(spk): return hashlib.sha256(spk).digest()[::M def _address_to_hash(addr): script_pubkey = bitcoin.wallet.CBitcoinAddress(addr).to_scriptPubKey() return _script_pubkey_to_hash(script_pubkey) # Returns an array of UTXOS from an address in the following format # "txId": utxo_txid_hex, # "confirmations": num # [{"txId":"a0d12eb3716e2e70fd00525486ace0da2947f82d818b7be0285f16ff672cf237","vout":5,"height":647484,"value":30455293,"confirmations":2}] def unpack_outpoint(outpoint): txid, vout = outpoint.split(':') return (txid, int(vout)) def unpack_vout(outpoint, tx, block_count): vout = tx["vout"][outpoint[1]] if "confirmations" in tx and tx["confirmations"] > 0: height = block_count - tx["confirmations"] + 1 tx["confirmations"] = 0 "txId": tx["txid"], "vout": outpoint[1], "height": height, "value": int(round(vout["value"] * config.UNIT)), "confirmations": tx["confirmations"] def get_unspent_txouts(source): ensure_addrindexrs_connected() block_count = getblockcount() result = _backend.send({ "method": "blockchain.scripthash.get_utxos", "params": [_address_to_hash(source)] if not(result is None) and "result" in result: result = result["result"] result = [unpack_outpoint(x) for x in result] # each item on the result array is like # {"tx_hash": hex_encoded_hash} h = getrawtransaction_batch([x[0] for x in result], verbose=True, skip_missing=True) batch = [unpack_vout(outpoint, batch[outpoint[0]], block_count) for outpoint in result if outpoint[0] in batch] batch = [x for x in batch if x is not None] return batch # Returns transactions in the following format # "blockhash": hexstring, # "blocktime": num, # "confirmations": num, # "hash": hexstring, # "hex": hexstring, # "txid": hexstring, # "txinwitness": array of hex_witness_program, // Only if it's a witness-containing tx # "txid": hexstring, # "sequence": num, # "coinbase": X, // contents not important, this is only present if the tx is a coinbase # "scriptSig": { # "asm": asm_decompiled_program, # "hex": hex_program # "value": decimal, # "scriptPubKey": { # "type": string, # "reqSigs": num, # "hex": hexstring, // the program in hex # "asm": string, // the decompiled program # "addresses": [ ...list of found addresses on the program ] def search_raw_transactions(address, unconfirmed=True): ensure_addrindexrs_connected() hsh = _address_to_hash(address) txs = _backend.send({ "method": "blockchain.scripthash.getM "params": [hsh] batch = getrawtransaction_batch([x["tx_hash"] for x in txs], verbose=True) if not(unconfirmed): batch = [x for x in batch if x.height >= 0] # Returns the number of blocks the backend is behind the node def getindexblocksbehind(): # Addrindexrs never "gets behind" ensure_addrindexrs_connected() if '_backend' in globals(): _backend.stop() logger = logging.getLogger(__name__) from requests.exceptions import Timeout, ReadTimeout, ConnectionError import concurrent.futures from counterpartylib.lib import config, util raw_transactions_cache = util.DictCache(size=config.BACKEND_RAW_TRANSACTIONS_CACHE_SIZE) # used in getrawtransaction_batch() class BackendRPCError(Exception): class IndexdRPCError(Exception):M def rpc_call(payload): """Calls to bitcoin core and returns the response""" url = config.BACKEND_URL for i in range(TRIES): response = requests.post(url, data=json.dumps(payload), headers={'content-type': 'application/json'}, verify=(not config.BACKEND_SSL_NO_VERIFY), timeout=config.REQUESTS_TIMEOUT) if i > 0: logger.debug('Successfully connected.') except (TimeoM ut, ReadTimeout, ConnectionError): logger.debug('Could not connect to backend at `{}`. (Try {}/{})'.format(util.clean_url_for_log(url), i+1, TRIES)) time.sleep(5) if response == None: if config.TESTNET: network = 'testnet' elif config.REGTEST: network = 'regtest' network = 'mainnet' raise BackendRPCError('Cannot communicate with backend at `{}`. (server is set to run on {}, is backend?)'.format(util.clean_url_for_M elif response.status_code in (401,): raise BackendRPCError('Authorization error connecting to {}: {} {}'.format(util.clean_url_for_log(url), response.status_code, response.reason)) elif response.status_code not in (200, 500): raise BackendRPCError(str(response.status_code) + ' ' + response.reason) # Handle json decode errors response_json = response.json() except json.decoder.JSONDecodeError as e: raise BackendRPCError('Received invalid JM SON from backend with a response of {}'.format(str(response.status_code) + ' ' + response.reason)) # Batch query returns a list if isinstance(response_json, list): return response_json if 'error' not in response_json.keys() or response_json['error'] == None: return response_json['result'] elif response_json['error']['code'] == -5: # RPC_INVALID_ADDRESS_OR_KEY raise BackendRPCError('{} Is `txindex` enabled in {} Core?'.format(response_json['error'], config.BTC_NAME)) if response_json['error']['code'] in [-28, -8, -2]: Block height out of range The network does not appear to fully agree! logger.debug('Backend not ready. Sleeping for ten seconds.') # If Bitcoin Core takes more than `sys.getrecursionlimit() * 10 = 9970` # seconds to start, this ll hit the maximum recursion depth limit. time.sleep(10) return rpc_call(payload) raise BackendRPCError('Error connecting to {M }: {}'.format(util.clean_url_for_log(url), response_json['error'])) def rpc(method, params): "method": method, "params": params, "jsonrpc": "2.0", return rpc_call(payload) def rpc_batch(request_list): responses = collections.deque() def make_call(chunk): #send a list of requests to bitcoind to be executed #note that this is list executed serially, in the same thread in bitcoind #e.g. see: https://github.com/bitcoinM /bitcoin/blob/master/src/rpcserver.cpp#L939 responses.extend(rpc_call(chunk)) chunks = util.chunkify(request_list, config.RPC_BATCH_SIZE) with concurrent.futures.ThreadPoolExecutor(max_workers=config.BACKEND_RPC_BATCH_NUM_WORKERS) as executor: for chunk in chunks: executor.submit(make_call, chunk) return list(responses) def extract_addresses(txhash_list): logger.debug('extract_addresses, txs: %d' % (len(txhash_list), )) tx_hashes_tx = getrawtransaction_batch(txhashM _list, verbose=True) return extract_addresses_from_txlist(tx_hashes_tx, getrawtransaction_batch) def extract_addresses_from_txlist(tx_hashes_tx, _getrawtransaction_batch): helper for extract_addresses, seperated so we can pass in a mocked _getrawtransaction_batch for test purposes logger.debug('extract_addresses_from_txlist, txs: %d' % (len(tx_hashes_tx.keys()), )) tx_hashes_addresses = {} tx_inputs_hashes = set() # use set to avoid duplicates for tx_hash, tx in tx_hashM tx_hashes_addresses[tx_hash] = set() for vout in tx['vout']: if 'addresses' in vout['scriptPubKey']: tx_hashes_addresses[tx_hash].update(tuple(vout['scriptPubKey']['addresses'])) tx_inputs_hashes.update([vin['txid'] for vin in tx['vin']]) logger.debug('extract_addresses, input TXs: %d' % (len(tx_inputs_hashes), )) # chunk txs to avoid huge memory spikes for tx_inputs_hashes_chunk in util.chunkify(list(tx_inputs_hashes), config.BACKENDM _RAW_TRANSACTIONS_CACHE_SIZE): raw_transactions = _getrawtransaction_batch(tx_inputs_hashes_chunk, verbose=True) for tx_hash, tx in tx_hashes_tx.items(): for vin in tx['vin']: vin_tx = raw_transactions.get(vin['txid'], None) if not vin_tx: continue vout = vin_tx['vout'][vin['vout']] if 'addresses' in vout['scriptPubKey']: tx_hashes_addresses[tx_hash].update(tuple(vout['scriptPubKey']M return tx_hashes_addresses, tx_hashes_tx def getblockcount(): return rpc('getblockcount', []) def getblockhash(blockcount): return rpc('getblockhash', [blockcount]) def getblock(block_hash): return rpc('getblock', [block_hash, False]) def getrawtransaction(tx_hash, verbose=False, skip_missing=False): return getrawtransaction_batch([tx_hash], verbose=verbose, skip_missing=skip_missing)[tx_hash] def getrawmempool(): return rpc('getrawmempool', []) def fee_per_kb(conf_tarM get, mode, nblocks=None): :param conf_target: :return: fee_per_kb in satoshis, or None when unable to determine if nblocks is None and conf_target is None: conf_target = nblocks feeperkb = rpc('estimatesmartfee', [conf_target, mode]) if 'errors' in feeperkb and feeperkb['errors'][0] == 'Insufficient data or no feerate found': return int(max(feeperkb['feerate'] * config.UNIT, config.DEFAULT_FEE_PER_KB_ESTIMATE_SMART)) wtransaction(tx_hex): return rpc('sendrawtransaction', [tx_hex]) GETRAWTRANSACTION_MAX_RETRIES=2 def getrawtransaction_batch(txhash_list, verbose=False, skip_missing=False, _retry=0): _logger = logger.getChild("getrawtransaction_batch") if len(txhash_list) > config.BACKEND_RAW_TRANSACTIONS_CACHE_SIZE: #don't try to load in more than BACKEND_RAW_TRANSACTIONS_CACHE_SIZE entries in a single call txhash_list_chunks = util.chunkify(txhash_list, config.BACKEND_RAW_TRANSACTIONS_CACHE_SIZE) for txhash_list_chunk in txhash_list_chunks: txes.update(getrawtransaction_batch(txhash_list_chunk, verbose=verbose, skip_missing=skip_missing)) tx_hash_call_id = {} noncached_txhashes = set() txhash_list = set(txhash_list) # payload for transactions not in cache for tx_hash in txhash_list: if tx_hash not in raw_transactions_cache: call_id = binascii.hexlify(os.urandom(5)).decode('utf8') "method": 'getrawtransaction', "params": [tx_hash, 1], "jsonrpc": "2.0", "id": call_id noncached_txhashes.add(tx_hash) tx_hash_call_id[call_id] = tx_hash #refresh any/all cache entries that already exist in the cache, # so they're not inadvertently removed by another thread before we can consult them #(this assumes that the size of the working set for any given workload doesn't exceed tM he max size of the cache) for tx_hash in txhash_list.difference(noncached_txhashes): raw_transactions_cache.refresh(tx_hash) _logger.debug("getrawtransaction_batch: txhash_list size: {} / raw_transactions_cache size: {} / # getrawtransaction calls: {}".format( len(txhash_list), len(raw_transactions_cache), len(payload))) # populate cache if len(payload) > 0: batch_responses = rpc_batch(payload) for response in batch_responses: if 'error' not in response M or response['error'] is None: tx_hex = response['result'] tx_hash = tx_hash_call_id[response['id']] raw_transactions_cache[tx_hash] = tx_hex elif skip_missing and 'error' in response and response['error']['code'] == -5: raw_transactions_cache[tx_hash] = None logging.debug('Missing TX with no raw info skipped (txhash: {}): {}'.format( tx_hash_call_id.get(response.get('id', '??'), '??'), response['error']))M #TODO: this seems to happen for bogus transactions? Maybe handle it more gracefully than just erroring out? raise BackendRPCError('{} (txhash:: {})'.format(response['error'], tx_hash_call_id.get(response.get('id', '??'), '??'))) # get transactions from cache for tx_hash in txhash_list: if verbose: result[tx_hash] = raw_transactions_cache[tx_hash] result[tx_hash] = raM w_transactions_cache[tx_hash]['hex'] if raw_transactions_cache[tx_hash] is not None else None except KeyError as e: #shows up most likely due to finickyness with addrindex not always returning results that we need... _logger.warning("tx missing in rawtx cache: {} -- txhash_list size: {}, hash: {} / raw_transactions_cache size: {} / # rpc_batch calls: {} / txhash in noncached_txhashes: {} / txhash in txhash_list: {} -- list {}".format( e, len(txhash_list), hashlib.md5(json.dumps(lM ist(txhash_list)).encode()).hexdigest(), len(raw_transactions_cache), len(payload), tx_hash in noncached_txhashes, tx_hash in txhash_list, list(txhash_list.difference(noncached_txhashes)) )) if _retry < GETRAWTRANSACTION_MAX_RETRIES: #try again time.sleep(0.05 * (_retry + 1)) # Wait a bit, hitting the index non-stop may cause it to just break down... TODO: Better handling r = getrawtransaction_batch([tx_hash], verbose=verbose, skip_missing=skip_missing, _M result[tx_hash] = r[tx_hash] raise #already tried again, give up def get_unspent_txouts(source): return indexd_rpc_call('/a/'+source+'/utxos') def search_raw_transactions(address, unconfirmed=True): all_transactions = indexd_rpc_call('/a/'+address+'/txs?verbose=1') return all_transactions # filter for confirmed transactions only confirmed_transactions = list(filter(lambda t: 'confirmaM tions' in t and t['confirmations'] > 0, all_transactions)) return confirmed_transactions def getindexblocksbehind(): status = indexd_rpc_call('/status') if status['ready']: if status['blocksBehind']: return status['blocksBehind'] raise IndexdRPCError('Unknown status for indexd') def indexd_rpc_call(path): url = config.INDEXD_URL+path response = requests.get(url, headers={'content-type': 'application/json'}, t=config.REQUESTS_TIMEOUT) except (Timeout, ReadTimeout, ConnectionError): logger.debug('Could not connect to backend at `{}`.'.format(util.clean_url_for_log(url),)) if response == None: if config.TESTNET: network = 'testnet' elif config.REGTEST: network = 'regtest' network = 'mainnet' raise IndexdRPCError('Cannot communicate with {} indexd server at `{}`.'.format(network, util.clean_url_for_log(url))) elif response.statuM raise IndexdRPCError('Indexd returned error: {} {} {}'.format(response.status_code, response.reason, response.text)) elif response.status_code not in (200, 500): raise IndexdRPCError("Bad response from {}: {} {}".format(util.clean_url_for_log(url), response.status_code, response.reason)) # Return result, with error handling. response_json = response.json() if isinstance(response_json, (list, tuple)) or 'error' not in response_json.keys() or response_json['error'] == NM return response_json raise IndexdRPCError('{}'.format(response_json['error'])) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 logger = logging.getLogger(__name__) from decimal import Decimal as D import bitcoin as bitcoinlib import bitcoin.rpc as bitcoinlib_rpc from bitcoin.core import CBlock from counterpartylib.lib import util from counterpartylib.lib import script from counterpartylib.lib import config from counterpartylib.lib import exceptions from counterpartylib.lib.backend import addrindexrs MEMPOOL_CACHE_INITIALIZED = False def sortkeypicker(keynames): """http://stackoverflow.com/a/1143719""" for i, k in enumerate(keynames): if k[:1] == '-': keynames[i] = k[1:] negate.add(k[1:]) def getit(adict): composite = [adict[k] for k in keynames] for i, (k, v) in enumerate(zip(keynamM if k in negate: composite[i] = -v return composite mdl = sys.modules['counterpartylib.lib.backend.{}'.format(config.BACKEND_NAME)] BACKEND().stop() def getblockcount(): return BACKEND().getblockcount() def getblockhash(blockcount): return BACKEND().getblockhash(blockcount) def getblock(block_hash): block_hex = BACKEND().getblock(block_hash) return CBlock.deserializM e(util.unhexlify(block_hex)) def cache_pretx(txid, rawtx): PRETX_CACHE[binascii.hexlify(txid).decode('utf8')] = binascii.hexlify(rawtx).decode('utf8') def clear_pretx(txid): del PRETX_CACHE[binascii.hexlify(txid).decode('utf8')] def getrawtransaction(tx_hash, verbose=False, skip_missing=False): if tx_hash in PRETX_CACHE: return PRETX_CACHE[tx_hash] return BACKEND().getrawtransaction(tx_hash, verbose=verbose, skip_missing=skip_missing) def getrawtransaction_batch(txhash_listM , verbose=False, skip_missing=False): return BACKEND().getrawtransaction_batch(txhash_list, verbose=verbose, skip_missing=skip_missing) def sendrawtransaction(tx_hex): return BACKEND().sendrawtransaction(tx_hex) def getrawmempool(): return BACKEND().getrawmempool() def getindexblocksbehind(): return BACKEND().getindexblocksbehind() def extract_addresses(txhash_list): return BACKEND().extract_addresses(txhash_list) def ensure_script_pub_key_for_inputs(coins): txhash_set = set() if 'scriptPubKey' not in coin: txhash_set.add(coin['txid']) if len(txhash_set) > 0: txs = BACKEND().getrawtransaction_batch(list(txhash_set), verbose=True, skip_missing=False) for coin in coins: if 'scriptPubKey' not in coin: # get the scriptPubKey txid = coin['txid'] for vout in txs[txid]['vout']: if vout['n'] == coin['vout']: coin['scriptPubKey'] = vout['scM def fee_per_kb(conf_target, mode, nblocks=None): :param conf_target: :return: fee_per_kb in satoshis, or None when unable to determine return BACKEND().fee_per_kb(conf_target, mode, nblocks=nblocks) def deserialize(tx_hex): return bitcoinlib.core.CTransaction.deserialize(binascii.unhexlify(tx_hex)) return bitcoinlib.core.CTransaction.serialize(ctx) def is_valid(address): script.valiM except script.AddressError: return False def get_txhash_list(block): return [bitcoinlib.core.b2lx(ctx.GetHash()) for ctx in block.vtx] def get_tx_list(block): raw_transactions = {} tx_hash_list = [] for ctx in block.vtx: if util.enabled('correct_segwit_txids'): hsh = ctx.GetTxid() hsh = ctx.GetHash() tx_hash = bitcoinlib.core.b2lx(hsh) raw = ctx.serialize() tx_hash_list.append(tx_hM raw_transactions[tx_hash] = bitcoinlib.core.b2x(raw) return (tx_hash_list, raw_transactions) def sort_unspent_txouts(unspent, unconfirmed=False): # Filter out all dust amounts to avoid bloating the resultant transaction unspent = list(filter(lambda x: x['value'] > config.DEFAULT_MULTISIG_DUST_SIZE, unspent)) # Sort by amount, using the largest UTXOs available if config.REGTEST: # REGTEST has a lot of coinbase inputs that can't be spent due to maturity # this doesn'M t usually happens on mainnet or testnet because most fednodes aren't mining unspent = sorted(unspent, key=lambda x: (x['confirmations'], x['value']), reverse=True) unspent = sorted(unspent, key=lambda x: x['value'], reverse=True) def get_btc_supply(normalize=False): """returns the total supply of {} (based on what Bitcoin Core says the current block height is)""".format(config.BTC) block_count = getblockcount() blocks_remaining = block_count while blocks_remaining > 0: if blocks_remaining >= 210000: blocks_remaining -= 210000 total_supply += 210000 * reward reward /= 2 total_supply += (blocks_remaining * reward) blocks_remaining = 0 return total_supply if normalize else int(total_supply * config.UNIT) class MempoolError(Exception): def get_unspent_txouts(source, unconfirmed=False, unspent_tx_hash=None): """returns a list of uM nspent outputs for a specific address @return: A list of dicts, with each entry in the dict having the following keys: unspent = BACKEND().get_unspent_txouts(source) # filter by unspent_tx_hash if unspent_tx_hash is not None: unspent = list(filter(lambda x: x['txId'] == unspent_tx_hash, unspent)) # filter unconfirmed if not unconfirmed: unspent = [utxo for utxo in unspent if utxo['confirmations'] > 0] for utxo in unspent: utxo['amount'] = M float(utxo['value'] / config.UNIT) utxo['txid'] = utxo['txId'] del utxo['txId'] # do not add scriptPubKey def search_raw_transactions(address, unconfirmed=True): return BACKEND().search_raw_transactions(address, unconfirmed) class UnknownPubKeyError(Exception): def pubkeyhash_to_pubkey(pubkeyhash, provided_pubkeys=None): # Search provided pubkeys. if provided_pubkeys: if type(provided_pubkeys) != list: provided_pubkeys = [providM for pubkey in provided_pubkeys: if pubkeyhash == script.pubkey_to_pubkeyhash(util.unhexlify(pubkey)): return pubkey elif pubkeyhash == script.pubkey_to_p2whash(util.unhexlify(pubkey)): return pubkey # Search blockchain. raw_transactions = search_raw_transactions(pubkeyhash, unconfirmed=True) for tx_id in raw_transactions: tx = raw_transactions[tx_id] for vin in tx['vin']: if 'txinwitness' in vin: if len(vin['txinwitness']) >= 2: # catch unhexlify errs for when txinwitness[1] isn't a witness program (eg; for P2W) try: pubkey = vin['txinwitness'][1] if pubkeyhash == script.pubkey_to_p2whash(util.unhexlify(pubkey)): return pubkey except binascii.Error: pass elif 'coinbase' not in vin: scriptsig = vin['scriptSig']M asm = scriptsig['asm'].split(' ') if len(asm) >= 2: # catch unhexlify errs for when asm[1] isn't a pubkey (eg; for P2SH) try: pubkey = asm[1] if pubkeyhash == script.pubkey_to_pubkeyhash(util.unhexlify(pubkey)): return pubkey except binascii.Error: pass raise UnknownPubKeyError('Public key was neither provided nor publisM hed in blockchain.') def multisig_pubkeyhashes_to_pubkeys(address, provided_pubkeys=None): signatures_required, pubkeyhashes, signatures_possible = script.extract_array(address) pubkeys = [pubkeyhash_to_pubkey(pubkeyhash, provided_pubkeys) for pubkeyhash in pubkeyhashes] return script.construct_array(signatures_required, pubkeys, signatures_possible) def init_mempool_cache(): """prime the mempool cache, so that functioning is faster... global MEMPOOL_CACHE_INITIALIZED g('Initializing mempool cache...') start = time.time() mempool_txhash_list = getrawmempool() #with this function, don't try to load in more than BACKEND_RAW_TRANSACTIONS_CACHE_SIZE entries num_tx = min(len(mempool_txhash_list), config.BACKEND_RAW_TRANSACTIONS_CACHE_SIZE) mempool_tx = BACKEND().getrawtransaction_batch(mempool_txhash_list[:num_tx], skip_missing=True, verbose=True) vin_txhash_list = [] max_remaining_num_tx = config.BACKEND_RAW_TRANSACTIONS_CACHE_SIZE - num_tx for txid in mempool_tx: tx = mempool_tx[txid] if not(tx is None): vin_txhash_list += [vin['txid'] for vin in tx['vin']] BACKEND().getrawtransaction_batch(vin_txhash_list[:max_remaining_num_tx], skip_missing=True, verbose=True) MEMPOOL_CACHE_INITIALIZED = True logger.info('Mempool cache initialized: {:.2f}s for {:,} transactions'.format(time.time() - start, num_tx + min(max_remaining_num_tx, len(vin_txhash_list)))) expandtab shiftwidth=4 softtabstop=4 #### bc_data_stream.py # Workalike python implementation of Bitcoin's CDataStream class. from .exceptions import SerializationError class BCDataStream(object): def __init__(self): self.input = None self.read_cursor = 0 self.input = None self.read_cursor = 0 def write(self, bytes): # Initialize with string of bytes if self.input is None: self.input = bytes self.input += bM def map_file(self, file, start): # Initialize with bytes from file self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) self.read_cursor = start def seek_file(self, position): self.read_cursor = position def close_file(self): self.input.close() def read_string(self): # Strings are encoded depending on length: # 0 to 252 : 1-byte-length followed by bytes (if any) # 253 to 65,535 : byte'253' 2-byte-length followed by bytes # 65,536 to 4,294,967,295 : bytM e '254' 4-byte-length followed by bytes # ... and the Bitcoin client is coded to understand: # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string # ... but I don't think it actually handles any strings that big. if self.input is None: raise SerializationError("call write(bytes) before trying to deserialize") length = self.read_compact_size() except IndexError: raise SerializationError("attempt to read past end of buffer") f.read_bytes(length) def write_string(self, string): # Length-encoded as with read-string self.write_compact_size(len(string)) self.write(string) def read_bytes(self, length): result = self.input[self.read_cursor:self.read_cursor+length] self.read_cursor += length except IndexError: raise SerializationError("attempt to read past end of buffer") def read_boolean(self): return self.read_bytes(1)[0] != chr(0) def read_int16(self):M return self._read_num('<h') def read_uint16(self): return self._read_num('<H') def read_int32(self): return self._read_num('<i') def read_uint32(self): return self._read_num('<I') def read_int64(self): return self._read_num('<q') def read_uint64(self): return self._read_num('<Q') def write_boolean(self, val): return self.write(chr(1) if val else chr(0)) def write_int16(self, val): return self._write_num('<h', val) def write_uint16(self, val): return self._write_num('<H', val) def write_int32(selM f, val): return self._write_num('<i', val) def write_uint32(self, val): return self._write_num('<I', val) def write_int64(self, val): return self._write_num('<q', val) def write_uint64(self, val): return self._write_num('<Q', val) def read_compact_size(self): size = self.input[self.read_cursor] self.read_cursor += 1 size = self._read_num('<H') elif size == 254: size = self._read_num('<I') elif size == 255: size = self._read_num('<Q') def write_compact_size(self, size): raise SerializationError("attempt to write size < 0") elif size < 253: self.write(chr(size)) elif size < 2**16: self.write('\xfd') self._write_num('<H', size) elif size < 2**32: self.write('\xfe') self._write_num('<I', size) elif size < 2**64: self.write('\xff') self._write_num('<Q', size) def _read_num(self, format): (i,) = struct.unpack_from(format, self.input, self.read_cursor) ad_cursor += struct.calcsize(format) def _write_num(self, format, num): s = struct.pack(format, num) def read_var_int(self): cur_byte = self.read_bytes(1)[0] n = (n << 7) | (cur_byte & 0x7F) if cur_byte & 0x80: return n #### blocks_parser.py import os, json, time, logging, binascii logger = logging.getLogger(__name__) from .bc_data_stream import BCDataStrM from .utils import b2h, double_hash, ib2h, inverse_hash def open_leveldb(db_dir): import plyvel raise Exception("Please install the plyvel package via pip3.") return plyvel.DB(db_dir, create_if_missing=False) except plyvel._plyvel.IOError as e: logger.info(str(e)) raise Exception("Ensure that bitcoind is stopped.") class BlockchainParser(): def __init__(self, blocks_dir, leveldb_dir): self.blocks_dir = blocks_dir self.leveldb_dir = leveldb_dir self.file_num = -1 self.current_file_size = 0 self.current_block_file = None self.data_stream = None self.ldb = open_leveldb(self.leveldb_dir) def read_tx_in(self, vds): tx_in['txid'] = ib2h(vds.read_bytes(32)) tx_in['vout'] = vds.read_uint32() script_sig_size = vds.read_compact_size() tx_in['scriptSig'] = b2h(vds.read_bytes(script_sig_size)) tx_in['sequence'] = vds.read_uint32() if tx_in['txid'] == '0000000000000000000000000000000000000000000000000000000000000000': tx_in = { 'coinbase': tx_in['scriptSig'], 'sequence': tx_in['sequence'] return tx_in def read_tx_out(self, vds): tx_out['value'] = vds.read_int64() / 100000000 script = vds.read_bytes(vds.read_compact_size()) tx_out['scriptPubKey'] = { 'hex': b2h(script) return tx_out d_transaction(self, vds): transaction = {} start_pos = vds.read_cursor transaction['version'] = vds.read_int32() transaction['vin'] = [] for i in range(vds.read_compact_size()): transaction['vin'].append(self.read_tx_in(vds)) transaction['vout'] = [] for i in range(vds.read_compact_size()): transaction['vout'].append(self.read_tx_out(vds)) transaction['lock_time'] = vds.read_uint32() data = vds.input[start_pos:vds.reaM transaction['tx_hash'] = ib2h(double_hash(data)) transaction['__data__'] = b2h(data) return transaction def read_block_header(self, vds): block_header = {} block_header['magic_bytes'] = vds.read_int32() #if block_header['magic_bytes'] != 118034699: # raise Exception('Not a block') block_header['block_size'] = vds.read_int32() header_start = vds.read_cursor block_header['version'] = vds.read_int32() block_headerM ['hash_prev'] = ib2h(vds.read_bytes(32)) block_header['hash_merkle_root'] = ib2h(vds.read_bytes(32)) block_header['block_time'] = vds.read_uint32() block_header['bits'] = vds.read_uint32() block_header['nonce'] = vds.read_uint32() header_end = vds.read_cursor header = vds.input[header_start:header_end] block_header['block_hash'] = ib2h(double_hash(header)) block_header['__header__'] = b2h(header) return block_header def read_block(self, vdM block = self.read_block_header(vds) block['transaction_count'] = vds.read_compact_size() block['transactions'] = [] for i in range(block['transaction_count']): block['transactions'].append(self.read_transaction(vds)) return block def prepare_data_stream(self, file_num, pos_in_file): if self.data_stream is None or file_num != self.file_num: self.file_num = file_num if self.current_block_file: self.current_bloM data_file_path = os.path.join(self.blocks_dir, 'blk%05d.dat' % (self.file_num,)) self.current_block_file = open(data_file_path, "rb") self.data_stream = BCDataStream() self.data_stream.map_file(self.current_block_file, pos_in_file) self.data_stream.seek_file(pos_in_file) def read_raw_block(self, block_hash): block_hash = binascii.unhexlify(inverse_hash(block_hash)) block_data = self.ldb.get(bytes('b', 'utfM ds = BCDataStream() ds.write(block_data) version = ds.read_var_int() height = ds.read_var_int() status = ds.read_var_int() tx_count = ds.read_var_int() file_num = ds.read_var_int() block_pos_in_file = ds.read_var_int() - 8 block_undo_pos_in_file = ds.read_var_int() block_header = ds.read_bytes(80) self.prepare_data_stream(file_num, block_pos_in_file) block = self.read_block(self.data_stream) block['block_index'] = height return block def read_raw_transaction(self, tx_hash): tx_hash = binascii.unhexlify(inverse_hash(tx_hash)) tx_data = self.ldb.get(bytes('t', 'utf-8') + tx_hash) ds = BCDataStream() ds.write(tx_data) file_num = ds.read_var_int() block_pos_in_file = ds.read_var_int() tx_pos_in_block = ds.read_var_int() tx_pos_in_file = block_pos_in_file + 80 + tx_pos_in_block self.prepare_data_stream(file_nuM transaction = self.read_transaction(self.data_stream) return transaction def close(self): if self.current_block_file: self.current_block_file.close() self.ldb.close() class ChainstateParser(): def __init__(self, leveldb_dir): self.ldb = open_leveldb(leveldb_dir) def get_last_block_hash(self): block_hash = self.ldb.get(bytes('B', 'utf-8')) block_hash = ib2h(block_hash) return block_hash self.ldb.close() class SolvingError(Exception): pass class SerializationError(Exception): """ Thrown when there's a problem deserializing or serializing """ import binascii, os, random, json, hashlib bytes_from_int = chr if bytes == str else lambda x: bytes([x]) return binascii.hexlify(b).decode('utf-8') def random_hex(length): return binascii.b2a_hex(os.urandom(length)) def double_hash(b): return hashlib.sha256(hashliM b.sha256(b).digest()).digest() def inverse_hash(hashstring): hashstring = hashstring[::-1] return ''.join([hashstring[i:i+2][::-1] for i in range(0, len(hashstring), 2)]) return inverse_hash(b2h(b)) class JsonDecimalEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, decimal.Decimal): return str(o) return super(DecimalEncoder, self).default(o) #### p2sh_encoding.py This module contains p2sh data encodingM import traceback # not needed if not printing exceptions on p2sh decoding logger = logging.getLogger(__name__) import bitcoin as bitcoinlib from bitcoin.core.script import CScript from counterpartylib.lib import config from counterpartylib.lib import script from counterpartylib.lib import exceptions def maximum_data_chunk_size(pubkeylength): if pubkeylength >= 0: return bitcoinlib.core.script.MAX_SCRIPT_ELEMENT_SIZE - len(confiM g.PREFIX) - pubkeylength - 12 #Two bytes are for unique offset. This will work for a little more than 1000 outputs return bitcoinlib.core.script.MAX_SCRIPT_ELEMENT_SIZE - len(config.PREFIX) - 44 # Redeemscript size for p2pkh addresses, multisig won't work here def calculate_outputs(destination_outputs, data_array, fee_per_kb, exact_fee=None): datatx_size = 10 # 10 base datatx_size += 181 # 181 for source input datatx_size += (25 + 9) * len(destination_outputs) # destination outputsM datatx_size += 13 # opreturn that signals P2SH encoding datatx_size += len(data_array) * (9 + 181) # size of p2sh inputs, excl data datatx_size += sum([len(data_chunk) for data_chunk in data_array]) # data in scriptSig datatx_necessary_fee = int(datatx_size / 1000 * fee_per_kb) pretx_output_size = 10 # 10 base pretx_output_size += len(data_array) * 29 # size of P2SH output size_for_fee = pretx_output_size # split the tx fee evenly between all datatx outputs = math.ceil(datatx_necessary_fee / len(data_array)) data_value = config.DEFAULT_REGULAR_DUST_SIZE # adjust the data output with the new value and recalculate data_btc_out data_btc_out = data_value * len(data_array) remain_fee = exact_fee - data_value * len(data_array) if remain_fee > 0: #if the dust isn't enough to reach the exact_fee, data value will be an array with only the last fee bumped data_value = [data_value for i in range(len(data_arrM data_value[len(data_array)-1] = data_value[len(data_array)-1] + remain_fee data_btc_out = exact_fee data_output = (data_array, data_value) logger.getChild('p2shdebug').debug('datatx size: %d fee: %d' % (datatx_size, datatx_necessary_fee)) logger.getChild('p2shdebug').debug('pretx output size: %d' % (pretx_output_size, )) logger.getChild('p2shdebug').debug('size_for_fee: %d' % (size_for_fee, )) return size_for_fee, datatx_necessary_fee, data_value, dM def decode_p2sh_input(asm, p2sh_is_segwit=False): ''' Looks at the scriptSig for the input of the p2sh-encoded data transaction [signature] [data] [OP_HASH160 ... OP_EQUAL] pubkey, source, redeem_script_is_valid, found_data = decode_data_redeem_script(asm[-1], p2sh_is_segwit) if redeem_script_is_valid: # this is a signed transaction, so we got {sig[,sig]} {datachunk} {redeemScript} datachunk = found_data redeemScript = asm[-1] #asm[-2:] #print('ASM:', len(asm)) pubkey, source, redeem_script_is_valid, found_data = decode_data_redeem_script(asm[-1], p2sh_is_segwit) if not redeem_script_is_valid or len(asm) != 3: return None, None, None # this is an unsigned transaction (last is outputScript), so we got [datachunk] [redeemScript] [temporaryOutputScript] datachunk, redeemScript, _substituteScript = asm data = datachunk if data[:len(config.PREFIX)] == config.PREFIX: data = data[len(confiM if data == b'': return source, None, None raise exceptions.DecodeError('unrecognised P2SH output') return source, None, data def decode_data_push(arr, pos): opcode = bitcoinlib.core.script.CScriptOp(arr[pos]) if opcode > 0 and opcode < bitcoinlib.core.script.OP_PUSHDATA1: pushlen = arr[pos] elif opcode == bitcoinlib.core.script.OP_PUSHDATA1: pushlen = arr[pos + 1] elif opcode == bitcoinlib.core.script.OP_PUSHDATA2: (pushlen, ) = struct.unpack('<H', arr[pos + 1:pos + 3]) elif opcode == bitcoinlib.core.script.OP_PUSHDATA4: (pushlen, ) = struct.unpack('<L', arr[pos + 1:pos + 5]) return pos + pushlen, arr[pos:pos + pushlen] def decode_data_redeem_script(redeemScript, p2sh_is_segwit=False): script_len = len(redeemScript) found_data = b'' if script_len == 41 and \ redeemScript[0] == bitcoinlib.core.scM redeemScript[35] == bitcoinlib.core.script.OP_CHECKSIGVERIFY and \ redeemScript[37] == bitcoinlib.core.script.OP_DROP and \ redeemScript[38] == bitcoinlib.core.script.OP_DEPTH and \ redeemScript[39] == bitcoinlib.core.script.OP_0 and \ redeemScript[40] == bitcoinlib.core.script.OP_EQUAL: # - OP_DROP [push] [33-byte pubkey] OP_CHECKSIGVERIFY [n] OP_DROP OP_DEPTH 0 OP_EQUAL pubkey = redeemScript[2:35] if p2sh_is_segwit: source = script.pubkey_to_p2whash(pubkey) source = script.pubkey_to_pubkeyhash(pubkey) redeem_script_is_valid = True elif script_len > 41 and \ redeemScript[0] == bitcoinlib.core.script.OP_DROP and \ redeemScript[script_len-4] == bitcoinlib.core.script.OP_DROP and \ redeemScript[script_len-3] == bitcoinlib.core.script.OP_DEPTH and \ redeemScript[script_len-2] == bitcoinlib.core.script.OP_0 and \ redeemScript[scripM t_len-1] == bitcoinlib.core.script.OP_EQUAL: # - OP_DROP {arbitrary multisig script} [n] OP_DROP OP_DEPTH 0 OP_EQUAL pubkey = None source = None redeem_script_is_valid = True pubkey = None source = None redeem_script_is_valid = False opcode = bitcoinlib.core.script.CScriptOp(redeemScript[0]) if opcode > bitcoinlib.core.script.OP_0 and opcode < bitcoinlib.core.script.OP_PUSHDATA1 or \ opcode in (bitcoinlib.core.script.OP_PUSHDATA1, bitcoinlib.core.script.OP_PUSHDATA2, bitcoinlib.core.script.OP_PUSHDATA4): pos = 0 pos, found_data = decode_data_push(redeemScript, 0) if redeemScript[pos] == bitcoinlib.core.script.OP_DROP: pos += 1 valid_sig = False opcode = redeemScript[pos] if type(opcode) != type(''): if opcode >= bitcoinlib.core.script.OP_2 M and opcode <= bitcoinlib.core.script.OP_15: # it's multisig req_sigs = opcode - bitcoinlib.core.script.OP_1 + 1 pos += 1 pubkey = None num_sigs = 0 found_sigs = False while not found_sigs: pos, npubkey = decode_data_push(redeemScript, pos) num_sigs += 1 if redeemScript[pos] - bitcoinlib.core.script.OP_1 + 1 == num_sigs: found_sigs = True pos += 1 valid_sig = redeemScript[pos] == bitcoinlib.core.script.OP_CHECKMULTISIGVERIFY else: # it's p2pkh pos, pubkey = decode_data_push(redeemScript, pos) if p2sh_is_segwit: M source = script.pubkey_to_p2whash(pubkey) else: source = script.pubkey_to_pubkeyhash(pubkey) valid_sig = redeemScript[pos] == bitcoinlib.core.script.OP_CHECKSIGVERIFY pos += 1 if valid_sig: uniqueOffsetLength = 0 for i in range(pos+1, len(redeemScript)): if redeemScript[i] == bitcoinlib.corM uniqueOffsetLength = i-pos-1 break redeem_script_is_valid = redeemScript[pos + 1 + uniqueOffsetLength] == bitcoinlib.core.script.OP_DROP and \ redeemScript[pos + 2 + uniqueOffsetLength] == bitcoinlib.core.script.OP_DEPTH and \ redeemScript[pos + 3 + uniqueOffsetLength] == 0 and \ redeemScript[pos + 4 + uM niqueOffsetLength] == bitcoinlib.core.script.OP_EQUAL except Exception as e: pass #traceback.print_exc() return pubkey, source, redeem_script_is_valid, found_data def make_p2sh_encoding_redeemscript(datachunk, n, pubKey=None, multisig_pubkeys=None, multisig_pubkeys_required=None): _logger = logger.getChild('p2sh_encoding') assert len(datachunk) <= bitcoinlib.core.script.MAX_SCRIPT_ELEMENT_SIZE dataDropScript = [datachunk, bitcoinlib.core.script.OP_DROP] # just drop the data chM cleanupScript = [n, bitcoinlib.core.script.OP_DROP, bitcoinlib.core.script.OP_DEPTH, 0, bitcoinlib.core.script.OP_EQUAL] # unique offset + prevent scriptSig malleability if pubKey is not None: # a p2pkh script looks like this: {pubkey} OP_CHECKSIGVERIFY verifyOwnerScript = [pubKey, bitcoinlib.core.script.OP_CHECKSIGVERIFY] elif multisig_pubkeys_required is not None and multisig_pubkeys: # a 2-of-3 multisig looks like this: # 2 {pubkey1} {pubkey2} {pubkey3} 3 OP_CHEM multisig_pubkeys_required = int(multisig_pubkeys_required) if multisig_pubkeys_required < 2 or multisig_pubkeys_required > 15: raise exceptions.TransactionError('invalid multisig pubkeys value') verifyOwnerScript = [multisig_pubkeys_required] for multisig_pubkey in multisig_pubkeys: verifyOwnerScript.append(multisig_pubkey) verifyOwnerScript = verifyOwnerScript + [len(multisig_pubkeys), bitcoinlib.core.script.OP_CHECKMULTISIGVERIFY] raise exceptions.TransactionError('Either pubKey or multisig pubKeys must be provided') #redeemScript = CScript(datachunk) + CScript(dataDropScript + verifyOwnerScript + cleanupScript) redeemScript = CScript(dataDropScript + verifyOwnerScript + cleanupScript) _logger.debug('datachunk %s' % (binascii.hexlify(datachunk))) _logger.debug('dataDropScript %s (%s)' % (repr(CScript(dataDropScript)), binascii.hexlify(CScript(dataDropScript)))) _logger.debug('verifyOwnerScript %s (%s)' %M (repr(CScript(verifyOwnerScript)), binascii.hexlify(CScript(verifyOwnerScript)))) _logger.debug('entire redeemScript %s (%s)' % (repr(redeemScript), binascii.hexlify(redeemScript))) #scriptSig = CScript([]) + redeemScript # PUSH(datachunk) + redeemScript scriptSig = CScript([redeemScript]) outputScript = redeemScript.to_p2sh_scriptPubKey() _logger.debug('scriptSig %s (%s)' % (repr(scriptSig), binascii.hexlify(scriptSig))) _logger.debug('outputScript %s (%s)' % (repr(outputScript), binascM ii.hexlify(outputScript))) # outputScript looks like OP_HASH160 {{ hash160([redeemScript]) }} OP_EQUALVERIFY # redeemScript looks like OP_DROP {{ pubkey }} OP_CHECKSIGVERIFY {{ n }} OP_DROP OP_DEPTH 0 OP_EQUAL # scriptSig is {{ datachunk }} OP_DROP {{ pubkey }} OP_CHECKSIGVERIFY {{ n }} OP_DROP OP_DEPTH 0 OP_EQUAL return scriptSig, redeemScript, outputScript def make_standard_p2sh_multisig_script(multisig_pubkeys, multisig_pubkeys_required): # a 2-of-3 multisig looks like this: ey1} {pubkey2} {pubkey3} 3 OP_CHECKMULTISIG multisig_pubkeys_required = int(multisig_pubkeys_required) multisig_script = [multisig_pubkeys_required] for multisig_pubkey in multisig_pubkeys: multisig_script.append(multisig_pubkey) multisig_script = multisig_script + [len(multisig_pubkeys), bitcoinlib.core.script.OP_CHECKMULTISIG] return multisig_script Construct and serialize the Bitcoin transactions that are Counterparty transactions. logger = logging.getLogger(__name__) import bitcoin as bitcoinlib from bitcoin.core import Hash160 from bitcoin.core.script import CScript from bitcoin.wallet import P2PKHBitcoinAddress, P2SHBitcoinAddress from counterpartylib.lib import config from counterpartylib.lib import exceptions from counterpartylib.lib import util ounterpartylib.lib import script from counterpartylib.lib import backend from counterpartylib.lib import arc4 from counterpartylib.lib.transaction_helper import p2sh_encoding from bitcoin.bech32 import CBech32Data OP_PUSHDATA1 = b'\x4c' OP_HASH160 = b'\xa9' OP_EQUALVERIFY = b'\x88' OP_CHECKSIG = b'\xac' OP_CHECKMULTISIG = b'\xae' UTXO_LOCKS_PER_ADDREM SS_MAXSIZE = 5000 # set higher than the max number of UTXOs we should expect to # manage in an aging cache for any one source address, at any one period return (i).to_bytes(1, byteorder='little') elif i <= 0xffff: return b'\xfd' + (i).to_bytes(2, byteorder='little') elif i <= 0xffffffff: return b'\xfe' + (i).to_bytes(4, byteorder='little') return b'\xff' + (i).to_bytes(8, byteorder='little') return (i).to_bytes(1, byteorder='little') # Push i bytes. return b'\x4c' + (i).to_bytes(1, byteorder='little') # OP_PUSHDATA1 elif i <= 0xffff: return b'\x4d' + (i).to_bytes(2, byteorder='little') # OP_PUSHDATA2 return b'\x4e' + (i).to_bytes(4, byteorder='little') # OP_PUSHDATA4 def get_script(address): if script.is_multisig(address): return get_multisig_script(address) script.is_bech32(address): return get_p2w_script(address) return get_monosig_script(address) except script.VersionByteError as e: return get_p2sh_script(address) def get_multisig_script(address): signatures_required, pubkeys, signatures_possible = script.extract_array(address) # Required signatures. if signatures_required == 1: op_required = OP_1 elif signatures_required == 2: elif signatures_required == 3: op_required = OP_3 raise script.InputError('Required signatures must be 1, 2 or 3.') # Required signatures. # Note 1-of-1 addresses are not supported (they don't go through extract_array anyway). if signatures_possible == 2: op_total = OP_2 elif signatures_possible == 3: op_total = OP_3 raise script.InputError('Total possible signatures must be 2 or 3.') # Construct script. = op_required # Required signatures for public_key in pubkeys: public_key = binascii.unhexlify(public_key) tx_script += op_push(len(public_key)) # Push bytes of public key tx_script += public_key # Data chunk (fake) public key tx_script += op_total # Total signatures tx_script += OP_CHECKMULTISIG # OP_CHECKMULTISIG return (tx_script, None) et_monosig_script(address): # Construct script. pubkeyhash = script.base58_check_decode(address, config.ADDRESSVERSION) tx_script = OP_DUP # OP_DUP tx_script += OP_HASH160 # OP_HASH160 tx_script += op_push(20) # Push 0x14 bytes tx_script += pubkeyhash # pubKeyHash tx_script += OP_EQUALVERIFY # OP_EQUALVERIFY tx_script += OP_CHECKSM IG # OP_CHECKSIG return (tx_script, None) def get_p2sh_script(address): # Construct script. scripthash = script.base58_check_decode(address, config.P2SH_ADDRESSVERSION) tx_script = OP_HASH160 tx_script += op_push(len(scripthash)) tx_script += scripthash tx_script += OP_EQUAL return (tx_script, None) def get_p2w_script(address): # Construct script. scripthash = bytes(CBech32Data(address)) if len(scripthash) == 20: # P2WPKH encM tx_script = OP_0 tx_script += b'\x14' tx_script += scripthash witness_script = OP_HASH160 witness_script += op_push(len(scripthash)) witness_script += scripthash witness_script += OP_EQUAL return (tx_script, witness_script) elif len(scripthash) == 32: # P2WSH encoding raise Exception('P2WSH encoding not yet supported') def make_fully_valid(pubkey_start): """Take a too short data pubkey and make it look like a real pubM Take an obfuscated chunk of data that is two bytes too short to be a pubkey and add a sign byte to its beginning and a nonce byte to its end. Choose these bytes so that the resulting sequence of bytes is a fully valid pubkey (i.e. on the ECDSA curve). Find the correct bytes by guessing randomly until the check passes. (In parsing, these two bytes are ignored.) assert type(pubkey_start) == bytes assert len(pubkey_start) == 31 # One sign byte and one nonce byte required (fM random_bytes = hashlib.sha256(pubkey_start).digest() # Deterministically generated, for unit tests. sign = (random_bytes[0] & 0b1) + 2 # 0x02 or 0x03 nonce = initial_nonce = random_bytes[1] while not script.is_fully_valid(pubkey): # Increment nonce. assert nonce != initial_nonce # Construct a possibly fully valid public key. pubkey = bytes([sign]) + pubkey_start + bytes([nonce % 256]) rt len(pubkey) == 33 def serialise(encoding, inputs, destination_outputs, data_output=None, change_output=None, dust_return_pubkey=None): s = (1).to_bytes(4, byteorder='little') # Version use_segwit = False for i in range(len(inputs)): txin = inputs[i] spk = txin['scriptPubKey'] if spk[0:2] == '00': # Witness version 0 datalen = binascii.unhexlify(spk[2:4])[0] if datalen == 20 or datalen == 32: # 20 is fM or P2WPKH and 32 is for P2WSH if not(use_segwit): s = (2).to_bytes(4, byteorder='little') # Rewrite version use_segwit = True txin['is_segwit'] = True s += b'\x00' # marker s += b'\x01' # flag # Number of inputs. s += var_int(int(len(inputs))) witness_txins = [] witness_data = {} # List of Inputs. for i in range(len(inputs)): txin = inputs[i] s += binascii.unhexlifM y(bytes(txin['txid'], 'utf-8'))[::-1] # TxOutHash s += txin['vout'].to_bytes(4, byteorder='little') # TxOutIndex tx_script = binascii.unhexlify(bytes(txin['scriptPubKey'], 'utf-8')) s += var_int(int(len(tx_script))) # Script length s += tx_script # Script s += b'\xff' * 4 # Sequence # Number of outputs. n += len(destination_outputs) if data_output:M data_array, value = data_output for data_chunk in data_array: n += 1 data_array = [] if change_output: n += 1 # Destination output. for destination, value in destination_outputs: s += value.to_bytes(8, byteorder='little') # Value tx_script, witness_script = get_script(destination) #if use_segwit and destination in witness_data: # Not deleteing, We will need this for P2WSH # witness_data[destination].appeM # tx_script = witness_script #if witness_script: # tx_script = witness_script s += var_int(int(len(tx_script))) # Script length s += tx_script for data_chunk in data_array: data_array, value = data_output s += value.to_bytes(8, byteorder='little') # Value data_chunk = config.PREFIX + data_chunk # Initialise encryption key (once per output). assert isinstancM e(inputs[0]['txid'], str) key = arc4.init_arc4(binascii.unhexlify(inputs[0]['txid'])) # Arbitrary, easy if encoding == 'multisig': assert dust_return_pubkey # Get data (fake) public key. pad_length = (33 * 2) - 1 - 2 - 2 - len(data_chunk) assert pad_length >= 0 data_chunk = bytes([len(data_chunk)]) + data_chunk + (pad_length * b'\x00') data_chunk = key.encrypt(data_chunk) data_pubkey_1 = maM ke_fully_valid(data_chunk[:31]) data_pubkey_2 = make_fully_valid(data_chunk[31:]) # Construct script. tx_script = OP_1 # OP_1 tx_script += op_push(33) # Push bytes of data chunk (fake) public key (1/2) tx_script += data_pubkey_1 # (Fake) public key (1/2) tx_script += op_push(33) # Push bytes of data chunk (fake) pubM tx_script += data_pubkey_2 # (Fake) public key (2/2) tx_script += op_push(len(dust_return_pubkey)) # Push bytes of source public key tx_script += dust_return_pubkey # Source public key tx_script += OP_3 # OP_3 tx_script += OP_CHECKMULTISIG # OP_CHECKMULTISIG elif encoding == 'opreturn': data_chunk = key.M tx_script = OP_RETURN # OP_RETURN tx_script += op_push(len(data_chunk)) # Push bytes of data chunk (NOTE: OP_SMALLDATA?) tx_script += data_chunk # Data elif encoding == 'pubkeyhash': pad_length = 20 - 1 - len(data_chunk) assert pad_length >= 0 data_chunk = bytes([len(data_chunk)]) + data_chunk + (pad_length * b'\x00') data_chM unk = key.encrypt(data_chunk) # Construct script. tx_script = OP_DUP # OP_DUP tx_script += OP_HASH160 # OP_HASH160 tx_script += op_push(20) # Push 0x14 bytes tx_script += data_chunk # (Fake) pubKeyHash tx_script += OP_EQUALVERIFY # OP_EQUALVERIFY tx_script += OP_CHECKSIG M # OP_CHECKSIG raise exceptions.TransactionError('Unknown encoding s += var_int(int(len(tx_script))) # Script length s += tx_script # Change output. if change_output: change_address, change_value = change_output s += change_value.to_bytes(8, byteorder='little') # Value tx_script, witness_script = get_script(change_address) #print("Change address!", change_address, "\n", witnessM _data, "\n", tx_script, "\n", witness_script) #if witness_script: #use_segwit and change_address in witness_data: # if not(change_address in witness_data): # witness_data[change_address] = [] # witness_data[change_address].append(witness_script) # tx_script = witness_script # use_segwit = True s += var_int(int(len(tx_script))) # Script length s += tx_script for i in range(len(inputs))M txin = inputs[i] if txin['is_segwit']: s += b'\x02' s += b'\x00\x00' s += b'\x00' s += (0).to_bytes(4, byteorder='little') # LockTime def serialise_p2sh_pretx(inputs, source, source_value, data_output, change_output=None, pubkey=None, multisig_pubkeys=None, multisig_pubkeys_required=None): assert data_output # we don't do this unless there's data data_array, data_value = data_outM s = (1).to_bytes(4, byteorder='little') # Version # Number of inputs. s += var_int(int(len(inputs))) # List of Inputs. for i in range(len(inputs)): txin = inputs[i] s += binascii.unhexlify(bytes(txin['txid'], 'utf-8'))[::-1] # TxOutHash s += txin['vout'].to_bytes(4, byteorder='little') # TxOutIndex tx_script = binascii.unhexlify(bytes(txin['scriptPubKey'], 'utf-8')) s += var_int(int(len(tx_script))) # Script length ript # Script s += b'\xff' * 4 # Sequence # Number of outputs. n = len(data_array) if change_output: # encode number of outputs # P2SH for data encodeded inputs for n, data_chunk in enumerate(data_array): data_chunk = config.PREFIX + data_chunk # prefix the data_chunk # get the scripts scriptSig, redeemScript, outputScript = p2sh_encoding.make_p2sh_encoding_redeemscript(data_chunk, nM , pubkey, multisig_pubkeys, multisig_pubkeys_required) #if data_value is an array, then every output fee is specified in it if type(data_value) == list: s += data_value[n].to_bytes(8, byteorder='little') # Value s += data_value.to_bytes(8, byteorder='little') # Value s += var_int(int(len(outputScript))) # Script length s += outputScript # Script # Change output. if change_output: ge_address, change_value = change_output tx_script, witness_script = get_script(change_address) s += change_value.to_bytes(8, byteorder='little') # Value s += var_int(int(len(tx_script))) # Script length s += tx_script # Script s += (0).to_bytes(4, byteorder='little') # LockTime def serialise_p2sh_datatx(txid, source, source_input, destination_outputs, data_output, pubkey=None, multisig_pubkeys=None, multisiM g_pubkeys_required=None): assert data_output # we don't do this unless there's data txhash = bitcoinlib.core.lx(bitcoinlib.core.b2x(txid)) # reverse txId data_array, value = data_output s = (1).to_bytes(4, byteorder='little') # number of inputs is the length of data_array (+1 if a source_input exists) number_of_inputs = len(data_array) if source_input is not None: number_of_inputs += 1 s += var_int(number_of_inputs) # Handle a source input here for a PM if source_input is not None: s += binascii.unhexlify(bytes(source_input['txid'], 'utf-8'))[::-1] # TxOutHash s += source_input['vout'].to_bytes(4, byteorder='little') # TxOutIndex # since pubkey is not returned from indexd, add it from bitcoind source_inputs = backend.ensure_script_pub_key_for_inputs([source_input]) source_input = source_inputs[0] tx_script = binascii.unhexlify(bytes(source_input['scriptPubKey'], 'utf-8')) int(int(len(tx_script))) # Script length s += tx_script # Script s += b'\xff' * 4 # Sequence # list of inputs for n, data_chunk in enumerate(data_array): data_chunk = config.PREFIX + data_chunk # prefix the data_chunk # get the scripts scriptSig, redeemScript, outputScript = p2sh_encoding.make_p2sh_encoding_redeemscript(data_cM hunk, n, pubkey, multisig_pubkeys, multisig_pubkeys_required) #substituteScript = scriptSig + outputScript s += txhash # TxOutHash s += (n).to_bytes(4, byteorder='little') # TxOutIndex (assumes 0-based) #s += var_int(len(substituteScript)) # Script length #s += substituteScript # Script s += var_int(len(scriptSig))# + len(outputScript)) M # Script length s += scriptSig # Script #s += outputScript # Script s += b'\xff' * 4 # Sequence # number of outputs, always 1 for the opreturn n += len(destination_outputs) # encode output length # destination outputs for destination, value in destination_outputs: tx_script, witness_script = get_script(destination)M s += value.to_bytes(8, byteorder='little') # Value s += var_int(int(len(tx_script))) # Script length s += tx_script # Script # opreturn to signal P2SH encoding key = arc4.init_arc4(txid) data_chunk = config.PREFIX + b'P2SH' data_chunk = key.encrypt(data_chunk) tx_script = OP_RETURN # OP_RETURN tx_script += op_push(len(data_chunk)) # Push bytes of data chunk tx_script += data_chunk # Data s += (0).to_bytes(8, byteorder='little') # Value s += var_int(int(len(tx_script))) # Script length s += tx_script # Script s += (0).to_bytes(4, byteorder='little') # LockTime UUUUUUUUUUUUUUUUUUUUUUUUUUUUU !22222222222222222222222222222222222222222222222222 text/plain;charset=utf-8 # Merged Python Files Datastreams are identified by the address that publishes them, and referenced in transaction outputs. For CFD leverage, 1x = 5040, 2x = 10080, etc.: 5040 is a superior highly composite number and a colossally abundant number, and has 1-10, 12 as factors. All wagers are in XCP. Expiring a bet match doesn open the constituent bets. (So all bets may be logger = logging.getLogger(__name__) from counterpartylib.lib import config from counterpartylib.lib import exceptions from counterpartylib.lib import util from counterpartylib.lib import log from counterpartylib.lib import message_type LENGTH = 2 + 4 + 8 + 8 + 8 + 4 + 4 def initialise (db): cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS bets( tx_index INTEGER UNIQUE, tx_hash TEXT M block_index INTEGER, source TEXT, feed_address TEXT, bet_type INTEGER, deadline INTEGER, wager_quantity INTEGER, wager_remaining INTEGER, counterwager_quantity INTEGER, counterwager_remaining INTEGER, target_value REAL, leverage INTEGER, expirationM expire_index INTEGER, fee_fraction_int INTEGER, status TEXT, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index), PRIMARY KEY (tx_index, tx_hash)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON bets (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTSM index_hash_idx ON bets (tx_index, tx_hash) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS expire_idx ON bets (status, expire_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS feed_valid_bettype_idx ON bets (feed_address, status, bet_type) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON bets (source) ''') execute('''CREATE INDEX IF NOT EXISTS status_idx ON bets (status) ''') cursor.execute('''CREATE TABLE IF NOT EXISTS bet_matches( id TEXT PRIMARY KEY, tx0_index INTEGER, tx0_hash TEXT, tx0_address TEXT, tx1_index INTEGER, tx1_hash TEXT, tx1_address TEXT, tx0_bet_type INTEGER, tx1_bet_type INTEGER, feed_address TEXT, initial_value INTEGER, deadline INTEGER, target_value REAL, leverage INTEGER, forward_quantity INTEGER, backward_quantity INTEGER, tx0_block_index INTEGER, tx1_block_index INTEGER, block_index INTEGER, tx0_expiration INM tx1_expiration INTEGER, match_expire_index INTEGER, fee_fraction_int INTEGER, status TEXT, FOREIGN KEY (tx0_index, tx0_hash, tx0_block_index) REFERENCES transactions(tx_index, tx_hash, block_index), FOREIGN KEY (tx1_index, tx1_hash, tx1_block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS match_expire_idx ON bet_matches (status, match_expire_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS valid_feed_idx ON bet_matches (feed_address, status) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS id_idx ON bet_matches (id) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx0_address_idx ON bet_matches (tx0_address) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx1_address_idx ON bet_matches (tx1_address) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS status_idx ON bet_matches (status) ''') # Bet Expirations cursor.execute('''CREATE TABLE IF NOT EXISTS bet_expirations( bet_index INTEGER PRIMARY KEY, bet_hash TEXT UNIQUE, source TEXT, block_indexM FOREIGN KEY (block_index) REFERENCES blocks(block_index), FOREIGN KEY (bet_index, bet_hash) REFERENCES bets(tx_index, tx_hash)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON bet_expirations (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON bet_expirations (source) ''') # Bet Match Expirations sor.execute('''CREATE TABLE IF NOT EXISTS bet_match_expirations( bet_match_id TEXT PRIMARY KEY, tx0_address TEXT, tx1_address TEXT, block_index INTEGER, FOREIGN KEY (bet_match_id) REFERENCES bet_matches(id), FOREIGN KEY (block_index) REFERENCES blocks(block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON bet_matcM h_expirations (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx0_address_idx ON bet_match_expirations (tx0_address) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx1_address_idx ON bet_match_expirations (tx1_address) ''') # Bet Match Resolutions cursor.execute('''CREATE TABLE IF NOT EXISTS bet_match_resolutions( bet_match_id TEXT PRIMARY KEY, bet_match_type_id INTEGER, block_index INTEGER, winner TEXT, settled BOOL, bull_credit INTEGER, bear_credit INTEGER, escrow_less_fee INTEGER, fee INTEGER, FOREIGN KEY (bet_match_id) REFERENCES bet_matches(id), FOREIGN KEY (block_index) REFERENCES blocks(block_index)) ''') def cancel_bet (db, bM et, status, block_index): cursor = db.cursor() # Update status of bet. 'status': status, 'tx_hash': bet['tx_hash'] sql='update bets set status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'bets', bindings) util.credit(db, bet['source'], config.XCP, bet['wager_remaining'], action='recredit wager remaining', event=bet['tx_hash']) cursor = db.cursor() def cancel_bet_match (db, bet_match, sM tatus, block_index): fill, etc. constituent bets. cursor = db.cursor() # Recredit tx0 address. util.credit(db, bet_match['tx0_address'], config.XCP, bet_match['forward_quantity'], action='recredit forward quantity', event=bet_match['id']) # Recredit tx1 address. util.credit(db, bet_match['tx1_address'], config.XCP, bet_match['backward_quantity'], action='recredit backward quantity', event=bet_match['id']) # Update status of bM 'status': status, 'bet_match_id': bet_match['id'] sql='update bet_matches set status = :status where id = :bet_match_id' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'bet_matches', bindings) def get_fee_fraction (db, feed_address): '''Get fee fraction from last broadcast from the feed_address address. cursor = db.cursor() broadcasts = list(cursor.execute('''SELECT * FROM broadcasts WHERE (sM tatus = ? AND source = ?) ORDER BY tx_index ASC''', ('valid', feed_address))) last_broadcast = broadcasts[-1] fee_fraction_int = last_broadcast['fee_fraction_int'] if fee_fraction_int: return fee_fraction_int / 1e8 else: return 0 def validate (db, source, feed_address, bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration, block_index): erage is None: leverage = 5040 if wager_quantity > config.MAX_INT or counterwager_quantity > config.MAX_INT or bet_type > config.MAX_INT \ or deadline > config.MAX_INT or leverage > config.MAX_INT or block_index + expiration > config.MAX_INT: problems.append('integer overflow') # Look at feed to be bet on. cursor = db.cursor() broadcasts = list(cursor.execute('''SELECT * FROM broadcasts WHERE (status = ? AND source = ?) ORDER BY tx_index ASC''', ('valid', feedM if not broadcasts: problems.append('feed doesn elif not broadcasts[-1]['text']: problems.append('feed is locked') elif broadcasts[-1]['timestamp'] >= deadline: problems.append('deadline in that feed if not bet_type in (0, 1, 2, 3): problems.append('unknown bet type') # Valid leverage level? if leverage != 5040 and bet_type in (2,3): # Equal, NotEqual problems.append('leverage used with Equal or M if leverage < 5040 and not bet_type in (0,1): # BullCFD, BearCFD (fractional leverage makes sense precisely with CFDs) problems.append('leverage level too low') if bet_type in (0,1): # BullCFD, BearCFD if block_index >= 312350: # Protocol change. problems.append('CFDs temporarily disabled') if not isinstance(wager_quantity, int): problems.append('wager_quantity must be in satoshis') return problems, leverage if not isinstance(counterwageM problems.append('counterwager_quantity must be in satoshis') return problems, leverage if not isinstance(expiration, int): problems.append('expiration must be expressed as an integer block delta') return problems, leverage if wager_quantity <= 0: problems.append('non if counterwager_quantity <= 0: problems.append('non positive counterwager') if deadline < 0: problems.append('negative deadline') if expiration < 0: problems.apM pend('negative expiration') if expiration == 0 and not (block_index >= 317500 or config.TESTNET or config.REGTEST): # Protocol change. problems.append('zero expiration') if target_value: if bet_type in (0,1): # BullCFD, BearCFD problems.append('CFDs have no target value') if target_value < 0: problems.append('negative target value') if expiration > config.MAX_EXPIRATION: problems.append('expiration overflow') return problems, leverage ef compose (db, source, feed_address, bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration): if util.get_balance(db, source, config.XCP) < wager_quantity: raise exceptions.ComposeError('insufficient funds') problems, leverage = validate(db, source, feed_address, bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration, util.CURRENT_BLOCK_INDEX) if util.date_passed(deadline): problems.append('deadline passed') if problems: raise exceptions.ComposeError(problems) data = message_type.pack(ID) data += struct.pack(FORMAT, bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration) return (source, [(feed_address, None)], data) def parse (db, tx, message): bet_parse_cursor = db.cursor() # Unpack message. if len(message) != LENGTH: raise exceptions.UnpackM (bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration) = struct.unpack(FORMAT, message) status = 'open' except (exceptions.UnpackError, struct.error): (bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration, fee_fraction_int) = 0, 0, 0, 0, 0, 0, 0, 0 status = 'invalid: could not unpack' odds, fee_fraction = 0, 0 feed_address = tx['destination'] odds = util.price(wager_quantity, counterwager_quantity) except ZeroDivisionError: odds = 0 fee_fraction = get_fee_fraction(db, feed_address) bet_parse_cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], config.XCP)) balances = list(bet_parse_cursor) if not balances: wager_quantity = 0 balance = balances[0]['quantity'] if balance < wager_quantity: wager_quantity = balance counterwager_quantity = int(util.price(wager_quantity, odds)) problems, leverage = validate(db, tx['source'], feed_address, bet_type, deadline, wager_quantity, counterwager_quantity, target_value, leverage, expiration, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) # Debit quantity wagered. (Escrow.) util.debit(db, tx['source'], config.XCP, wager_quantity, action='bet', event=tx['tx_hash']) # Add parsed transaction to message-type 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'feed_address': feed_address, 'bet_type': bet_type, 'deadline': deadline, 'wager_quantity': wager_quantity, 'wager_remaining': wager_quanM 'counterwager_quantity': counterwager_quantity, 'counterwager_remaining': counterwager_quantity, 'target_value': target_value, 'leverage': leverage, 'expiration': expiration, 'expire_index': tx['block_index'] + expiration, 'fee_fraction_int': fee_fraction * 1e8, 'status': status, if "integer overflow" not in status: sql = 'insert into bets values(:tx_index, :tx_hash, :block_index, :source, :feed_address, :bet_type, :deadline, :wM ager_quantity, :wager_remaining, :counterwager_quantity, :counterwager_remaining, :target_value, :leverage, :expiration, :expire_index, :fee_fraction_int, :status)' bet_parse_cursor.execute(sql, bindings) logger.warn("Not storing [bet] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) if status == 'open' and tx['block_index'] != config.MEMPOOL_BLOCK_INDEX: match(db, tx) bet_parse_cursor.close() cursor = db.cursor() # Get bet in question. bets = list(cursor.execute('''SELECT * FROM bets\ WHERE (tx_index = ? AND status = ?)''', (tx['tx_index'], 'open'))) cursor.close() assert len(bets) == 1 # Get counterbet_type. if tx1['bet_type'] % 2: counterbet_type = tx1['bet_type'] - 1 else: counterbet_type = tx1['bet_type'] + 1 feed_address = tx1['feed_address'] ursor.execute('''SELECT * FROM bets\ WHERE (feed_address=? AND status=? AND bet_type=?)''', (tx1['feed_address'], 'open', counterbet_type)) tx1_wager_remaining = tx1['wager_remaining'] tx1_counterwager_remaining = tx1['counterwager_remaining'] bet_matches = cursor.fetchall() if tx['block_index'] > 284500 or config.TESTNET or config.REGTEST: # Protocol change. sorted(bet_matches, key=lambda x: x['tx_index']) M # Sort by tx index second. sorted(bet_matches, key=lambda x: util.price(x['wager_quantity'], x['counterwager_quantity'])) # Sort by price first. tx1_status = tx1['status'] for tx0 in bet_matches: if tx1_status != 'open': break logger.debug('Considering: ' + tx0['tx_hash']) tx0_wager_remaining = tx0['wager_remaining'] tx0_counterwager_remaining = tx0['counterwager_remaining'] # Bet types must be opposite. if counterbet_type != tx0['betM logger.debug('Skipping: bet types disagree.') continue # Leverages must agree exactly if tx0['leverage'] != tx1['leverage']: logger.debug('Skipping: leverages disagree.') continue # Target values must agree exactly. if tx0['target_value'] != tx1['target_value']: logger.debug('Skipping: target values disagree.') continue # Fee fractions must agree exactly. if tx0['fee_fraction_int'] != M tx1['fee_fraction_int']: logger.debug('Skipping: fee fractions disagree.') continue # Deadlines must agree exactly. if tx0['deadline'] != tx1['deadline']: logger.debug('Skipping: deadlines disagree.') continue # If the odds agree, make the trade. The found order sets the odds, # and they trade as much as they can. tx0_odds = util.price(tx0['wager_quantity'], tx0['counterwager_quantity']) tx0_inverse_odds = util.price(tM x0['counterwager_quantity'], tx0['wager_quantity']) tx1_odds = util.price(tx1['wager_quantity'], tx1['counterwager_quantity']) if tx['block_index'] < 286000: tx0_inverse_odds = util.price(1, tx0_odds) # Protocol change. logger.debug('Tx0 Inverse Odds: {}; Tx1 Odds: {}'.format(float(tx0_inverse_odds), float(tx1_odds))) if tx0_inverse_odds > tx1_odds: logger.debug('Skipping: price mismatch.') logger.debug('Potential forward quantities: {}, {}'.foM rmat(tx0_wager_remaining, int(util.price(tx1_wager_remaining, tx1_odds)))) forward_quantity = int(min(tx0_wager_remaining, int(util.price(tx1_wager_remaining, tx1_odds)))) logger.debug('Forward Quantity: {}'.format(forward_quantity)) backward_quantity = round(forward_quantity / tx0_odds) logger.debug('Backward Quantity: {}'.format(backward_quantity)) if not forward_quantity: logger.debug('Skipping: zero forward quantity.') if tx1['block_index'] >= 286500 or config.TESTNET or config.REGTEST: # Protocol change. if not backward_quantity: logger.debug('Skipping: zero backward quantity.') continue bet_match_id = util.make_id(tx0['tx_hash'], tx1['tx_hash']) # Debit the order. # Counterwager remainings may be negative. tx0_wager_remaining = tx0_wager_remaining - forward_quantity tx0_counterwager_remaiM ning = tx0_counterwager_remaining - backward_quantity tx1_wager_remaining = tx1_wager_remaining - backward_quantity tx1_counterwager_remaining = tx1_counterwager_remaining - forward_quantity tx0_status = 'open' if tx0_wager_remaining <= 0 or tx0_counterwager_remaining <= 0: # Fill order, and recredit give_remaining. tx0_status = 'filled' util.credit(db, tx0['source'], config.XCP, tx0_wager_remaining, eM vent=tx1['tx_hash'], action='filled') bindings = { 'wager_remaining': tx0_wager_remaining, 'counterwager_remaining': tx0_counterwager_remaining, 'status': tx0_status, 'tx_hash': tx0['tx_hash'] sql='update bets set wager_remaining = :wager_remaining, counterwager_remaining = :counterwager_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, tx['bM lock_index'], 'update', 'bets', bindings) if tx1['block_index'] >= 292000 or config.TESTNET or config.REGTEST: # Protocol change if tx1_wager_remaining <= 0 or tx1_counterwager_remaining <= 0: # Fill order, and recredit give_remaining. tx1_status = 'filled' util.credit(db, tx1['source'], config.XCP, tx1_wager_remaining, event=tx1['tx_hash'], action='filled') bindings = { 'wager_remM aining': tx1_wager_remaining, 'counterwager_remaining': tx1_counterwager_remaining, 'status': tx1_status, 'tx_hash': tx1['tx_hash'] sql='update bets set wager_remaining = :wager_remaining, counterwager_remaining = :counterwager_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, tx['block_index'], 'update', 'bets', bindings) # Get last value of feed. broadcasts = list(cursor.execute('''SELECT * FROM broadcasts WHERE (status = ? AND source = ?) ORDER BY tx_index ASC''', ('valid', feed_address))) initial_value = broadcasts[-1]['value'] # Record bet fulfillment. bindings = { 'id': util.make_id(tx0['tx_hash'], tx['tx_hash']), 'tx0_index': tx0['tx_index'], 'tx0_hash': tx0['tx_hash'], 'tx0_address': tx0['source'], 'tx1_index': tx1['tx_index'], 'tx1_hash': tx1['tx_hash'], 'tx1_address': tx1['source'], 'tx0_bet_type': tx0['bet_type'], 'tx1_bet_type': tx1['bet_type'], 'feed_address': tx1['feed_address'], 'initial_value': initial_value, 'deadline': tx1['deadline'], 'target_value': tx1['target_value'], 'leverage': tx1['leverage'], 'forward_quantity': forward_quantity, 'backward_quantity': bM 'tx0_block_index': tx0['block_index'], 'tx1_block_index': tx1['block_index'], 'block_index': max(tx0['block_index'], tx1['block_index']), 'tx0_expiration': tx0['expiration'], 'tx1_expiration': tx1['expiration'], 'match_expire_index': min(tx0['expire_index'], tx1['expire_index']), 'fee_fraction_int': tx1['fee_fraction_int'], 'status': 'pending', ql='insert into bet_matches values(:id, :tx0_index, :tx0_hash, :tx0_address, :tx1_index, :tx1_hash, :tx1_address, :tx0_bet_type, :tx1_bet_type, :feed_address, :initial_value, :deadline, :target_value, :leverage, :forward_quantity, :backward_quantity, :tx0_block_index, :tx1_block_index, :block_index, :tx0_expiration, :tx1_expiration, :match_expire_index, :fee_fraction_int, :status)' cursor.execute(sql, bindings) def expire (db, block_index, block_time): cursor = db.curM # Expire bets and give refunds for the quantity wager_remaining. cursor.execute('''SELECT * FROM bets \ WHERE (status = ? AND expire_index < ?)''', ('open', block_index)) for bet in cursor.fetchall(): cancel_bet(db, bet, 'expired', block_index) # Record bet expiration. bindings = { 'bet_index': bet['tx_index'], 'bet_hash': bet['tx_hash'], 'source': bet['source'], 'block_index': block_index sql='insert into bet_expirations values(:bet_index, :bet_hash, :source, :block_index)' cursor.execute(sql, bindings) # Expire bet matches whose deadline is more than two weeks before the current block time. cursor.execute('''SELECT * FROM bet_matches \ WHERE (status = ? AND deadline < ?)''', ('pending', block_time - config.TWO_WEEKS)) for bet_match in cursor.fetchall(): cancel_bet_match(db, bet_match, 'expired', block_index) # Record bet match expiratM bindings = { 'bet_match_id': bet_match['id'], 'tx0_address': bet_match['tx0_address'], 'tx1_address': bet_match['tx1_address'], 'block_index': block_index sql='insert into bet_match_expirations values(:bet_match_id, :tx0_address, :tx1_address, :block_index)' cursor.execute(sql, bindings) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 sage, with or without a price. Multiple messages per block are allowed. Bets are be made on the 'timestamp' field, and not the block index. An address is a feed of broadcasts. Feeds may be locked with a broadcast whose text field is identical to (case insensitive). Bets on a feed reference the address that is the source of the feed in an output which includes the (latest) required fee. Broadcasts without a price may not be used for betting. Broadcasts about events with a small number of possible outcoM mes (e.g. sports games), should be written, for example, such that a price of 1 XCP means one outcome, 2 XCP means another, etc., which schema should be described in the 'text' field. fee_fraction: .05 XCP means 5%. It may be greater than 1, however; but because it is stored as a four byte integer, it may not be greater than about from fractions import Fraction logger = logging.getLogger(__name__) from bitcoin.core import VarInM from counterpartylib.lib import exceptions from counterpartylib.lib import config from counterpartylib.lib import util from counterpartylib.lib import log from counterpartylib.lib import message_type # NOTE: Pascal strings are used for storing texts for backwards cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS broadcasts( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, timestamp INTEGER, value REAL, fee_fraction_int INTEGER, text TEXT, locked BOOL, status TEXT, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') cursor.execute('''CREATEM INDEX IF NOT EXISTS block_index_idx ON broadcasts (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS status_source_idx ON broadcasts (status, source) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS status_source_index_idx ON broadcasts (status, source, tx_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS timestamp_idx ON broadcasts (timesM ''') def validate (db, source, timestamp, value, fee_fraction_int, text, block_index): if timestamp > config.MAX_INT or value > config.MAX_INT or fee_fraction_int > config.MAX_INT: problems.append('integer overflow') if util.enabled('max_fee_fraction'): if fee_fraction_int >= config.UNIT: problems.append('fee fraction greater than or equal to 1') if fee_fraction_int > 4294967295: problemM s.append('fee fraction greater than 42.94967295') if timestamp < 0: problems.append('negative timestamp') problems.append('null source address') # Check previous broadcast in this feed. cursor = db.cursor() broadcasts = list(cursor.execute('''SELECT * FROM broadcasts WHERE (status = ? AND source = ?) ORDER BY tx_index ASC''', ('valid', source))) last_broadcast = broadcasts[-1] if last_broadcast['locked']: problems.append('locked feed') elif timestamp <= last_broadcast['timestamp']: problems.append('feed timestamps not monotonically increasing') if not (block_index >= 317500 or config.TESTNET or config.REGTEST): # Protocol change. if len(text) > 52: problems.append('text too long') if util.enabled('options_require_memo') and text and text.lower().startswith('options'): # Check for options and if they are valid. options = util.parsM e_options_from_string(text) if options is not False: util.validate_address_options(options) except exceptions.OptionsError as e: problems.append(str(e)) def compose (db, source, timestamp, value, fee_fraction, text): # Store the fee fraction as an integer. fee_fraction_int = int(fee_fraction * 1e8) problems = validate(db, source, timestamp, value, fee_fraction_int, text, util.CURRENT_BLOCK_INDEX) if problems: raise exceptions.CoM mposeError(problems) data = message_type.pack(ID) # always use custom length byte instead of problematic usage of 52p format and make sure to encode('utf-8') for length if util.enabled('broadcast_pack_text'): data += struct.pack(FORMAT, timestamp, value, fee_fraction_int) data += VarIntSerializer.serialize(len(text.encode('utf-8'))) data += text.encode('utf-8') if len(text) <= 52: curr_format = FORMAT + '{}p'.format(len(text) + 1) curr_format = FORMAT + '{}s'.format(len(text)) data += struct.pack(curr_format, timestamp, value, fee_fraction_int, text.encode('utf-8')) return (source, [], data) def parse (db, tx, message): cursor = db.cursor() # Unpack message. if util.enabled('broadcast_pack_text', tx['block_index']): timestamp, value, fee_fraction_int, rawtext = struct.unpack(FORMAT + '{}s'.format(len(message) - LENGTH), message) textlen = VarIntSerializer.deserialize(raM if textlen == 0: text = b'' text = rawtext[-textlen:] assert len(text) == textlen if len(message) - LENGTH <= 52: curr_format = FORMAT + '{}p'.format(len(message) - LENGTH) curr_format = FORMAT + '{}s'.format(len(message) - LENGTH) timestamp, value, fee_fraction_int, text = struct.unpack(curr_format, message) text = text.M except UnicodeDecodeError: text = '' status = 'valid' except (struct.error) as e: timestamp, value, fee_fraction_int, text = 0, None, 0, None status = 'invalid: could not unpack' if status == 'valid': # For SQLite3 timestamp = min(timestamp, config.MAX_INT) value = min(value, config.MAX_INT) problems = validate(db, tx['source'], timestamp, value, fee_fraction_int, text, tx['block_index']) if problems: statuM s = 'invalid: ' + '; '.join(problems) if text and text.lower() == 'lock': timestamp, value, fee_fraction_int, text = 0, None, None, None lock = False # Add parsed transaction to message-type 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'timestamp': timestamp, 'value': value, fee_fraction_int': fee_fraction_int, 'text': text, 'locked': lock, 'status': status, if "integer overflow" not in status: sql = 'insert into broadcasts values(:tx_index, :tx_hash, :block_index, :source, :timestamp, :value, :fee_fraction_int, :text, :locked, :status)' cursor.execute(sql, bindings) logger.warn("Not storing [broadcast] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) rocessing if broadcast is invalid for any reason if util.enabled('broadcast_invalid_check') and status != 'valid': # Options? Should not fail to parse due to above checks. if util.enabled('options_require_memo') and text and text.lower().startswith('options'): options = util.parse_options_from_string(text) if options is not False: op_bindings = { 'block_index': tx['block_index'], 'address': tx['source'], 'options': options } sql = 'insert or replace into addresses(address, options, block_index) values(:address, :options, :block_index)' cursor = db.cursor() cursor.execute(sql, op_bindings) # Negative values (default to ignore). if value is None or value < 0: # Cancel Open Bets? if value == -2: cursor.execute('''SELECT * FROM bets \ WHERE (status = ? AND feed_address = ?)''', ('open', tx['source'])) for i in list(cursor): bet.cancel_bet(db, i, 'dropped', tx['block_index']) # Cancel Pending Bet Matches? if value == -3: cursor.execute('''SELECT * FROM bet_matches \ WHERE (status = ? AND feed_address = ?)''', ('pending', tx['source'])) for bet_match in list(cursor): bet.cancel_bet_match(db, bet_match, 'dropped', tx['block_indM cursor.close() # stop processing if broadcast is invalid for any reason # @TODO: remove this check once broadcast_invalid_check has been activated if util.enabled('max_fee_fraction') and status != 'valid': # Handle bet matches that use this feed. cursor.execute('''SELECT * FROM bet_matches \ WHERE (status=? AND feed_address=?) ORDER BY tx1_index ASC, tx0_index ASC''', ('pending', tx['sourM for bet_match in cursor.fetchall(): broadcast_bet_match_cursor = db.cursor() bet_match_id = util.make_id(bet_match['tx0_hash'], bet_match['tx1_hash']) bet_match_status = None # Calculate total funds held in escrow and total fee to be paid if # the bet match is settled. Escrow less fee is amount to be paid back # to betters. total_escrow = bet_match['forward_quantity'] + bet_match['backward_quantity'] if util.enabled('inmutable_fee_fractionM fee_fraction = bet_match['fee_fraction_int'] / config.UNIT fee_fraction = fee_fraction_int / config.UNIT fee = int(fee_fraction * total_escrow) # Truncate. escrow_less_fee = total_escrow - fee # Get known bet match type IDs. cfd_type_id = util.BET_TYPE_ID['BullCFD'] + util.BET_TYPE_ID['BearCFD'] equal_type_id = util.BET_TYPE_ID['Equal'] + util.BET_TYPE_ID['NotEqual'] # Get the bet match type ID of this bet matcM bet_match_type_id = bet_match['tx0_bet_type'] + bet_match['tx1_bet_type'] # Contract for difference, with determinate settlement date. if bet_match_type_id == cfd_type_id: # Recognise tx0, tx1 as the bull, bear (in the right direction). if bet_match['tx0_bet_type'] < bet_match['tx1_bet_type']: bull_address = bet_match['tx0_address'] bear_address = bet_match['tx1_address'] bull_escrow = bet_match['forward_quantity'] bear_escrow = bet_match['backward_quantity'] bull_address = bet_match['tx1_address'] bear_address = bet_match['tx0_address'] bull_escrow = bet_match['backward_quantity'] bear_escrow = bet_match['forward_quantity'] leverage = Fraction(bet_match['leverage'], 5040) initial_value = bet_match['initial_value'] bear_credit = bear_escrow - (value - initial_value) * leverage * config.UNIT bull_credit = escrow_less_fee - bear_credit bear_credit = round(bear_credit) bull_credit = round(bull_credit) # Liquidate, as necessary. if bull_credit >= escrow_less_fee or bull_credit <= 0: if bull_credit >= escrow_less_fee: bull_credit = escrow_less_fee bear_credit = 0 bet_match_status = 'settled: liquidated for bull' util.credit(db, bull_address, config.XCP,M bull_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) elif bull_credit <= 0: bull_credit = 0 bear_credit = escrow_less_fee bet_match_status = 'settled: liquidated for bear' util.credit(db, bear_address, config.XCP, bear_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) # Pay fee to feed. util.credit(db, bet_match['feed_address'], config.XCP, feeM , action='feed fee', event=tx['tx_hash']) # For logging purposes. bindings = { 'bet_match_id': bet_match_id, 'bet_match_type_id': bet_match_type_id, 'block_index': tx['block_index'], 'settled': False, 'bull_credit': bull_credit, 'bear_credit': bear_credit, 'winner': None, 'escrow_less_fee': None, 'fee': feM sql='insert into bet_match_resolutions values(:bet_match_id, :bet_match_type_id, :block_index, :settled, :bull_credit, :bear_credit, :winner, :escrow_less_fee, :fee)' cursor.execute(sql, bindings) # Settle (if not liquidated). elif timestamp >= bet_match['deadline']: bet_match_status = 'settled' util.credit(db, bull_address, config.XCP, bull_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hasM util.credit(db, bear_address, config.XCP, bear_credit, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) # Pay fee to feed. util.credit(db, bet_match['feed_address'], config.XCP, fee, action='feed fee', event=tx['tx_hash']) # For logging purposes. bindings = { 'bet_match_id': bet_match_id, 'bet_match_type_id': bet_match_type_id, 'block_index': tx['block_indeM 'settled': True, 'bull_credit': bull_credit, 'bear_credit': bear_credit, 'winner': None, 'escrow_less_fee': None, 'fee': fee sql='insert into bet_match_resolutions values(:bet_match_id, :bet_match_type_id, :block_index, :settled, :bull_credit, :bear_credit, :winner, :escrow_less_fee, :fee)' cursor.execute(sql, bindings) # Equal[/NotEqM elif bet_match_type_id == equal_type_id and timestamp >= bet_match['deadline']: # Recognise tx0, tx1 as the bull, bear (in the right direction). if bet_match['tx0_bet_type'] < bet_match['tx1_bet_type']: equal_address = bet_match['tx0_address'] notequal_address = bet_match['tx1_address'] equal_address = bet_match['tx1_address'] notequal_address = bet_match['tx0_address'] # Decide M who won, and credit appropriately. if value == bet_match['target_value']: winner = 'Equal' bet_match_status = 'settled: for equal' util.credit(db, equal_address, config.XCP, escrow_less_fee, action='bet {}'.format(bet_match_status), event=tx['tx_hash']) winner = 'NotEqual' bet_match_status = 'settled: for notequal' util.credit(db, notequal_address, config.XCP, escrow_less_fee, action='bet {}M '.format(bet_match_status), event=tx['tx_hash']) # Pay fee to feed. util.credit(db, bet_match['feed_address'], config.XCP, fee, action='feed fee', event=tx['tx_hash']) # For logging purposes. bindings = { 'bet_match_id': bet_match_id, 'bet_match_type_id': bet_match_type_id, 'block_index': tx['block_index'], 'settled': None, 'bull_credit': None, 'bear_credit': None, 'winner': winner, 'escrow_less_fee': escrow_less_fee, 'fee': fee sql='insert into bet_match_resolutions values(:bet_match_id, :bet_match_type_id, :block_index, :settled, :bull_credit, :bear_credit, :winner, :escrow_less_fee, :fee)' cursor.execute(sql, bindings) # Update the bet match if bet_match_status: bindings = { 'status': bet_match_status, 'bet_match_id': utilM .make_id(bet_match['tx0_hash'], bet_match['tx1_hash']) sql='update bet_matches set status = :status where id = :bet_match_id' cursor.execute(sql, bindings) log.message(db, tx['block_index'], 'update', 'bet_matches', bindings) broadcast_bet_match_cursor.close() # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 = logging.getLogger(__name__) from counterpartylib.lib import config from counterpartylib.lib import exceptions from counterpartylib.lib import util from counterpartylib.lib import log from counterpartylib.lib import message_type cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS btcpays( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, destination TEXT, btc_amount INTEGER, order_match_id TEXT, status TEXT, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') # Disallows invalids: FOREIGN KEY (order_match_id) REFERENCES order_matches(id)) cursor.execute('''CREATE INDEX IF NOT EXISTS blockM _index_idx ON btcpays (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON btcpays (source) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS destination_idx ON btcpays (destination) ''') def validate (db, source, order_match_id, block_index): order_match = None cursor = db.cursor() cursor.execute('''SELECT * FROM order_matches \ WM HERE id = ?''', (order_match_id,)) order_matches = cursor.fetchall() if len(order_matches) == 0: problems.append('no such order match %s' % order_match_id) return None, None, None, None, order_match, problems elif len(order_matches) > 1: assert False order_match = order_matches[0] if order_match['status'] == 'expired': problems.append('order match expired') elif order_match['status'] == 'completed': blems.append('order match completed') elif order_match['status'].startswith('invalid'): problems.append('order match invalid') elif order_match['status'] != 'pending': raise exceptions.OrderError('unrecognised order match status') # Figure out to which address the BTC are being paid. # Check that source address is correct. if order_match['backward_asset'] == config.BTC: if source != order_match['tx1_address'] and not (block_index >= 313900 or config.TESTNM ET or config.REGTEST): # Protocol change. problems.append('incorrect source address') destination = order_match['tx0_address'] btc_quantity = order_match['backward_quantity'] escrowed_asset = order_match['forward_asset'] escrowed_quantity = order_match['forward_quantity'] elif order_match['forward_asset'] == config.BTC: if source != order_match['tx0_address'] and not (block_index >= 313900 or config.TESTNET or config.REGTEST): # Protocol change. problems.append('incorrect source address') destination = order_match['tx1_address'] btc_quantity = order_match['forward_quantity'] escrowed_asset = order_match['backward_asset'] escrowed_quantity = order_match['backward_quantity'] assert False return destination, btc_quantity, escrowed_asset, escrowed_quantity, order_match, problems def compose (db, source, order_match_id): tx0_hash, tx1_hash = util.parse_id(order_match_id) destination, btc_quantitM y, escrowed_asset, escrowed_quantity, order_match, problems = validate(db, source, order_match_id, util.CURRENT_BLOCK_INDEX) if problems: raise exceptions.ComposeError(problems) # Warn if down to the wire. time_left = order_match['match_expire_index'] - util.CURRENT_BLOCK_INDEX if time_left < 4: logger.warning('Only {} blocks until that order match expires. The payment might not make into the blockchain in time.'.format(time_left)) if 10 - time_left < 4: logger.warning('Order maM tch has only {} confirmation(s).'.format(10 - time_left)) tx0_hash_bytes, tx1_hash_bytes = binascii.unhexlify(bytes(tx0_hash, 'utf-8')), binascii.unhexlify(bytes(tx1_hash, 'utf-8')) data = message_type.pack(ID) data += struct.pack(FORMAT, tx0_hash_bytes, tx1_hash_bytes) return (source, [(destination, btc_quantity)], data) def parse (db, tx, message): cursor = db.cursor() # Unpack message. if len(message) != LENGTH: raise exceptions.UnpackError _bytes, tx1_hash_bytes = struct.unpack(FORMAT, message) tx0_hash, tx1_hash = binascii.hexlify(tx0_hash_bytes).decode('utf-8'), binascii.hexlify(tx1_hash_bytes).decode('utf-8') order_match_id = util.make_id(tx0_hash, tx1_hash) status = 'valid' except (exceptions.UnpackError, struct.error) as e: tx0_hash, tx1_hash, order_match_id = None, None, None status = 'invalid: could not unpack' if status == 'valid': destination, btc_quantity, escrowed_asset, escrowed_quaM ntity, order_match, problems = validate(db, tx['source'], order_match_id, tx['block_index']) if problems: order_match = None status = 'invalid: ' + '; '.join(problems) if status == 'valid': # BTC must be paid all at once. if tx['btc_amount'] >= btc_quantity: # Credit source address for the currency that he bought with the bitcoins. util.credit(db, tx['source'], escrowed_asset, escrowed_quantity, action='btcpay', event=tx['tx_hash']) status = 'valid' # Update order match. bindings = { 'status': 'completed', 'order_match_id': order_match_id sql='update order_matches set status = :status where id = :order_match_id' cursor.execute(sql, bindings) log.message(db, tx['block_index'], 'update', 'order_matches', bindings) # Update give and get order status as filled if order_match is completed if util.enabled('btc_orM bindings = { 'status': 'pending', 'tx0_hash': tx0_hash, 'tx1_hash': tx1_hash sql='select * from order_matches where status = :status and ((tx0_hash in (:tx0_hash, :tx1_hash)) or ((tx1_hash in (:tx0_hash, :tx1_hash))))' cursor.execute(sql, bindings) order_matches = cursor.fetchall() if len(order_matches) == 0: # mark both btc gM et and give orders as filled when order_match is completed and give or get remaining = 0 bindings = { 'status': 'filled', 'tx0_hash': tx0_hash, 'tx1_hash': tx1_hash } sql='update orders set status = :status where ((tx_hash in (:tx0_hash, :tx1_hash)) and ((give_remaining = 0) or (get_remaining = 0)))' cursor.execute(sql, bindings) else: # always mark btc get order as filled when order_match is completed and give or get remaining = 0 bindings = { 'status': 'filled', 'source': tx['destination'], 'tx0_hash': tx0_hash, 'tx1_hash': tx1_hash } sql='update orders set status = :status where ((tx_hash in (:tx0_hash, :tx1_hash)) and ((give_remaining = 0) or (get_remaining = 0)) and (source = :sourcM cursor.execute(sql, bindings) # Add parsed transaction to message-type 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'destination': tx['destination'], 'btc_amount': tx['btc_amount'], 'order_match_id': order_match_id, 'status': status, if "integer overflow" not in status: sql = 'insert into btcpays values(:M tx_index, :tx_hash, :block_index, :source, :destination, :btc_amount, :order_match_id, :status)' cursor.execute(sql, bindings) logger.warn("Not storing [btcpay] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 logger = logging.getLogger(__name__) from fractions import Fraction from counterpartylib.lib import (config, exceptions, util) """Burn {} to earn {} during a special period of time.""".format(config.BTC, config.XCP) def initialise (db): cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS burns( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, burned INTEGER, earned INTEGER, status TEXT, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS status_idx ON burns (status) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS address_idx ON burns (source) ''') def validate (db, source, destination, quantity, block_indexM # Check destination address. if destination != config.UNSPENDABLE: problems.append('wrong destination address') if not isinstance(quantity, int): problems.append('quantity must be in satoshis') return problems if quantity < 0: problems.append('negative quantity') # Try to make sure that the burned funds won't go to waste. if block_index < config.BURN_START - 1: problems.append('too early') elif block_index > config.M problems.append('too late') def compose (db, source, quantity, overburn=False): cursor = db.cursor() destination = config.UNSPENDABLE problems = validate(db, source, destination, quantity, util.CURRENT_BLOCK_INDEX, overburn=overburn) if problems: raise exceptions.ComposeError(problems) # Check that a maximum of 1 BTC total is burned per address. burns = list(cursor.execute('''SELECT * FROM burns WHERE (status = ? AND source = ?)''', ('valid', source))M already_burned = sum([burn['burned'] for burn in burns]) if quantity > (1 * config.UNIT - already_burned) and not overburn: raise exceptions.ComposeError('1 {} may be burned per address'.format(config.BTC)) return (source, [(destination, quantity)], None) def parse (db, tx, MAINNET_BURNS, message=None): burn_parse_cursor = db.cursor() if config.TESTNET or config.REGTEST: problems = [] status = 'valid' if status == 'valid': blems = validate(db, tx['source'], tx['destination'], tx['btc_amount'], tx['block_index'], overburn=False) if problems: status = 'invalid: ' + '; '.join(problems) if tx['btc_amount'] != None: sent = tx['btc_amount'] sent = 0 if status == 'valid': # Calculate quantity of XCP earned. (Maximum 1 BTC in total, ever.) cursor = db.cursor() cursor.execute('''SELECT * FROM burns WHERE (status = ? AND sourM ce = ?)''', ('valid', tx['source'])) burns = cursor.fetchall() already_burned = sum([burn['burned'] for burn in burns]) ONE = 1 * config.UNIT max_burn = ONE - already_burned if sent > max_burn: burned = max_burn # Exceeded maximum burn; earn what you can. else: burned = sent total_time = config.BURN_END - config.BURN_START partial_time = config.BURN_END - tx['block_index'] multiplier = (1000 + (500 * FractM ion(partial_time, total_time))) earned = round(burned * multiplier) # Credit source address with earned XCP. util.credit(db, tx['source'], config.XCP, earned, action='burn', event=tx['tx_hash']) burned = 0 earned = 0 tx_index = tx['tx_index'] tx_hash = tx['tx_hash'] block_index = tx['block_index'] source = tx['source'] # Mainnet burns are hard line = MAINNM ET_BURNS[tx['tx_hash']] except KeyError: util.credit(db, line['source'], config.XCP, int(line['earned']), action='burn', event=line['tx_hash']) tx_index = tx['tx_index'] tx_hash = line['tx_hash'] block_index = line['block_index'] source = line['source'] burned = line['burned'] earned = line['earned'] status = 'valid' # Add parsed transaction to message-type # TODO: store sent in table 'tx_index': tx_index, 'tx_hash': tx_hash, 'block_index': block_index, 'source': source, 'burned': burned, 'earned': earned, 'status': status, if "integer overflow" not in status: sql = 'insert into burns values(:tx_index, :tx_hash, :block_index, :source, :burned, :earned, :status)' burn_parse_cursor.execute(sql, bindings) logger.warn("Not storing [burn] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debuM g("Bindings: %s" % (json.dumps(bindings), )) burn_parse_cursor.close() # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 offer_hash is the hash of either a bet or an order. logger = logging.getLogger(__name__) from counterpartylib.lib import (config, exceptions, util, message_type) from . import (order, bet, rps) def initialise (db): cursor = db.cursorM cursor.execute('''CREATE TABLE IF NOT EXISTS cancels( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, offer_hash TEXT, status TEXT, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') # Offer hash is not a foreign key. (And itM cannot be, because of some invalid cancels.) cursor.execute('''CREATE INDEX IF NOT EXISTS cancels_block_index_idx ON cancels (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON cancels (source) ''') def validate (db, source, offer_hash): # TODO: make query only if necessary cursor = db.cursor() cursor.execute('''SELECT * from orders WHERE tx_hash = ?''', (offer_hash,))M orders = list(cursor) cursor.execute('''SELECT * from bets WHERE tx_hash = ?''', (offer_hash,)) bets = list(cursor) cursor.execute('''SELECT * from rps WHERE tx_hash = ?''', (offer_hash,)) rps = list(cursor) offer_type = None if orders: offer_type = 'order' elif bets: offer_type = 'bet' elif rps: offer_type = 'rps' else: problems = ['no open offer with that hash'] offers = orders + bets + rps if offer['source'] != source: problems.append('incorrect source address') if offer['status'] != 'open': problems.append('offer not open') return offer, offer_type, problems def compose (db, source, offer_hash): # Check that offer exists. offer, offer_type, problems = validate(db, source, offer_hash) if problems: raise exceptions.ComposeError(problems) offer_hash_bytes = binascii.unhexlify(bytes(offer_hash, 'utf-8')) data = message_type.packM data += struct.pack(FORMAT, offer_hash_bytes) return (source, [], data) def parse (db, tx, message): cursor = db.cursor() # Unpack message. if len(message) != LENGTH: raise exceptions.UnpackError offer_hash_bytes = struct.unpack(FORMAT, message)[0] offer_hash = binascii.hexlify(offer_hash_bytes).decode('utf-8') status = 'valid' except (exceptions.UnpackError, struct.error) as e: offer_hash = None status = 'invalid: couldM if status == 'valid': offer, offer_type, problems = validate(db, tx['source'], offer_hash) if problems: status = 'invalid: ' + '; '.join(problems) if status == 'valid': # Cancel if order. if offer_type == 'order': order.cancel_order(db, offer, 'cancelled', tx['block_index']) # Cancel if bet. elif offer_type == 'bet': bet.cancel_bet(db, offer, 'cancelled', tx['block_index']) # Cancel if rps. f offer_type == 'rps': rps.cancel_rps(db, offer, 'cancelled', tx['block_index']) # If neither order or bet, mark as invalid. assert False # Add parsed transaction to message-type 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'offer_hash': offer_hash, 'status': status, if "integer overflow" not in status: sql='INSERT INTO cancels VALUES (:tx_index, :tx_hash, :block_index, :source, :offer_hash, :status)' cursor.execute(sql, bindings) logger.warn("Not storing [cancel] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 """Destroy a quantity of an asset.""" logger = logging.geM from counterpartylib.lib import util from counterpartylib.lib import config from counterpartylib.lib import script from counterpartylib.lib import message_type from counterpartylib.lib.script import AddressError from counterpartylib.lib.exceptions import * cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS destructions( tx_index INTEGER PRIMARY KEY, M tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, asset INTEGER, quantity INTEGER, tag TEXT, status TEXT, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS status_idx ON destructions (status) '''M cursor.execute('''CREATE INDEX IF NOT EXISTS address_idx ON destructions (source) ''') def pack(db, asset, quantity, tag): data = message_type.pack(ID) if isinstance(tag, str): tag = bytes(tag.encode('utf8'))[0:MAX_TAG_LENGTH] elif isinstance(tag, bytes): tag = tag[0:MAX_TAG_LENGTH] data += struct.pack(FORMAT, util.get_asset_id(db, asset, util.CURRENT_BLOCK_INDEX), quantity) def unpack(db, message): asset_id, quantity = struct.unpack(FORMAT, message[0:16]) tag = message[16:] asset = util.get_asset_name(db, asset_id, util.CURRENT_BLOCK_INDEX) except struct.error: raise UnpackError('could not unpack') except AssetIDError: raise UnpackError('asset id invalid') return asset, quantity, tag def validate (db, source, destination, asset, quantity): util.get_asset_id(db, asset, util.CURRENT_BLOCK_INDEX) raise ValidateError('asset invalid') script.validate(source) except AddressError: raise ValidateError('source address invalid') raise ValidateError('destination exists') if asset == config.BTC: raise ValidateError('cannot destroy {}'.format(config.BTC)) if type(quantity) != int: raise ValidateError('quantity not integer') if quantity > config.MAX_INT: raise ValidateError('integer overflow, quanM if quantity < 0: raise ValidateError('quantity negative') if util.get_balance(db, source, asset) < quantity: raise BalanceError('balance insufficient') def compose (db, source, asset, quantity, tag): # resolve subassets asset = util.resolve_subasset_longname(db, asset) validate(db, source, None, asset, quantity) data = pack(db, asset, quantity, tag) return (source, [], data) def parse (db, tx, message): status = 'valid' asset, quantity, tM ag = None, None, None asset, quantity, tag = unpack(db, message) validate(db, tx['source'], tx['destination'], asset, quantity) util.debit(db, tx['source'], asset, quantity, 'destroy', tx['tx_hash']) except UnpackError as e: status = 'invalid: ' + ''.join(e.args) except (ValidateError, BalanceError) as e: status = 'invalid: ' + ''.join(e.args) 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': asset, 'quantity': quantity, 'tag': tag, 'status': status, if "integer overflow" not in status: sql = 'insert into destructions values(:tx_index, :tx_hash, :block_index, :source, :asset, :quantity, :tag, :status)' cursor = db.cursor() cursor.execute(sql, bindings) logger.warn("Not storing [destroy] tx [M %s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # What is a dispenser? # A dispenser is a type of order where the holder address gives out a given amount # of units of an asset for a given amount of BTC satoshis received. # It's a very simple but powerful semantic to allow swaps to operate on-chain. from math import floor logger = logging.getLogger(__name__) from counterpartylib.lib import config from counterpartylib.lib import exceptions from counterpartylib.lib import util from counterpartylib.lib import log from counterpartylib.lib import message_type from counterpartylib.lib import address STATUS_OPEN_EMPTY_ADDRESS = 1 #STATUS_OPEN_ORACLE_PRICE = 20 #STATUS_OPEN_ORACLE_PRICE_EMPTY_ADDRESS = 21 cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS dispensers( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, asset TEXT, give_quantity INTEGER, escrow_quantity INTEGER, satoshirate INTEGER, status INTEGER, give_remaining IM FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') # Disallows invalids: FOREIGN KEY (order_match_id) REFERENCES order_matches(id)) cursor.execute('''CREATE INDEX IF NOT EXISTS dispensers_source_idx ON dispensers (source) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS dispensers_asset_idx ON dispensers (asset) cursor.execute('''CREATE TABLE IF NOT EXISTS dispenses( tx_index INTEGER, dispense_index INTEGER, tx_hash TEXT, block_index INTEGER, source TEXT, destination TEXT, asset TEXT, dispense_quantity INTEGER, dispenser_tx_hash TEXT, PRIMARY KEY (tx_index, dispense_index, source, destM FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') columns = [column['name'] for column in cursor.execute('''PRAGMA table_info(dispenses)''')] if 'dispenser_tx_hash' not in columns: cursor.execute('ALTER TABLE dispenses ADD COLUMN dispenser_tx_hash TEXT') columns = [column['name'] for column in cursor.execute('''PRAGMA table_info(dispensers)''')] if 'oracle_addrM ess' not in columns: cursor.execute('ALTER TABLE dispensers ADD COLUMN oracle_address TEXT') def validate (db, source, asset, give_quantity, escrow_quantity, mainchainrate, status, open_address, block_index, oracle_address): order_match = None if asset == config.BTC: problems.append('cannot dispense %s' % config.BTC) return None, problems # resolve subassets asset = util.resolve_subasset_longname(db, asset) if status == STATUS_OPEM N or status == STATUS_OPEN_EMPTY_ADDRESS: if give_quantity <= 0: problems.append('give_quantity must be positive') if mainchainrate <= 0: problems.append('mainchainrate must be positive') if escrow_quantity < give_quantity: problems.append('escrow_quantity must be greater or equal than give_quantity') elif not(status == STATUS_CLOSED): problems.append('invalid status %i' % status) cursor = db.cursor() cursor.execute('''SELECT quantity M WHERE address = ? and asset = ?''', (source,asset,)) available = cursor.fetchall() if len(available) == 0: problems.append('address doesn\'t has the asset %s' % asset) elif len(available) >= 1 and available[0]['quantity'] < escrow_quantity: problems.append('address doesn\'t has enough balance of %s (%i < %i)' % (asset, available[0]['quantity'], escrow_quantity)) if status == STATUS_OPEN_EMPTY_ADDRESS and not(open_address): open_address = source status = STATUS_OPEN query_address = open_address if status == STATUS_OPEN_EMPTY_ADDRESS else source cursor.execute('''SELECT * FROM dispensers WHERE source = ? AND asset = ? AND status=?''', (query_address, asset, STATUS_OPEN)) open_dispensers = cursor.fetchall() if status == STATUS_OPEN or status == STATUS_OPEN_EMPTY_ADDRESS: if len(open_dispensers) > 0 and open_dispensers[0]['satoshirate'] != mainchainrate: problemM s.append('address has a dispenser already opened for asset %s with a different mainchainrate' % asset) if len(open_dispensers) > 0 and open_dispensers[0]['give_quantity'] != give_quantity: problems.append('address has a dispenser already opened for asset %s with a different give_quantity' % asset) elif status == STATUS_CLOSED: if len(open_dispensers) == 0: problems.append('address doesnt has an open dispenser for asset %s' % asset) == STATUS_OPEN_EMPTY_ADDRESS: cursor.execute('''SELECT count(*) cnt FROM balances WHERE address = ?''', (query_address,)) existing_balances = cursor.fetchall() if existing_balances[0]['cnt'] > 0: problems.append('cannot open on another address if it has any balance history') if len(problems) == 0: asset_id = util.generate_asset_id(asset, block_index) if asset_id == 0: problems.append('cannot dispense %s' % asset) # M How can we test this on a test vector? if oracle_address is not None and util.enabled('oracle_dispensers', block_index): last_price, last_fee, last_label, last_updated = util.get_oracle_last_price(db, oracle_address, block_index) if last_price is None: problems.append('The oracle address %s has not broadcasted any price yet' % oracle_address) if len(problems) > 0: return None, problems return asset_id, None pose (db, source, asset, give_quantity, escrow_quantity, mainchainrate, status, open_address=None, oracle_address=None): assetid, problems = validate(db, source, asset, give_quantity, escrow_quantity, mainchainrate, status, open_address, util.CURRENT_BLOCK_INDEX, oracle_address) if problems: raise exceptions.ComposeError(problems) destination = [] data = message_type.pack(ID) data += struct.pack(FORMAT, assetid, give_quantity, escrow_quantity, mainchainrate, status) if status == STATUS_OPENM _EMPTY_ADDRESS and open_address: data += address.pack(open_address) if oracle_address is not None and util.enabled('oracle_dispensers'): oracle_fee = calculate_oracle_fee(db, escrow_quantity, give_quantity, mainchainrate, oracle_address, util.CURRENT_BLOCK_INDEX) if oracle_fee >= config.DEFAULT_REGULAR_DUST_SIZE: destination.append((oracle_address,oracle_fee)) data += address.pack(oracle_address) return (source, destination, data) lculate_oracle_fee(db, escrow_quantity, give_quantity, mainchainrate, oracle_address, block_index): last_price, last_fee, last_fiat_label, last_updated = util.get_oracle_last_price(db, oracle_address, block_index) last_fee_multiplier = (last_fee / config.UNIT) #Format mainchainrate to ######.## oracle_mainchainrate = util.satoshirate_to_fiat(mainchainrate) oracle_mainchainrate_btc = oracle_mainchainrate/last_price #Calculate the total amount earned for dispenser and M remaining = int(floor(escrow_quantity / give_quantity)) total_quantity_btc = oracle_mainchainrate_btc * remaining oracle_fee_btc = int(total_quantity_btc * last_fee_multiplier *config.UNIT) return oracle_fee_btc def parse (db, tx, message): cursor = db.cursor() # Unpack message. action_address = tx['source'] oracle_address = None assetid, give_quantity, escrow_quantity, mainchainrate, dispenser_status = struct.unpack(FORMAT, message[0:LENGTH]) read = LENGTH if dispenser_status == STATUS_OPEN_EMPTY_ADDRESS: action_address = address.unpack(message[LENGTH:LENGTH+21]) read = LENGTH + 21 if len(message) > read: oracle_address = address.unpack(message[read:read+21]) asset = util.generate_asset_name(assetid, util.CURRENT_BLOCK_INDEX) status = 'valid' except (exceptions.UnpackError, struct.error) as e: assetid, give_quantity, mainchainrate, asset = None, None, None, None status = 'invalid: could not unpack' if status == 'valid': if util.enabled("dispenser_parsing_validation", util.CURRENT_BLOCK_INDEX): asset_id, problems = validate(db, tx['source'], asset, give_quantity, escrow_quantity, mainchainrate, dispenser_status, action_address if dispenser_status == STATUS_OPEN_EMPTY_ADDRESS else None, tx['block_index'], oracle_address) problems = None if problems: status = 'invalid: ' + '; '.join(problM if dispenser_status == STATUS_OPEN or dispenser_status == STATUS_OPEN_EMPTY_ADDRESS: cursor.execute('SELECT * FROM dispensers WHERE source=:source AND asset=:asset AND status=:status', { 'source': action_address, 'asset': asset, 'status': STATUS_OPEN existing = cursor.fetchall() if len(existing) == 0: if (oracle_address != None) and utiM l.enabled('oracle_dispensers', tx['block_index']): oracle_fee = calculate_oracle_fee(db, escrow_quantity, give_quantity, mainchainrate, oracle_address, tx['block_index']) if oracle_fee >= config.DEFAULT_REGULAR_DUST_SIZE: if tx["destination"] != oracle_address or tx["btc_amount"] < oracle_fee: status = 'invalid: insufficient or non-existent oracle fee' if status == 'valid': # Create the new dispenser try: if dispenser_status == STATUS_OPEN_EMPTY_ADDRESS: cursor.execute('SELECT count(*) cnt FROM balances WHERE address=:address AND quantity > 0', { 'address': action_address }) counts = cursor.fetchall()[0] if counts['cnt'] == 0: util.debit(db, tx['source'], asset, escrow_quantity, action='open dispenser empty addr', event=tx['tx_hash']) util.credit(db, action_address, asset, escrow_quantity, action='open dispenser empty addr', event=tx['tx_hash']) util.debit(db, action_address, asset, escrow_quantity, action='open dispenser empty addr', event=tx['tx_hash']) elsM status = 'invalid: address not empty' else: util.debit(db, tx['source'], asset, escrow_quantity, action='open dispenser', event=tx['tx_hash']) except util.DebitError as e: status = 'invalid: insufficient funds' if status == 'valid': bindings = { 'tx_index': tx['tx_index'], M 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': action_address, 'asset': asset, 'give_quantity': give_quantity, 'escrow_quantity': escrow_quantity, 'satoshirate': mainchainrate, 'status': STATUS_OPEN, 'give_remaining': escrow_quantity, 'oraM cle_address': oracle_address } sql = 'insert into dispensers values(:tx_index, :tx_hash, :block_index, :source, :asset, :give_quantity, :escrow_quantity, :satoshirate, :status, :give_remaining, :oracle_address)' cursor.execute(sql, bindings) elif len(existing) == 1 and existing[0]['satoshirate'] == mainchainrate and existing[0]['give_quantity'] == give_quantity: if tx["source"]==action_address: if (oracle_address != None) and util.enabled('oracle_dispensers', tx['block_index']): oracle_fee = calculate_oracle_fee(db, escrow_quantity, give_quantity, mainchainrate, oracle_address, tx['block_index']) if oracle_fee >= config.DEFAULT_REGULAR_DUST_SIZE: if tx["destination"] != oracle_address or tx["btc_amount"] < oracle_fee: status = 'invalid: iM nsufficient or non-existent oracle fee' if status == 'valid': # Refill the dispenser by the given amount bindings = { 'source': tx['source'], 'asset': asset, 'prev_status': dispenser_status, 'give_remaining': existing[0]['give_remaining'] + escrow_quantity, M 'status': STATUS_OPEN, 'block_index': tx['block_index'], 'action':'refill dispenser', 'escrow_quantity':escrow_quantity } try: util.debit(db, tx['source'], asset, escrow_quantity, action='refill dispenser', event=tx['tx_hash']) sql = 'UPDATE dispensers SET give_remaining=:give_remaining \ WHERE source=:source AND asset=:asset AND status=:status' cursor.execute(sql, bindings) except (util.DebitError): status = 'insufficient funds' else: status = 'invalid: can only refill dispenser from source' else: status = 'can only have one open dispenser per asset per address' if dispenser_status == STATUS_CLOSED: cursor.execute('SELECT tx_index, give_remaining FROM dispensers WHERE source=:source AND asset=:asset AND status=:status', { 'source': tx['source'], 'asset': asset, 'status': STATUS_OPEN existing = cursor.fetchall() if len(existing) == 1: util.credit(db, tx['source'], asset, existing[0]['give_remaining'], action='close dispenser', eM bindings = { 'source': tx['source'], 'asset': asset, 'status': STATUS_CLOSED, 'block_index': tx['block_index'], 'tx_index': existing[0]['tx_index'] } sql = 'UPDATE dispensers SET give_remaining=0, status=:status WHERE source=:source AND asset=:asset' cursor.execute(sql, bindings) elsM status = 'dispenser inexistent' status = 'invalid: status must be one of OPEN or CLOSE' if status != 'valid': logger.warn("Not storing [dispenser] tx [%s]: %s" % (tx['tx_hash'], status)) def is_dispensable(db, address, amount): cursor = db.cursor() cursor.execute('SELECT * FROM dispensers WHERE source=:source AND status=:status', { 'source': address, 'status': STATUS_OPEN dispensers = cuM for next_dispenser in dispensers: if next_dispenser["oracle_address"] != None: last_price, last_fee, last_fiat_label, last_updated = util.get_oracle_last_price(db, next_dispenser['oracle_address'], util.CURRENT_BLOCK_INDEX) fiatrate = util.satoshirate_to_fiat(next_dispenser["satoshirate"]) if amount >= fiatrate/last_price: return True if amount >= next_dispenser["satoshirate"]: def dispense(db, tx): cursor = db.cursor() cursor.execute('SELECT * FROM dispensers WHERE source=:source AND status=:status ORDER BY asset', { 'source': tx['destination'], 'status': STATUS_OPEN dispensers = cursor.fetchall() dispense_index = 0 for dispenser in dispensers: satoshirate = dispenser['satoshirate'] give_quantity = dispenser['give_quantity'] if satoshirate > 0 and give_quantity > 0: if (dispenser['oracle_address'] != None) and util.enabled('oracle_dispensers', tx['block_index']): last_price, last_fee, last_fiat_label, last_updated = util.get_oracle_last_price(db, dispenser['oracle_address'], tx['block_index']) fiatrate = util.satoshirate_to_fiat(satoshirate) must_give = int(floor(((tx['btc_amount'] / config.UNIT) * last_price)/fiatrate)) must_give = int(floor(tx['btc_amount'] / satoshirate)) remaining = int(floor(dispenser['give_remaining'] / give_quantity)) actually_given = min(must_give, remaining) * give_quantity give_remaining = dispenser['give_remaining'] - actually_given assert give_remaining >= 0 # Skip dispense if quantity is 0 if util.enabled('zero_quantity_value_adjustment_1') and actually_given==0: continue util.credit(db, tx['source'], dispenser['asset'], actually_given, action='dispense', eveM dispenser['give_remaining'] = give_remaining if give_remaining < dispenser['give_quantity']: # close the dispenser dispenser['give_remaining'] = 0 if give_remaining > 0: # return the remaining to the owner util.credit(db, dispenser['source'], dispenser['asset'], give_remaining, action='dispenser close', event=tx['tx_hash']) dispenser['status'] = STATUS_CLOSED dispenser['block_index'] = tx['block_index'] dispenser['prev_status'] = STATUS_OPEN cursor.execute('UPDATE DISPENSERS SET give_remaining=:give_remaining, status=:status \ WHERE source=:source AND asset=:asset AND satoshirate=:satoshirate AND give_quantity=:give_quantity AND status=:prev_status', dispenser) bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'dispense_index': dispense_index, 'block_index': tx['block_index'], 'source': tx['destination'], 'destination': tx['source'], 'asset': dispenser['asset'], 'dispense_quantity': actually_given, 'dispenser_tx_hash': dispenser['tx_hash'] sql = 'INSERT INTO dispenses(tx_index, dispense_index, tx_hash, block_index, source, destination, asset, dispense_quantity, dispenser_tx_hash) \ VALUES(:tx_index, :dispense_index, :tM x_hash, :block_index, :source, :destination, :asset, :dispense_quantity, :dispenser_tx_hash);' cursor.execute(sql, bindings) dispense_index += 1 """Pay out dividends.""" logger = logging.getLogger(__name__) from counterpartylib.lib import (config, exceptions, util, message_type) def initialise (db): cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS dividends( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, asset TEXT, dividend_asset TEXT, quantity_per_unit INTEGER, fee_paid INTEGER, status TEXT, FOREIM GN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON dividends (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON dividends (source) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS asset_idx ON dividends (asset) ''') lidate (db, source, quantity_per_unit, asset, dividend_asset, block_index): cursor = db.cursor() if asset == config.BTC: problems.append('cannot pay dividends to holders of {}'.format(config.BTC)) if asset == config.XCP: if (not block_index >= 317500) or block_index >= 320000 or config.TESTNET or config.REGTEST: # Protocol change. problems.append('cannot pay dividends to holders of {}'.format(config.XCP)) if quantity_per_unit <= 0: positive quantity per unit') if quantity_per_unit > config.MAX_INT: problems.append('integer overflow') # Examine asset. issuances = list(cursor.execute('''SELECT * FROM issuances WHERE (status = ? AND asset = ?) ORDER BY tx_index ASC''', ('valid', asset))) if not issuances: problems.append('no such asset, {}.'.format(asset)) return None, None, problems, 0 divisible = issuances[0]['divisible'] # Only issuer can pay dividends. lock_index >= 320000 or config.TESTNET or config.REGTEST: # Protocol change. if issuances[-1]['issuer'] != source: problems.append('only issuer can pay dividends') # Examine dividend asset. if dividend_asset in (config.BTC, config.XCP): dividend_divisible = True issuances = list(cursor.execute('''SELECT * FROM issuances WHERE (status = ? AND asset = ?)''', ('valid', dividend_asset))) if not issuances: problems.append('no such dividend asseM t, {}.'.format(dividend_asset)) return None, None, problems, 0 dividend_divisible = issuances[0]['divisible'] # Calculate dividend quantities. exclude_empty = False if util.enabled('zero_quantity_value_adjustment_1'): exclude_empty = True holders = util.holders(db, asset, exclude_empty) dividend_total = 0 for holder in holders: if block_index < 294500 and not (config.TESTNET or config.REGTEST): # Protocol change. if holder['escrow']: continue address = holder['address'] address_quantity = holder['address_quantity'] if block_index >= 296000 or config.TESTNET or config.REGTEST: # Protocol change. if address == source: continue dividend_quantity = address_quantity * quantity_per_unit if divisible: dividend_quantity /= config.UNIT if not util.enabled('nondivisible_dividend_fix') and not dividend_divisible: dividend_quantity /= config.UNIT # Pre-fix behaviourM if dividend_asset == config.BTC and dividend_quantity < config.DEFAULT_MULTISIG_DUST_SIZE: continue # A bit hackish. dividend_quantity = int(dividend_quantity) outputs.append({'address': address, 'address_quantity': address_quantity, 'dividend_quantity': dividend_quantity}) addresses.append(address) dividend_total += dividend_quantity if not dividend_total: problems.append('zero dividend') if dividend_asset != config.BTC: dividend_balances = list(cursoM r.execute('''SELECT * FROM balances WHERE (address = ? AND asset = ?)''', (source, dividend_asset))) if not dividend_balances or dividend_balances[0]['quantity'] < dividend_total: problems.append('insufficient funds ({})'.format(dividend_asset)) if not problems and dividend_asset != config.BTC: holder_count = len(set(addresses)) if block_index >= 330000 or config.TESTNET or config.REGTEST: # Protocol change. fee = int(0.0002 * config.UNIT * holder_couM balances = list(cursor.execute('''SELECT * FROM balances WHERE (address = ? AND asset = ?)''', (source, config.XCP))) if not balances or balances[0]['quantity'] < fee: problems.append('insufficient funds ({})'.format(config.XCP)) if not problems and dividend_asset == config.XCP: total_cost = dividend_total + fee if not dividend_balances or dividend_balances[0]['quantity'] < total_cost: problems.append('insufficient funds ({M })'.format(dividend_asset)) if fee > config.MAX_INT or dividend_total > config.MAX_INT: problems.append('integer overflow') return dividend_total, outputs, problems, fee def compose (db, source, quantity_per_unit, asset, dividend_asset): # resolve subassets asset = util.resolve_subasset_longname(db, asset) dividend_asset = util.resolve_subasset_longname(db, dividend_asset) dividend_total, outputs, problems, fee = validate(db, source, quantityM _per_unit, asset, dividend_asset, util.CURRENT_BLOCK_INDEX) if problems: raise exceptions.ComposeError(problems) logger.info('Total quantity to be distributed in dividends: {} {}'.format(util.value_out(db, dividend_total, dividend_asset), dividend_asset)) if dividend_asset == config.BTC: return (source, [(output['address'], output['dividend_quantity']) for output in outputs], None) asset_id = util.get_asset_id(db, asset, util.CURRENT_BLOCK_INDEX) dividend_asset_id = util.get_asset_id(dM b, dividend_asset, util.CURRENT_BLOCK_INDEX) data = message_type.pack(ID) data += struct.pack(FORMAT_2, quantity_per_unit, asset_id, dividend_asset_id) return (source, [], data) def parse (db, tx, message): dividend_parse_cursor = db.cursor() # Unpack message. if (tx['block_index'] > 288150 or config.TESTNET or config.REGTEST) and len(message) == LENGTH_2: quantity_per_unit, asset_id, dividend_asset_id = struct.unpack(FORMAT_2, message) asset = util.getM _asset_name(db, asset_id, tx['block_index']) dividend_asset = util.get_asset_name(db, dividend_asset_id, tx['block_index']) status = 'valid' elif len(message) == LENGTH_1: quantity_per_unit, asset_id = struct.unpack(FORMAT_1, message) asset = util.get_asset_name(db, asset_id, tx['block_index']) dividend_asset = config.XCP status = 'valid' raise exceptions.UnpackError except (exceptions.UnpackError, exceptioM ns.AssetNameError, struct.error) as e: dividend_asset, quantity_per_unit, asset = None, None, None status = 'invalid: could not unpack' if dividend_asset == config.BTC: status = 'invalid: cannot pay {} dividends within protocol'.format(config.BTC) if status == 'valid': # For SQLite3 quantity_per_unit = min(quantity_per_unit, config.MAX_INT) dividend_total, outputs, problems, fee = validate(db, tx['source'], quantity_per_unit, asset, dividend_asset, block_inM dex=tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if status == 'valid': util.debit(db, tx['source'], dividend_asset, dividend_total, action='dividend', event=tx['tx_hash']) if tx['block_index'] >= 330000 or config.TESTNET or config.REGTEST: # Protocol change. util.debit(db, tx['source'], config.XCP, fee, action='dividend fee', event=tx['tx_hash']) for output in outputs: if not util.enabled('M dont_credit_zero_dividend') or output['dividend_quantity'] > 0: util.credit(db, output['address'], dividend_asset, output['dividend_quantity'], action='dividend', event=tx['tx_hash']) # Add parsed transaction to message-type 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': asset, 'dividend_asset': dividend_asset, 'quantity_per_unit': M 'fee_paid': fee, 'status': status, if "integer overflow" not in status: sql = 'insert into dividends values(:tx_index, :tx_hash, :block_index, :source, :asset, :dividend_asset, :quantity_per_unit, :fee_paid, :status)' dividend_parse_cursor.execute(sql, bindings) logger.warn("Not storing [dividend] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) dividend_parse_cursor.close() vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 Allow simultaneous lock and transfer. logger = logging.getLogger(__name__) from counterpartylib.lib import (config, util, exceptions, util, message_type) LENGTH_1 = 8 + 8 + 1 FORMAT_2 = '>QQ??If' LENGTH_2 = 8 + 8 + 1 + 1 + 4 + 4 SUBASSET_FORMAT = '>QQ?B' SUBASSET_FORMAT_LENGTH = 8 + 8 + 1 + 1 # NOTE: Pascal strings are used for storing descriptions for backwards cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS issuances( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, asset TEXT, quantity INTEGER, divisible BOOL, source TEXT, issuer TEXT, transfer BOOL, callable BOOL, call_date INTEGER, call_price REAL, description TEXT, fee_paid INTEGER, locked BOOL, status TEXT, asset_longname TEXT, reset BOOL, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) # Add asset_longname for sub-assets t do `ALTER TABLE IF COLUMN NOT EXISTS`. columns = [column['name'] for column in cursor.execute('''PRAGMA table_info(issuances)''')] if 'asset_longname' not in columns: cursor.execute('''ALTER TABLE issuances ADD COLUMN asset_longname TEXT''') if 'reset' not in columns: cursor.execute('''ALTER TABLE issuances ADD COLUMN reset BOOL''') # If sweep_hotfix activated, Create issuances copy, copy old data, drop old tabM le, rename new table, recreate indexes t do `ALTER TABLE IF COLUMN NOT EXISTS` nor can drop UNIQUE constraints if 'msg_index' not in columns: cursor.execute('''CREATE TABLE IF NOT EXISTS new_issuances( tx_index INTEGER, tx_hash TEXT, msg_index INTEGER DEFAULT 0, block_index INTEGER, asset TEXT, quantitM divisible BOOL, source TEXT, issuer TEXT, transfer BOOL, callable BOOL, call_date INTEGER, call_price REAL, description TEXT, fee_paid INTEGER, locked BOOL, status TEXT, asset_longname TEXT, reset BOOL, PRIMARY KEY (tx_index, msg_index), FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index), UNIQUE (tx_hash, msg_index)) ''') cursor.execute('''INSERT INTO new_issuances(tx_index, tx_hash, msg_index, block_index, asset, quantity, divisible, sourcM e, issuer, transfer, callable, call_date, call_price, description, fee_paid, locked, status, asset_longname, reset) SELECT tx_index, tx_hash, 0, block_index, asset, quantity, divisible, source, issuer, transfer, callable, call_date, call_price, description, fee_paid, locked, status, asset_longname, reset FROM issuances''', {}) cursor.execute('DROP TABLE issuances') cursor.execute('ALTER TABLE new_issuances RENAME TO issuances') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON issuances (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS valid_asset_idx ON issuances (asset, status) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS status_idx ON issuances (status) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON issuances (source) cursor.execute('''CREATE INDEX IF NOT EXISTS asset_longname_idx ON issuances (asset_longname) ''') def validate (db, source, destination, asset, quantity, divisible, lock, reset, callable_, call_date, call_price, description, subasset_parent, subasset_longname, block_index): if asset in (config.BTC, config.XCP): problems.append('cannot issue {} or {}'.format(config.BTC, config.XCP)) if call_date is None: cM if call_price is None: call_price = 0.0 if description is None: description = "" if divisible is None: divisible = True if lock is None: lock = False if reset is None: reset = False if isinstance(call_price, int): call_price = float(call_price) #^ helps especially with calls from JS based clients, where parseFloat(15) returns 15 (not 15.0), which json takes as an int if not isinstance(quantity, int): problems.append('quantity must be in satoshis') urn call_date, call_price, problems, fee, description, divisible, None, None if call_date and not isinstance(call_date, int): problems.append('call_date must be epoch integer') return call_date, call_price, problems, fee, description, divisible, None, None if call_price and not isinstance(call_price, float): problems.append('call_price must be a float') return call_date, call_price, problems, fee, description, divisible, None, None if quantity < 0: problems.append('negatM if call_price < 0: problems.append('negative call price') if call_date < 0: problems.append('negative call date') # Callable, or not. if not callable_: if block_index >= 312500 or config.TESTNET or config.REGTEST: # Protocol change. call_date = 0 call_price = 0.0 elif block_index >= 310000: # Protocol change. if call_date: problems.append('call date for non if call_priceM problems.append('call price for non # Valid re-issuance? cursor = db.cursor() cursor.execute('''SELECT * FROM issuances \ WHERE (status = ? AND asset = ?) ORDER BY tx_index ASC''', ('valid', asset)) issuances = cursor.fetchall() reissued_asset_longname = None reissuance = True last_issuance = issuances[-1] reissued_asset_longname = last_issuance['asset_loM issuance_locked = False if util.enabled('issuance_lock_fix'): for issuance in issuances: if issuance['locked']: issuance_locked = True break elif last_issuance['locked']: # before the issuance_lock_fix, only the last issuance was checked issuance_locked = True if last_issuance['issuer'] != source: problems.append('issued by another address') if (bool(last_issuanceM ['divisible']) != bool(divisible)) and ((not util.enabled("cip03", block_index)) or (not reset)): problems.append('cannot change divisibility') if bool(last_issuance['callable']) != bool(callable_): problems.append('cannot change callability') if last_issuance['call_date'] > call_date and (call_date != 0 or (block_index < 312500 and (not config.TESTNET or not config.REGTEST))): problems.append('cannot advance call date') if last_issuance['call_price'] > caM problems.append('cannot reduce call price') if issuance_locked and quantity: problems.append('locked asset and non if issuance_locked and reset: problems.append('cannot reset a locked asset') reissuance = False if description.lower() == 'lock': problems.append('cannot lock a non #if destination: # problems.append('cannot transfer a non problems.append('cannot reset a non existent asset') # validate parent ownership for subasset if subasset_longname is not None and not reissuance: cursor = db.cursor() cursor.execute('''SELECT * FROM issuances \ WHERE (status = ? AND asset = ?) ORDER BY tx_index ASC''', ('valid', subasset_parent)) parent_issuances = cursor.fetchall() cursor.close() if parent_issuances: last_parent_M issuance = parent_issuances[-1] if last_parent_issuance['issuer'] != source: problems.append('parent asset owned by another address') problems.append('parent asset not found') # validate subasset issuance is not a duplicate if subasset_longname is not None and not reissuance: cursor = db.cursor() cursor.execute('''SELECT * FROM assets \ WHERE (asset_longname = ?)''', (subasset_longname,)) assets = cursorM if len(assets) > 0: problems.append('subasset already exists') # validate that the actual asset is numeric if asset[0] != 'A': problems.append('a subasset must be a numeric asset') # Check for existence of fee funds. if quantity or (block_index >= 315000 or config.TESTNET or config.REGTEST): # Protocol change. if not reissuance or (block_index < 310000 and not config.TESTNET and not config.REGTEST): # Pay fee only upon first issuanceM . (Protocol change.) cursor = db.cursor() cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (source, config.XCP)) balances = cursor.fetchall() cursor.close() if util.enabled('numeric_asset_names'): # Protocol change. if subasset_longname is not None and util.enabled('subassets'): # Protocol change. # subasset issuance is 0.25 fee = int(0.25M elif len(asset) >= 13: fee = 0 else: fee = int(0.5 * config.UNIT) elif block_index >= 291700 or config.TESTNET or config.REGTEST: # Protocol change. fee = int(0.5 * config.UNIT) elif block_index >= 286000 or config.TESTNET or config.REGTEST: # Protocol change. fee = 5 * config.UNIT elif block_index > 281236 or config.TESTNET or config.REGTEST: # ProtocoM fee = 5 if fee and (not balances or balances[0]['quantity'] < fee): problems.append('insufficient funds') if not (block_index >= 317500 or config.TESTNET or config.REGTEST): # Protocol change. if len(description) > 42: problems.append('description too long') call_date = min(call_date, config.MAX_INT) assert isinstance(quantity, int) if reset and util.enabled("cip03", block_index):#reset will overwrite the M if quantity > config.MAX_INT: problems.append('total quantity overflow') total = sum([issuance['quantity'] for issuance in issuances]) if total + quantity > config.MAX_INT: problems.append('total quantity overflow') if util.enabled("cip03", block_index) and reset and issuances: cursor = db.cursor() #Checking that all supply are held by the owner of the asset cursor.execute('''SELECT * FROM balances \ WHERE asset = ? AND quantity > 0''', (asset,)) balances = cursor.fetchall() cursor.close() if (len(balances) == 0): if util.asset_supply(db, asset) > 0: problems.append('Cannot reset an asset with no holder') elif (len(balances) > 1): problems.append('Cannot reset an asset with many holders') elif (len(balances) == 1): if (balances[0]['address'] != last_issuance["issuer"]): problems.append(M 'Cannot reset an asset held by a different address than the owner') #if destination and quantity: # problems.append('cannot issue and transfer simultaneously') if util.enabled('integer_overflow_fix', block_index=block_index) and (fee > config.MAX_INT or quantity > config.MAX_INT): problems.append('integer overflow') return call_date, call_price, problems, fee, description, divisible, lock, reset, reissuance, reissued_asset_longname def compose (db, source, transfer_M destination, asset, quantity, divisible, lock, reset, description): # Callability is deprecated, so for re issuances set relevant parameters # to old values; for first issuances, make uncallable. cursor = db.cursor() cursor.execute('''SELECT * FROM issuances \ WHERE (status = ? AND asset = ?) ORDER BY tx_index ASC''', ('valid', asset)) issuances = cursor.fetchall() last_issuance = issuances[-1] callable_ = last_issuM call_date = last_issuance['call_date'] call_price = last_issuance['call_price'] callable_ = False call_date = 0 call_price = 0.0 # check subasset subasset_parent = None subasset_longname = None if util.enabled('subassets'): # Protocol change. subasset_parent, subasset_longname = util.parse_subasset_from_asset_name(asset) if subasset_longname is not None: # try to find an existing subassM sa_cursor = db.cursor() sa_cursor.execute('''SELECT * FROM assets \ WHERE (asset_longname = ?)''', (subasset_longname,)) assets = sa_cursor.fetchall() sa_cursor.close() if len(assets) > 0: # this is a reissuance asset = assets[0]['asset_name'] # this is a new issuance # generate a random numeric asset id which will map to this subasset asset = util.generate_random_asset() call_date, call_price, problems, fee, description, divisible, lock, reset, reissuance, reissued_asset_longname = validate(db, source, transfer_destination, asset, quantity, divisible, lock, reset, callable_, call_date, call_price, description, subasset_parent, subasset_longname, util.CURRENT_BLOCK_INDEX) if problems: raise exceptions.ComposeError(problems) asset_id = util.generate_asset_id(asset, util.CURRENT_BLOCK_INDEX) if subasset_longname is NoneM asset_format = util.get_value_by_block_index("issuance_asset_serialization_format") asset_format_length = util.get_value_by_block_index("issuance_asset_serialization_length") # Type 20 standard issuance FORMAT_2 >QQ??If # used for standard issuances and all reissuances data = message_type.pack(ID) if (len(description) <= 42) and not util.enabled('pascal_string_removed'): curr_format = FORMAT_2 + '{}p'.format(len(description) + 1)M curr_format = asset_format + '{}s'.format(len(description)) if (asset_format_length <= 19):# callbacks parameters were removed data += struct.pack(curr_format, asset_id, quantity, 1 if divisible else 0, 1 if lock else 0, 1 if reset else 0, description.encode('utf-8')) elif (asset_format_length <= 26): data += struct.pack(curr_format, asset_id, quantity, 1 if divisible else 0, 1 if callable_ else 0, call_date or 0, call_priceM or 0.0, description.encode('utf-8')) elif (asset_format_length <= 27):# param reset was inserted data += struct.pack(curr_format, asset_id, quantity, 1 if divisible else 0, 1 if reset else 0, 1 if callable_ else 0, call_date or 0, call_price or 0.0, description.encode('utf-8')) elif (asset_format_length <= 28):# param lock was inserted data += struct.pack(curr_format, asset_id, quantity, 1 if divisible else 0, 1 if lock else 0, 1 if reset else 0, 1 if callablM call_date or 0, call_price or 0.0, description.encode('utf-8')) subasset_format = util.get_value_by_block_index("issuance_subasset_serialization_format",util.CURRENT_BLOCK_INDEX) subasset_format_length = util.get_value_by_block_index("issuance_subasset_serialization_length",util.CURRENT_BLOCK_INDEX) # Type 21 subasset issuance SUBASSET_FORMAT >QQ?B # Used only for initial subasset issuance # compacts a subasset name to save space compacted_subasset_longname = util.compact_subasset_longname(subasset_longname) compacted_subasset_length = len(compacted_subasset_longname) data = message_type.pack(SUBASSET_ID) curr_format = subasset_format + '{}s'.format(compacted_subasset_length) + '{}s'.format(len(description)) if subasset_format_length <= 18: data += struct.pack(curr_format, asset_id, quantity, 1 if divisible else 0, compacted_subasset_length, compacted_subasset_longname, description.encoM elif subasset_format_length <= 19:# param reset was inserted data += struct.pack(curr_format, asset_id, quantity, 1 if divisible else 0, 1 if reset else 0, compacted_subasset_length, compacted_subasset_longname, description.encode('utf-8')) elif subasset_format_length <= 20:# param lock was inserted data += struct.pack(curr_format, asset_id, quantity, 1 if divisible else 0, 1 if lock else 0, 1 if reset else 0, compacted_subasset_length, compacted_subasset_longnaM me, description.encode('utf-8')) if transfer_destination: destination_outputs = [(transfer_destination, None)] destination_outputs = [] return (source, destination_outputs, data) def parse (db, tx, message, message_type_id): issuance_parse_cursor = db.cursor() asset_format = util.get_value_by_block_index("issuance_asset_serialization_format",tx['block_index']) asset_format_length = util.get_value_by_block_index("issuance_asset_serialization_length",tx['block_index']) subasset_format = util.get_value_by_block_index("issuance_subasset_serialization_format",tx['block_index']) subasset_format_length = util.get_value_by_block_index("issuance_subasset_serialization_length",tx['block_index']) # Unpack message. subasset_longname = None if message_type_id == SUBASSET_ID: if not util.enabled('subassets', block_index=tx['block_index']): logger.warn("subassets are not enabled at block %s" % tx['block_index']) raise exceptions.UnpackError # parse a subasset original issuance message lock = None reset = None if subasset_format_length <= 18: asset_id, quantity, divisible, compacted_subasset_length = struct.unpack(subasset_format, message[0:subasset_format_length]) elif subasset_format_length <= 19:# param reset was inserted asset_id, quantity, divisible, reset, compacted_subasset_length = struct.unpack(subasset_formatM , message[0:subasset_format_length]) elif subasset_format_length <= 20:# param lock was inserted asset_id, quantity, divisible, lock, reset, compacted_subasset_length = struct.unpack(subasset_format, message[0:subasset_format_length]) description_length = len(message) - subasset_format_length - compacted_subasset_length if description_length < 0: logger.warn("invalid subasset length: [issuance] tx [%s]: %s" % (tx['tx_haM sh'], compacted_subasset_length)) raise exceptions.UnpackError messages_format = '>{}s{}s'.format(compacted_subasset_length, description_length) compacted_subasset_longname, description = struct.unpack(messages_format, message[subasset_format_length:]) subasset_longname = util.expand_subasset_longname(compacted_subasset_longname) callable_, call_date, call_price = False, 0, 0.0 description = description.decode('utf-8') except UnicodeDecodeError: description = '' elif (tx['block_index'] > 283271 or config.TESTNET or config.REGTEST) and len(message) >= asset_format_length: # Protocol change. if (len(message) - asset_format_length <= 42) and not util.enabled('pascal_string_removed'): curr_format = asset_format + '{}p'.format(len(message) - asset_format_length) curr_format = asset_format + '{}s'.format(len(message) - asset_format_length)M lock = None reset = None if (asset_format_length <= 19):# callbacks parameters were removed asset_id, quantity, divisible, lock, reset, description = struct.unpack(curr_format, message) callable_, call_date, call_price = False, 0, 0.0 elif (asset_format_length <= 26):#the reset param didn't even exist asset_id, quantity, divisible, callable_, call_date, call_price, description = struct.unpack(curr_format, mM elif (asset_format_length <= 27):# param reset was inserted asset_id, quantity, divisible, reset, callable_, call_date, call_price, description = struct.unpack(curr_format, message) elif (asset_format_length <= 28):# param lock was inserted asset_id, quantity, divisible, lock, reset, callable_, call_date, call_price, description = struct.unpack(curr_format, message) call_price = round(call_price, 6) # TODO: arbitrary description = description.decode('utf-8') except UnicodeDecodeError: description = '' if len(message) != LENGTH_1: raise exceptions.UnpackError asset_id, quantity, divisible = struct.unpack(FORMAT_1, message) lock, reset, callable_, call_date, call_price, description = False, False, False, 0, 0.0, '' asset = util.generate_asset_name(asset_id, tx['block_index']) ##This is for backwards compatibility with assets names longer than 12 characters if asset.startswith('A'): namedAsset = util.get_asset_name(db, asset_id, tx['block_index']) if (namedAsset != 0): asset = namedAsset status = 'valid' except exceptions.AssetIDError: asset = None status = 'invalid: bad asset name' except exceptions.UnpackError as e: et, quantity, divisible, lock, reset, callable_, call_date, call_price, description = None, None, None, None, None, None, None, None, None status = 'invalid: could not unpack' # parse and validate the subasset from the message subasset_parent = None if status == 'valid' and subasset_longname is not None: # Protocol change. # ensure the subasset_longname is valid util.validate_subasset_longname(subasset_longname) subasset_parent, subasset_longnameM = util.parse_subasset_from_asset_name(subasset_longname) except exceptions.AssetNameError as e: asset = None status = 'invalid: bad subasset name' reissuance = None if status == 'valid': call_date, call_price, problems, fee, description, divisible, lock, reset, reissuance, reissued_asset_longname = validate(db, tx['source'], tx['destination'], asset, quantity, divisible, lock, reset, callable_, call_date, call_price, description, subasset_parent, subasseM t_longname, block_index=tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if not util.enabled('integer_overflow_fix', block_index=tx['block_index']) and 'total quantity overflow' in problems: quantity = 0 if (status == 'valid') and reset and util.enabled("cip03", tx['block_index']): balances_cursor = issuance_parse_cursor.execute('''SELECT * FROM balances WHERE asset = ? AND quantity > 0''', (asset,)) balances_result = balances_M if len(balances_result) <= 1: if len(balances_result) == 0: issuances_cursor = issuance_parse_cursor.execute('''SELECT * FROM issuances WHERE asset = ? ORDER BY tx_index DESC''', (asset,)) issuances_result = issuances_cursor.fetchall() owner_balance = 0 owner_address = issuances_result[0]['issuer'] owner_balance = balances_result[0]["quantity"] owner_address = balances_result[0]["address"] if owner_address == tx['source']: if owner_balance > 0: util.debit(db, tx['source'], asset, owner_balance, 'reset destroy', tx['tx_hash']) bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': asset, 'quantity': owner_balance, 'tag': "reset", 'status': "valid", 'reset': True, } sql = 'insert into destructions values(:tx_index, :tx_hash, :block_index, :source, :asset, :quantity, :tag, :status)' issuance_parse_cursor.execute(sql, bindings) bindings= { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'asset': asset, 'quantity': quantity, 'divisible': divisible, 'source': tx['source'], 'issuer': tx['source'], 'transfer': False, 'callable': callable_, 'call_date': call_date, 'call_price': call_price, 'description': descM 'fee_paid': 0, 'locked': lock, 'status': status, 'reset': True, 'asset_longname': reissued_asset_longname, sql='insert into issuances values(:tx_index, :tx_hash, 0, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status, :asset_longname, :reset)' issuM ance_parse_cursor.execute(sql, bindings) # Credit. if quantity: util.credit(db, tx['source'], asset, quantity, action="reset issuance", event=tx['tx_hash']) if tx['destination']: issuer = tx['destination'] transfer = True #quantity = 0 issuer = tx['source'] transfer = False # Debit fee. if status == 'valid': util.debit(db, tx['sourM ce'], config.XCP, fee, action="issuance fee", event=tx['tx_hash']) if not isinstance(lock,bool): lock = False if status == 'valid': if (description and description.lower() == 'lock') or lock: lock = True cursor = db.cursor() issuances = list(cursor.execute('''SELECT * FROM issuances \ WHERE (status = ? AND asset = ?) M ORDER BY tx_index ASC''', ('valid', asset))) cursor.close() if len(issuances) > 0: description = issuances[-1]['description'] # Use last description if not reissuance: # Add to table of assets. bindings= { 'asset_id': str(asset_id), 'asset_name': str(asset), 'block_index': tx['block_index'], 'asset_longname': subasset_longname, sql='insert into assets values(:asset_id, :asset_name, :block_index, :asset_longname)' issuance_parse_cursor.execute(sql, bindings) if status == 'valid' and reissuance: # when reissuing, add the asset_longname to the issuances table for API lookups asset_longname = reissued_asset_longname asset_longname = subasset_longname # Add parsed transaction to message-type 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'asset': asset, 'quantity': quantity, 'divisible': divisible, 'source': tx['source'], 'issuer': issuer, 'transfer': transfer, 'callable': callable_, 'call_date': call_date, 'call_price': call_price, 'description': description, 'fee_paid': fee, 'locked': loM 'reset': reset, 'status': status, 'asset_longname': asset_longname, if "integer overflow" not in status: sql='insert into issuances values(:tx_index, :tx_hash, 0, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status, :asset_longname, :reset)' issuance_parse_cursor.execute(sql, bindings) logger.warn("Not storing M [issuance] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) if status == 'valid' and quantity: util.credit(db, tx['source'], asset, quantity, action="issuance", event=tx['tx_hash']) issuance_parse_cursor.close() # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # Filled orders may not be re opened, so only orders not involving BTC (and so # which cannot haveM expired order matches) may be filled. logger = logging.getLogger(__name__) from counterpartylib.lib import config from counterpartylib.lib import exceptions from counterpartylib.lib import util from counterpartylib.lib import backend from counterpartylib.lib import log from counterpartylib.lib import message_type LENGTH = 8 + 8 + 8 + 8 + 2 + 8 cursor = db.cursor() or.execute('''CREATE TABLE IF NOT EXISTS orders( tx_index INTEGER UNIQUE, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, give_asset TEXT, give_quantity INTEGER, give_remaining INTEGER, get_asset TEXT, get_quantity INTEGER, get_remaining INTEGER, expiration INTEGER, expire_index INTEGER, fee_required INTEGER, fee_required_remaining INTEGER, fee_provided INTEGER, fee_provided_remaining INTEGER, status TEXT, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index), PRIMARY KEY (tx_index, tx_hash)) ''') cursor.execute('''CREATE INDEX IF NOT EXISM block_index_idx ON orders (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS index_hash_idx ON orders (tx_index, tx_hash) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS expire_idx ON orders (expire_index, status) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS give_status_idx ON orders (give_asset, status) ''') .execute('''CREATE INDEX IF NOT EXISTS source_give_status_idx ON orders (source, give_asset, status) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS give_get_status_idx ON orders (get_asset, give_asset, status) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON orders (source) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS give_asset_idx ON orM ''') cursor.execute('''CREATE TABLE IF NOT EXISTS order_matches( id TEXT PRIMARY KEY, tx0_index INTEGER, tx0_hash TEXT, tx0_address TEXT, tx1_index INTEGER, tx1_hash TEXT, tx1_address TEXT, forward_asset TEXT, forward_quantity INTEGER, baM backward_quantity INTEGER, tx0_block_index INTEGER, tx1_block_index INTEGER, block_index INTEGER, tx0_expiration INTEGER, tx1_expiration INTEGER, match_expire_index INTEGER, fee_paid INTEGER, status TEXT, FOREIGN KEY (tx0_index, tx0_hash, tx0_block_index) REFERENCES transactions(tM x_index, tx_hash, block_index), FOREIGN KEY (tx1_index, tx1_hash, tx1_block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS match_expire_idx ON order_matches (status, match_expire_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS forward_status_idx ON order_matches (forward_asset, status) ''') cursor.execute(''M 'CREATE INDEX IF NOT EXISTS backward_status_idx ON order_matches (backward_asset, status) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS id_idx ON order_matches (id) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx0_address_idx ON order_matches (tx0_address) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx1_address_idx ON order_matches (tx1_address) ''') # Order Expirations cursor.execute('''CREATE TABLE IF NOT EXISTS order_expirations( order_index INTEGER PRIMARY KEY, order_hash TEXT UNIQUE, source TEXT, block_index INTEGER, FOREIGN KEY (block_index) REFERENCES blocks(block_index), FOREIGN KEY (order_index, order_hash) REFERENCES orders(tx_index, tx_hash)) ''') cursor.execute('''M CREATE INDEX IF NOT EXISTS block_index_idx ON order_expirations (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON order_expirations (source) ''') # Order Match Expirations cursor.execute('''CREATE TABLE IF NOT EXISTS order_match_expirations( order_match_id TEXT PRIMARY KEY, tx0_address TEXT, tx1_address TEXT, M block_index INTEGER, FOREIGN KEY (order_match_id) REFERENCES order_matches(id), FOREIGN KEY (block_index) REFERENCES blocks(block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON order_match_expirations (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx0_address_idx ON order_match_expirations (tx0_address) ''') or.execute('''CREATE INDEX IF NOT EXISTS tx1_address_idx ON order_match_expirations (tx1_address) ''') def exact_penalty (db, address, block_index, order_match_id): # Penalize addresses that don t make BTC payments. If an address lets an # order match expire, expire sell BTC orders from that address. cursor = db.cursor() bad_orders = list(cursor.execute('''SELECT * FROM orders \ WHERE (source = ? AND gM ive_asset = ? AND status = ?)''', (address, config.BTC, 'open'))) for bad_order in bad_orders: cancel_order(db, bad_order, 'expired', block_index) if not (block_index >= 314250 or config.TESTNET or config.REGTEST): # Protocol change. # Order matches. bad_order_matches = list(cursor.execute('''SELECT * FROM order_matches \ WHERE ((tx0_address = ? AND forward_asset = ?) OR (tx1_address = ? AND baM ckward_asset = ?)) AND (status = ?)''', (address, config.BTC, address, config.BTC, 'pending'))) for bad_order_match in bad_order_matches: cancel_order_match(db, bad_order_match, 'expired', block_index) def cancel_order (db, order, status, block_index): cursor = db.cursor() # Update status of order. 'status': status, 'tx_hash': order['tx_hash'] sql='update orders set statuM s = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'orders', bindings) if order['give_asset'] != config.BTC: # Can util.credit(db, order['source'], order['give_asset'], order['give_remaining'], action='cancel order', event=order['tx_hash']) if status == 'expired': # Record offer expiration. bindings = { 'order_index': order['tx_index'], 'order_hash': order['tx_hash'], 'source': order['source'], 'block_index': block_index sql='insert into order_expirations values(:order_index, :order_hash, :source, :block_index)' cursor.execute(sql, bindings) def cancel_order_match (db, order_match, status, block_index): '''The only cancelling is an expiration. cursor = db.cursor() # Skip order matches just expired as a penalty. (Not very efficient.) if not (block_index >= 314250 or config.TESTNET or config.RM EGTEST): # Protocol change. order_matches = list(cursor.execute('''SELECT * FROM order_matches \ WHERE (id = ? AND status = ?)''', (order_match['id'], 'expired'))) if order_matches: cursor.close() # Update status of order match. 'status': status, 'order_match_id': order_match['id'] sql='update order_matches set status = :status whM ere id = :order_match_id' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'order_matches', bindings) order_match_id = util.make_id(order_match['tx0_hash'], order_match['tx1_hash']) # If tx0 is dead, credit address directly; if not, replenish give remaining, get remaining, and fee required remaining. orders = list(cursor.execute('''SELECT * FROM orders \ WHERE tx_index = ?''', (order_match['tx0_index'],)))M assert len(orders) == 1 tx0_order = orders[0] if tx0_order['status'] in ('expired', 'cancelled'): tx0_order_status = tx0_order['status'] if order_match['forward_asset'] != config.BTC: util.credit(db, order_match['tx0_address'], order_match['forward_asset'], order_match['forward_quantity'], action='order {}'.format(tx0_order_status), event=order_match['id']) tx0_give_remaining = tx0_order['give_remaining'] + orM der_match['forward_quantity'] tx0_get_remaining = tx0_order['get_remaining'] + order_match['backward_quantity'] if tx0_order['get_asset'] == config.BTC and (block_index >= 297000 or config.TESTNET or config.REGTEST): # Protocol change. tx0_fee_required_remaining = tx0_order['fee_required_remaining'] + order_match['fee_paid'] tx0_fee_required_remaining = tx0_order['fee_required_remaining'] tx0_order_status = tx0_order['status'] _order_status == 'filled' and util.enabled("reopen_order_when_btcpay_expires_fix", block_index)): #This case could happen if a BTCpay expires and before the expiration, the order was filled by a correct BTCpay tx0_order_status = 'open' # So, we have to open the order again bindings = { 'give_remaining': tx0_give_remaining, 'get_remaining': tx0_get_remaining, 'status': tx0_order_status, 'fee_required_remaining': tx0_fee_required_reM 'tx_hash': order_match['tx0_hash'] sql='update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'orders', bindings) # If tx1 is dead, credit address directly; if not, replenish give remaining, get remaining, and fee required remaining. orders = list(cursor.execute('M ''SELECT * FROM orders \ WHERE tx_index = ?''', (order_match['tx1_index'],))) assert len(orders) == 1 tx1_order = orders[0] if tx1_order['status'] in ('expired', 'cancelled'): tx1_order_status = tx1_order['status'] if order_match['backward_asset'] != config.BTC: util.credit(db, order_match['tx1_address'], order_match['backward_asset'], order_match['backward_quaM ntity'], action='order {}'.format(tx1_order_status), event=order_match['id']) tx1_give_remaining = tx1_order['give_remaining'] + order_match['backward_quantity'] tx1_get_remaining = tx1_order['get_remaining'] + order_match['forward_quantity'] if tx1_order['get_asset'] == config.BTC and (block_index >= 297000 or config.TESTNET or config.REGTEST): # Protocol change. tx1_fee_required_remaining = tx1_order['fee_required_remaining'] + order_match['fee_paid'] tx1_fee_required_remaining = tx1_order['fee_required_remaining'] tx1_order_status = tx1_order['status'] if (tx1_order_status == 'filled' and util.enabled("reopen_order_when_btcpay_expires_fix", block_index)): #This case could happen if a BTCpay expires and before the expiration, the order was filled by a correct BTCpay tx1_order_status = 'open' # So, we have to open the order again bindings = { 'give_remaining': tx1_give_remaining, et_remaining': tx1_get_remaining, 'status': tx1_order_status, 'fee_required_remaining': tx1_fee_required_remaining, 'tx_hash': order_match['tx1_hash'] sql='update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'orders', bindings) if block_index < 286500M : # Protocol change. # Sanity check: one of the two must have expired. tx0_order_time_left = tx0_order['expire_index'] - block_index tx1_order_time_left = tx1_order['expire_index'] - block_index assert tx0_order_time_left or tx1_order_time_left # Penalize tardiness. if block_index >= 313900 or config.TESTNET or config.REGTEST: # Protocol change. if tx0_order['status'] == 'expired' and order_match['forward_asset'] == config.BTC: exact_penalty(db, orderM _match['tx0_address'], block_index, order_match['id']) if tx1_order['status'] == 'expired' and order_match['backward_asset'] == config.BTC: exact_penalty(db, order_match['tx1_address'], block_index, order_match['id']) if block_index >= 310000 or config.TESTNET or config.REGTEST: # Protocol change. if not (block_index >= 315000 or config.TESTNET or config.REGTEST): # Protocol change. cursor.execute('''SELECT * FROM transactions\ M WHERE tx_hash = ?''', (tx0_order['tx_hash'],)) match(db, list(cursor)[0], block_index) cursor.execute('''SELECT * FROM transactions\ WHERE tx_hash = ?''', (tx1_order['tx_hash'],)) match(db, list(cursor)[0], block_index) if status == 'expired': # Record order match expiration. bindings = { 'order_match_id': order_match['id'], 'tx0_address': order_match['tx0_address'], 'tx1_address': orderM _match['tx1_address'], 'block_index': block_index sql='insert into order_match_expirations values(:order_match_id, :tx0_address, :tx1_address, :block_index)' cursor.execute(sql, bindings) def validate (db, source, give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required, block_index): cursor = db.cursor() if give_quantity > config.MAX_INT or get_quantity > config.MAX_INT or fee_required > conM fig.MAX_INT or block_index + expiration > config.MAX_INT: problems.append('integer overflow') if give_asset == config.BTC and get_asset == config.BTC: problems.append('cannot trade {} for itself'.format(config.BTC)) if not isinstance(give_quantity, int): problems.append('give_quantity must be in satoshis') return problems if not isinstance(get_quantity, int): problems.append('get_quantity must be in satoshis') return problems if not isinstance(fee_reM problems.append('fee_required must be in satoshis') return problems if not isinstance(expiration, int): problems.append('expiration must be expressed as an integer block delta') return problems if give_quantity <= 0: problems.append('non positive give quantity') if get_quantity <= 0: problems.append('non positive get quantity') if fee_required < 0: problems.append('negative fee_required') if expiration < 0: problems.append('negative expiration')M if expiration == 0 and not (block_index >= 317500 or config.TESTNET or config.REGTEST): # Protocol change. problems.append('zero expiration') if not give_quantity or not get_quantity: problems.append('zero give or zero get') cursor.execute('select * from issuances where (status = ? and asset = ?)', ('valid', give_asset)) if give_asset not in (config.BTC, config.XCP) and not cursor.fetchall(): problems.append('no such asset to give ({})'.format(give_asset)) ute('select * from issuances where (status = ? and asset = ?)', ('valid', get_asset)) if get_asset not in (config.BTC, config.XCP) and not cursor.fetchall(): problems.append('no such asset to get ({})'.format(get_asset)) if expiration > config.MAX_EXPIRATION: problems.append('expiration overflow') def compose (db, source, give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required): cursor = db.cursor() # resolve subassets give_asset = util.resolve_subasset_longname(db, give_asset) get_asset = util.resolve_subasset_longname(db, get_asset) # Check balance. if give_asset != config.BTC: balances = list(cursor.execute('''SELECT * FROM balances WHERE (address = ? AND asset = ?)''', (source, give_asset))) if (not balances or balances[0]['quantity'] < give_quantity): raise exceptions.ComposeError('insufficient funds') problems = validate(db, source, give_asset, give_quantity, get_asset, get_quM antity, expiration, fee_required, util.CURRENT_BLOCK_INDEX) if problems: raise exceptions.ComposeError(problems) give_id = util.get_asset_id(db, give_asset, util.CURRENT_BLOCK_INDEX) get_id = util.get_asset_id(db, get_asset, util.CURRENT_BLOCK_INDEX) data = message_type.pack(ID) data += struct.pack(FORMAT, give_id, give_quantity, get_id, get_quantity, expiration, fee_required) return (source, [], data) def parse (db, tx, message): cursor = db.cursor() # Unpack message. if len(message) != LENGTH: raise exceptions.UnpackError give_id, give_quantity, get_id, get_quantity, expiration, fee_required = struct.unpack(FORMAT, message) give_asset = util.get_asset_name(db, give_id, tx['block_index']) get_asset = util.get_asset_name(db, get_id, tx['block_index']) status = 'open' except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: give_asset, give_quaM ntity, get_asset, get_quantity, expiration, fee_required = 0, 0, 0, 0, 0, 0 status = 'invalid: could not unpack' if status == 'open': price = util.price(get_quantity, give_quantity) except ZeroDivisionError: price = 0 order_parse_cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], give_asset)) balances = list(order_parse_cursorM if give_asset != config.BTC: if not balances: give_quantity = 0 balance = balances[0]['quantity'] if balance < give_quantity: give_quantity = balance get_quantity = int(price * give_quantity) problems = validate(db, tx['source'], give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(prM if util.enabled('btc_order_minimum'): min_btc_quantity = 0.001 * config.UNIT # 0.001 BTC if util.enabled('btc_order_minimum_adjustment_1'): min_btc_quantity = 0.00001 * config.UNIT # 0.00001 BTC if (give_asset == config.BTC and give_quantity < min_btc_quantity) or (get_asset == config.BTC and get_quantity < min_btc_quantity): if problems: status += '; btc order below minimum' else: status = 'invalid: btc order below minimum' # Debit give quantity. (Escrow.) if status == 'open': if give_asset != config.BTC: # No need (or way) to debit BTC. util.debit(db, tx['source'], give_asset, give_quantity, action='open order', event=tx['tx_hash']) # Add parsed transaction to message-type 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source']M 'give_asset': give_asset, 'give_quantity': give_quantity, 'give_remaining': give_quantity, 'get_asset': get_asset, 'get_quantity': get_quantity, 'get_remaining': get_quantity, 'expiration': expiration, 'expire_index': tx['block_index'] + expiration, 'fee_required': fee_required, 'fee_required_remaining': fee_required, 'fee_provided': tx['fee'], 'fee_provided_remaining': tx['fee'], 'status': status, "integer overflow" not in status: sql = 'insert into orders values(:tx_index, :tx_hash, :block_index, :source, :give_asset, :give_quantity, :give_remaining, :get_asset, :get_quantity, :get_remaining, :expiration, :expire_index, :fee_required, :fee_required_remaining, :fee_provided, :fee_provided_remaining, :status)' order_parse_cursor.execute(sql, bindings) logger.warn("Not storing [order] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(biM if status == 'open' and tx['block_index'] != config.MEMPOOL_BLOCK_INDEX: match(db, tx) order_parse_cursor.close() def match (db, tx, block_index=None): cursor = db.cursor() # Get order in question. orders = list(cursor.execute('''SELECT * FROM orders\ WHERE (tx_index = ? AND status = ?)''', (tx['tx_index'], 'open'))) cursor.close() assert len(orders) == 1 cursor.execute('''SELECT * FROM orders \ WHERE (give_asset=? AND get_asset=? AND status=? AND tx_hash != ?)''', (tx1['get_asset'], tx1['give_asset'], 'open', tx1['tx_hash'])) tx1_give_remaining = tx1['give_remaining'] tx1_get_remaining = tx1['get_remaining'] order_matches = cursor.fetchall() if tx['block_index'] > 284500 or config.TESTNET or config.REGTEST: # Protocol change. order_matches = sorted(order_matches, key=lambda x: x['tM x_index']) # Sort by tx index second. order_matches = sorted(order_matches, key=lambda x: util.price(x['get_quantity'], x['give_quantity'])) # Sort by price first. # Get fee remaining. tx1_fee_required_remaining = tx1['fee_required_remaining'] tx1_fee_provided_remaining = tx1['fee_provided_remaining'] tx1_status = tx1['status'] for tx0 in order_matches: order_match_id = util.make_id(tx0['tx_hash'], tx1['tx_hash']) if not block_index: block_index = max(tx0['block_index'], tx1['block_index']) if tx1_status != 'open': break logger.debug('Considering: ' + tx0['tx_hash']) tx0_give_remaining = tx0['give_remaining'] tx0_get_remaining = tx0['get_remaining'] # Ignore previous matches. (Both directions, just to be sure.) cursor.execute('''SELECT * FROM order_matches WHERE id = ? ''', (util.make_id(tx0['tx_hash'], tx1['tx_hash']), )) if list(cursor): ger.debug('Skipping: previous match') continue cursor.execute('''SELECT * FROM order_matches WHERE id = ? ''', (util.make_id(tx1['tx_hash'], tx0['tx_hash']), )) if list(cursor): logger.debug('Skipping: previous match') continue # Get fee provided remaining. tx0_fee_required_remaining = tx0['fee_required_remaining'] tx0_fee_provided_remaining = tx0['fee_provided_remaining'] # Make sure that that both orderM s still have funds remaining (if order involves BTC, and so cannot be if tx0['give_asset'] == config.BTC or tx0['get_asset'] == config.BTC: # Gratuitous if tx0_give_remaining <= 0 or tx1_give_remaining <= 0: logger.debug('Skipping: negative give quantity remaining') continue if block_index >= 292000 and block_index <= 310500 and not config.TESTNET or config.REGTEST: # Protocol changes if tx0_get_remaining <= 0 or tx1_getM logger.debug('Skipping: negative get quantity remaining') continue if block_index >= 294000 or config.TESTNET or config.REGTEST: # Protocol change. if tx0['fee_required_remaining'] < 0: logger.debug('Skipping: negative tx0 fee required remaining') continue if tx0['fee_provided_remaining'] < 0: logger.debug('Skipping: negative tx0 fee provided remaining') continue if tx1_fee_provided_remaining < 0: logger.debug('Skipping: negative tx1 fee provided remaining') continue if tx1_fee_required_remaining < 0: logger.debug('Skipping: negative tx1 fee required remaining') continue # If the prices agree, make the trade. The found order sets the price, # and they trade as much as they can. tx0_price = util.price(tx0['get_qM uantity'], tx0['give_quantity']) tx1_price = util.price(tx1['get_quantity'], tx1['give_quantity']) tx1_inverse_price = util.price(tx1['give_quantity'], tx1['get_quantity']) # Protocol change. if tx['block_index'] < 286000: tx1_inverse_price = util.price(1, tx1_price) logger.debug('Tx0 Price: {}; Tx1 Inverse Price: {}'.format(float(tx0_price), float(tx1_inverse_price))) if tx0_price > tx1_inverse_price: logger.debug('Skipping: price mismatch.') logger.debug('Potential forward quantities: {}, {}'.format(tx0_give_remaining, int(util.price(tx1_give_remaining, tx0_price)))) forward_quantity = int(min(tx0_give_remaining, int(util.price(tx1_give_remaining, tx0_price)))) logger.debug('Forward Quantity: {}'.format(forward_quantity)) backward_quantity = round(forward_quantity * tx0_price) logger.debug('Backward Quantity: {}'.format(backward_quantity)) if not forward_quantity: logger.debug('Skipping: zero forward quantity.') continue if block_index >= 286500 or config.TESTNET or config.REGTEST: # Protocol change. if not backward_quantity: logger.debug('Skipping: zero backward quantity.') continue forward_asset, backward_asset = tx1['get_asset'], tx1['give_asset'] if block_index >= 313900 or config.TESTNET or config.REGTEST: # Protocol change. min_btc_quantiM ty = 0.001 * config.UNIT # 0.001 BTC if (forward_asset == config.BTC and forward_quantity <= min_btc_quantity) or (backward_asset == config.BTC and backward_quantity <= min_btc_quantity): logger.debug('Skipping: below minimum {} quantity'.format(config.BTC)) continue # Check and update fee remainings. if block_index >= 286500 or config.TESTNET or config.REGTEST: # Protocol change. Deduct fee_required from provided_M remaining, etc., if possible (else don if tx1['get_asset'] == config.BTC: if block_index >= 310500 or config.TESTNET or config.REGTEST: # Protocol change. fee = int(tx1['fee_required'] * util.price(backward_quantity, tx1['give_quantity'])) else: fee = int(tx1['fee_required_remaining'] * util.price(forward_quantity, tx1_get_remaining)) logger.debug('Tx0 fee provided remaining:M {}; required fee: {}'.format(tx0_fee_provided_remaining / config.UNIT, fee / config.UNIT)) if tx0_fee_provided_remaining < fee: logger.debug('Skipping: tx0 fee provided remaining is too low.') continue else: tx0_fee_provided_remaining -= fee if block_index >= 287800 or config.TESTNET or config.REGTEST: # Protocol change. tx1_fee_required_remaining -= fM elif tx1['give_asset'] == config.BTC: if block_index >= 310500 or config.TESTNET or config.REGTEST: # Protocol change. fee = int(tx0['fee_required'] * util.price(backward_quantity, tx0['give_quantity'])) else: fee = int(tx0['fee_required_remaining'] * util.price(backward_quantity, tx0_get_remaining)) logger.debug('Tx1 fee provided remaining: {}; required fee: {}'.format(tx1_fee_provM ided_remaining / config.UNIT, fee / config.UNIT)) if tx1_fee_provided_remaining < fee: logger.debug('Skipping: tx1 fee provided remaining is too low.') continue else: tx1_fee_provided_remaining -= fee if block_index >= 287800 or config.TESTNET or config.REGTEST: # Protocol change. tx0_fee_required_remaining -= fee else: # Don if tx1['get_asset'] == config.BTC: if tx0_fee_provided_remaining < tx1['fee_required']: continue elif tx1['give_asset'] == config.BTC: if tx1_fee_provided_remaining < tx0['fee_required']: continue if config.BTC in (tx1['give_asset'], tx1['get_asset']): status = 'pending' status = 'completed' # Credit. util.credit(db, tx1['source'], tx1['get_asset'], forward_quantity, action='order match', event=order_match_id) util.credit(db, tx0['source'], tx0['get_asset'], backward_quantity, action='order match', event=order_match_id) # Debit the order, even if it involves giving bitcoins, and so one # can't debit the sending account. # Get remainings may be negative. tx0_give_remaining -= forward_quantity tx0_get_remaining -= baM tx1_give_remaining -= backward_quantity tx1_get_remaining -= forward_quantity # Update give_remaining, get_remaining. tx0_status = 'open' if tx0_give_remaining <= 0 or (tx0_get_remaining <= 0 and (block_index >= 292000 or config.TESTNET or config.REGTEST)): # Protocol change if tx0['give_asset'] != config.BTC and tx0['get_asset'] != config.BTC: # Fill order, and recredit give_remainiM tx0_status = 'filled' util.credit(db, tx0['source'], tx0['give_asset'], tx0_give_remaining, event=tx1['tx_hash'], action='filled') bindings = { 'give_remaining': tx0_give_remaining, 'get_remaining': tx0_get_remaining, 'fee_required_remaining': tx0_fee_required_remaining, 'fee_provided_remaining': tx0_fee_provided_remaining, 'status': tx0_status, 'tx_hash': tx0['tx_hM sql='update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining, fee_provided_remaining = :fee_provided_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'orders', bindings) if tx1_give_remaining <= 0 or (tx1_get_remaining <= 0 and (block_index >= 292000 or config.TESTNET or config.REGTM EST)): # Protocol change if tx1['give_asset'] != config.BTC and tx1['get_asset'] != config.BTC: # Fill order, and recredit give_remaining. tx1_status = 'filled' util.credit(db, tx1['source'], tx1['give_asset'], tx1_give_remaining, event=tx0['tx_hash'], action='filled') bindings = { 'give_remaining': tx1_give_remaining, 'get_remaining': tx1_get_remaining, 'fee_required_remaining'M : tx1_fee_required_remaining, 'fee_provided_remaining': tx1_fee_provided_remaining, 'status': tx1_status, 'tx_hash': tx1['tx_hash'] sql='update orders set give_remaining = :give_remaining, get_remaining = :get_remaining, fee_required_remaining = :fee_required_remaining, fee_provided_remaining = :fee_provided_remaining, status = :status where tx_hash = :tx_hash' cursor.execute(sql, bindings) log.message(db, block_index,M 'update', 'orders', bindings) # Calculate when the match will expire. if block_index >= 308000 or config.TESTNET or config.REGTEST: # Protocol change. match_expire_index = block_index + 20 elif block_index >= 286500 or config.TESTNET or config.REGTEST: # Protocol change. match_expire_index = block_index + 10 match_expire_index = min(tx0['expire_index'], tx1['expire_index']) # Record order matM bindings = { 'id': util.make_id(tx0['tx_hash'], tx['tx_hash']), 'tx0_index': tx0['tx_index'], 'tx0_hash': tx0['tx_hash'], 'tx0_address': tx0['source'], 'tx1_index': tx1['tx_index'], 'tx1_hash': tx1['tx_hash'], 'tx1_address': tx1['source'], 'forward_asset': forward_asset, 'forward_quantity': forward_quantity, 'backward_asset': backward_asset, 'backward_quantity': backward_quantity, 'tx0_block_index': tx0['block_index'], 'tx1_block_index': tx1['block_index'], 'block_index': block_index, 'tx0_expiration': tx0['expiration'], 'tx1_expiration': tx1['expiration'], 'match_expire_index': match_expire_index, 'fee_paid': fee, 'status': status, sql='insert into order_matches values(:id, :tx0_indexM , :tx0_hash, :tx0_address, :tx1_index, :tx1_hash, :tx1_address, :forward_asset, :forward_quantity, :backward_asset, :backward_quantity, :tx0_block_index, :tx1_block_index, :block_index, :tx0_expiration, :tx1_expiration, :match_expire_index, :fee_paid, :status)' cursor.execute(sql, bindings) if tx1_status == 'filled': break def expire (db, block_index): cursor = db.cursor() # Expire orders and give refunds for the quantity give_remainiM ng (if non-zero; if not BTC). cursor.execute('''SELECT * FROM orders \ WHERE (status = ? AND expire_index < ?)''', ('open', block_index)) orders = list(cursor) for order in orders: cancel_order(db, order, 'expired', block_index) # Expire order_matches for BTC with no BTC. cursor.execute('''SELECT * FROM order_matches \ WHERE (status = ? and match_expire_index < ?)''', ('pending', block_index)) order_matches = list(cursor) ch in order_matches: cancel_order_match(db, order_match, 'expired', block_index) # Expire btc sell order if match expires if util.enabled('btc_sell_expire_on_match_expire'): # Check for other pending order matches involving either tx0_hash or tx1_hash bindings = { 'status': 'pending', 'tx0_hash': order_match['tx0_hash'], 'tx1_hash': order_match['tx1_hash'] sql='select * from order_matches whereM status = :status and ((tx0_hash in (:tx0_hash, :tx1_hash)) or ((tx1_hash in (:tx0_hash, :tx1_hash))))' cursor.execute(sql, bindings) order_matches_pending = cursor.fetchall() # Set BTC sell order status as expired only if there are no pending order matches if len(order_matches_pending) == 0: if order_match['backward_asset'] == "BTC" and order_match['status'] == "expired": cursor.execute('''SELECT * FROM orders \ M WHERE tx_hash = ?''', (order_match['tx1_hash'],)) cancel_order(db, list(cursor)[0], 'expired', block_index) if order_match['forward_asset'] == "BTC" and order_match['status'] == "expired": cursor.execute('''SELECT * FROM orders \ WHERE tx_hash = ?''', (order_match['tx0_hash'],)) cancel_order(db, list(cursor)[0], 'expired', block_index) if block_index >= 315000 or config.TESTNET oM r config.REGTEST: # Protocol change. for order_match in order_matches: cursor.execute('''SELECT * FROM transactions\ WHERE tx_hash = ?''', (order_match['tx0_hash'],)) match(db, list(cursor)[0], block_index) cursor.execute('''SELECT * FROM transactions\ WHERE tx_hash = ?''', (order_match['tx1_hash'],)) match(db, list(cursor)[0], block_index) xpandtab shiftwidth=4 softtabstop=4 Transaction 1: rps (Open the game) source: address used to play the game wager: amount to bet move_random_hash: sha256(sha256(move + random)) (stored as bytes, 16 bytes random) possible_moves: arbitrary odd number >= 3 expiration: how many blocks the game is valid Matching conditions: - tx0_possible_moves = tx1_possible_moves - tx0_wager = tx1_wager Transaction 2: rpsresolve (Resolve the game) source: same address as first transaM random: 16 bytes random move: the move number rps_match_id: matching id from counterpartylib.lib import config from counterpartylib.lib import exceptions from counterpartylib.lib import util from counterpartylib.lib import log from counterpartylib.lib import message_type # possible_moves wager move_random_hash expiration LENGTH = 2 + 8 + 32 + 4 def initialise (db): cursor = db.cursM # RPS (Rock-Paper-Scissors) cursor.execute('''CREATE TABLE IF NOT EXISTS rps( tx_index INTEGER UNIQUE, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, possible_moves INTEGER, wager INTEGER, move_random_hash TEXT, expiration INTEGER, expire_index INTEGER, status TEXT, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index), PRIMARY KEY (tx_index, tx_hash)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON rps (source) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS matching_idx ON rps (wager, possible_moves) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS M status_idx ON rps (status) ''') cursor.execute('''CREATE TABLE IF NOT EXISTS rps_matches( id TEXT PRIMARY KEY, tx0_index INTEGER, tx0_hash TEXT, tx0_address TEXT, tx1_index INTEGER, tx1_hash TEXT, tx1_address TEXT, tx0_move_random_hash TEXT, tx1_move_random_hash TEXT, wager INTEGER, possible_moves INTEGER, tx0_block_index INTEGER, tx1_block_index INTEGER, block_index INTEGER, tx0_expiration INTEGER, tx1_expiration INTEGER, match_expire_index INTEGER, status TEXT, FOREIGN KEY (tx0_index, tx0_hash, tx0_block_index) REFERENCES transactions(tx_index, tx_hash, block_index), FOREIGN KEY (tx1_index, tx1_hash, tx1_block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS rps_match_expire_idx ON rps_matches (status, match_expire_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS rps_tx0_address_idx ON rps_matches (tx0_address) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS rps_tx1_address_idx ON rps_matches (tx1_address) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS status_idx ON rps_matches (status) ''') # RPS Expirations cursor.execute('''CREATE TABLE IF NOT EXISTS rps_expirations( rps_index INTEGER PRIMARY KEY, rps_hash TEXT UNIQUE, source TEXT, block_index INTEGER, FOREIGN KEY (block_indeM x) REFERENCES blocks(block_index), FOREIGN KEY (rps_index, rps_hash) REFERENCES rps(tx_index, tx_hash)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON rps_expirations (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON rps_expirations (source) ''') # RPS Match Expirations cursor.execute('''CREATE TABLE IF NOT EXISTS rps_match_expiM rps_match_id TEXT PRIMARY KEY, tx0_address TEXT, tx1_address TEXT, block_index INTEGER, FOREIGN KEY (rps_match_id) REFERENCES rps_matches(id), FOREIGN KEY (block_index) REFERENCES blocks(block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON rps_match_expirations (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx0_address_idx ON rps_match_expirations (tx0_address) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS tx1_address_idx ON rps_match_expirations (tx1_address) ''') def cancel_rps (db, rps, status, block_index): cursor = db.cursor() # Update status of rps. 'status': status, 'tx_hash': rps['tx_hash'] sql='''UPDATE rps SET status = :stM atus WHERE tx_hash = :tx_hash''' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'rps', bindings) util.credit(db, rps['source'], 'XCP', rps['wager'], action='recredit wager', event=rps['tx_hash']) def update_rps_match_status (db, rps_match, status, block_index): cursor = db.cursor() if status in ['expired', 'concluded: tie']: # Recredit tx0 address. util.credit(db, rps_match['tx0_address'], 'XCP', rps_match['wager'],M action='recredit wager', event=rps_match['id']) # Recredit tx1 address. util.credit(db, rps_match['tx1_address'], 'XCP', rps_match['wager'], action='recredit wager', event=rps_match['id']) elif status.startswith('concluded'): # Credit the winner winner = rps_match['tx0_address'] if status == 'concluded: first player wins' else rps_match['tx1_address'] util.credit(db, winner, 'XCP', 2 * rps_match['wager'], action='wins', event=rps_mM # Update status of rps match. 'status': status, 'rps_match_id': rps_match['id'] sql='UPDATE rps_matches SET status = :status WHERE id = :rps_match_id' cursor.execute(sql, bindings) log.message(db, block_index, 'update', 'rps_matches', bindings) def validate (db, source, possible_moves, wager, move_random_hash, expiration, block_index): if util.enabled('disable_rps'): problems.append('rps disabled'M if not isinstance(possible_moves, int): problems.append('possible_moves must be a integer') return problems if not isinstance(wager, int): problems.append('wager must be in satoshis') return problems if not isinstance(expiration, int): problems.append('expiration must be expressed as an integer block delta') return problems if not all(c in string.hexdigits for c in move_random_hash): problems.append('move_random_hash must be an hexadecimal M return problems move_random_hash_bytes = binascii.unhexlify(move_random_hash) if possible_moves < 3: problems.append('possible moves must be at least 3') if possible_moves % 2 == 0: problems.append('possible moves must be odd') problems.append('non if expiration < 0: problems.append('negative expiration') if expiration == 0 and not (block_index >= 317500 or config.TESTNET or config.REGTEST): # Protocol change. problems.append('zero expiration') if expiration > config.MAX_EXPIRATION: problems.append('expiration overflow') if len(move_random_hash_bytes) != 32: problems.append('move_random_hash must be 32 bytes in hexadecimal format') def compose(db, source, possible_moves, wager, move_random_hash, expiration): problems = validate(db, source, possible_moves, wager, move_random_hash, expiration, util.CURRENT_BLOCK_INDEX) if problems: raise exceptions.ComposeError(prM data = message_type.pack(ID) data += struct.pack(FORMAT, possible_moves, wager, binascii.unhexlify(move_random_hash), expiration) return (source, [], data) def parse(db, tx, message): rps_parse_cursor = db.cursor() # Unpack message. if len(message) != LENGTH: raise exceptions.UnpackError (possible_moves, wager, move_random_hash, expiration) = struct.unpack(FORMAT, message) status = 'open' except (exceptions.UnpackError, struct.error): (possible_moves, wager, move_random_hash, expiration) = 0, 0, '', 0 status = 'invalid: could not unpack' if status == 'open': move_random_hash = binascii.hexlify(move_random_hash).decode('utf8') rps_parse_cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], 'XCP')) balances = list(rps_parse_cursor) if not balances: wager = 0 e = balances[0]['quantity'] if balance < wager: wager = balance problems = validate(db, tx['source'], possible_moves, wager, move_random_hash, expiration, tx['block_index']) if problems: status = 'invalid: {}'.format(', '.join(problems)) # Debit quantity wagered. (Escrow.) if status == 'open': util.debit(db, tx['source'], 'XCP', wager, action="open RPS", event=tx['tx_hash']) # Add parsed transaction to message-type 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'possible_moves': possible_moves, 'wager': wager, 'move_random_hash': move_random_hash, 'expiration': expiration, 'expire_index': tx['block_index'] + expiration, 'status': status, sql = '''INSERT INTO rps VALUES (:tx_index, :tx_hash, :block_index, :source, :possible_moves, :wager, :move_random_hash, :expiration, :exM pire_index, :status)''' rps_parse_cursor.execute(sql, bindings) if status == 'open': match(db, tx, tx['block_index']) rps_parse_cursor.close() def match (db, tx, block_index): cursor = db.cursor() # Get rps in question. rps = list(cursor.execute('''SELECT * FROM rps WHERE tx_index = ? AND status = ?''', (tx['tx_index'], 'open'))) cursor.close() assert len(rps) == 1 possible_moves = tx1['posM wager = tx1['wager'] tx1_status = 'open' bindings = (possible_moves, 'open', wager, tx1['source']) # dont match twice same RPS already_matched = [] old_rps_matches = cursor.execute('''SELECT * FROM rps_matches WHERE tx0_hash = ? OR tx1_hash = ?''', (tx1['tx_hash'], tx1['tx_hash'])) for old_rps_match in old_rps_matches: counter_tx_hash = old_rps_match['tx1_hash'] if tx1['tx_hash'] == old_rps_match['tx0_hash'] else old_rps_match['tx0_hash'] already_matched.append(counter_tx_hash) already_matched_cond = '' if already_matched: already_matched_cond = '''AND tx_hash NOT IN ({})'''.format(','.join(['?' for e in range(0, len(already_matched))])) bindings += tuple(already_matched) sql = '''SELECT * FROM rps WHERE (possible_moves = ? AND status = ? AND wager = ? AND source != ? {}) ORDER BY tx_index LIMIT 1'''.format(already_matched_cond) rps_matches = list(cursor.execute(sql, bindings)) # update status for txn in [tx0, tx1]: bindings = { 'status': 'matched', 'tx_index': txn['tx_index'] cursor.execute('''UPDATE rps SET status = :status WHERE tx_index = :tx_index''', bindings) log.message(db, block_index, 'update', 'rps', bindings) bindings = { 'id': util.make_id(tx0['tx_hash'], tx1['tx_hash']), 'tx0_index': tx0['tx_index'], 'tx0_hash': tx0['tx_M 'tx0_address': tx0['source'], 'tx1_index': tx1['tx_index'], 'tx1_hash': tx1['tx_hash'], 'tx1_address': tx1['source'], 'tx0_move_random_hash': tx0['move_random_hash'], 'tx1_move_random_hash': tx1['move_random_hash'], 'wager': wager, 'possible_moves': possible_moves, 'tx0_block_index': tx0['block_index'], 'tx1_block_index': tx1['block_index'], 'block_index': block_index, 'tx0_expiration': tx0['expiration'], 'tx1_expiration': tx1['expiration'], 'match_expire_index': block_index + 20, 'status': 'pending' sql = '''INSERT INTO rps_matches VALUES (:id, :tx0_index, :tx0_hash, :tx0_address, :tx1_index, :tx1_hash, :tx1_address, :tx0_move_random_hash, :tx1_move_random_hash, :wager, :possible_moves, :tx0_block_index, :tx1_block_index, :blM ock_index, :tx0_expiration, :tx1_expiration, :match_expire_index, :status)''' cursor.execute(sql, bindings) def expire (db, block_index): cursor = db.cursor() # Expire rps and give refunds for the quantity wager. cursor.execute('''SELECT * FROM rps WHERE (status = ? AND expire_index < ?)''', ('open', block_index)) for rps in cursor.fetchall(): cancel_rps(db, rps, 'expired', block_index) # Record rps expirM bindings = { 'rps_index': rps['tx_index'], 'rps_hash': rps['tx_hash'], 'source': rps['source'], 'block_index': block_index sql = '''INSERT INTO rps_expirations VALUES (:rps_index, :rps_hash, :source, :block_index)''' cursor.execute(sql, bindings) # Expire rps matches expire_bindings = ('pending', 'pending and resolved', 'resolved and pending', block_index) cursor.execute('''SELECT * FROM rps_matches WHERE (status IM N (?, ?, ?) AND match_expire_index < ?)''', expire_bindings) for rps_match in cursor.fetchall(): new_rps_match_status = 'expired' # pending loses against resolved if rps_match['status'] == 'pending and resolved': new_rps_match_status = 'concluded: second player wins' elif rps_match['status'] == 'resolved and pending': new_rps_match_status = 'concluded: first player wins' update_rps_match_status(db, rps_match, new_rps_match_status, block_index) # Record rps match expiration. bindings = { 'rps_match_id': rps_match['id'], 'tx0_address': rps_match['tx0_address'], 'tx1_address': rps_match['tx1_address'], 'block_index': block_index sql = '''INSERT INTO rps_match_expirations VALUES (:rps_match_id, :tx0_address, :tx1_address, :block_index)''' cursor.execute(sql, bindings) # Rematch not expired and not resolved RPS if new_rps_match_status == 'expired': sql = '''SELECT * FROM rps WHERE tx_hash IN (?, ?) AND status = ? AND expire_index >= ?''' bindings = (rps_match['tx0_hash'], rps_match['tx1_hash'], 'matched', block_index) matched_rps = list(cursor.execute(sql, bindings)) for rps in matched_rps: cursor.execute('''UPDATE rps SET status = ? WHERE tx_index = ?''', ('open', rps['tx_index'])) # Re-debit XCP refund by close_rps_match. util.debit(db, rps['source'], 'XCP', rps['wager'M ], action='reopen RPS after matching expiration', event=rps_match['id']) # Rematch match(db, {'tx_index': rps['tx_index']}, block_index) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 logger = logging.getLogger(__name__) from counterpartylib.lib import (config, exceptions, util, message_type) # move random rps_match_id LENGTH = 2 + 16 + 32 + 32 def initialise (db): cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS rpsresolves( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, move INTEGER, random TEXT, rps_match_id TEXT, status TEXT, FOREIGN KEY (tx_indeM x, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON rpsresolves (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON rpsresolves (source) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS rps_match_id_idx ON rpsresolves (rps_match_id) ''') ef validate (db, source, move, random, rps_match_id): rps_match = None if not isinstance(move, int): problems.append('move must be a integer') return None, None, problems if not all(c in string.hexdigits for c in random): problems.append('random must be an hexadecimal string') return None, None, problems random_bytes = binascii.unhexlify(random) if len(random_bytes) != 16: problems.append('random must be 16 bytes in hexadecimal format'M return None, None, problems cursor = db.cursor() rps_matches = list(cursor.execute('''SELECT * FROM rps_matches WHERE id = ?''', (rps_match_id,))) if len(rps_matches) == 0: problems.append('no such rps match') return None, rps_match, problems elif len(rps_matches) > 1: assert False rps_match = rps_matches[0] problems.append('move must be greater than 0') elif move > rps_match['possible_moves']: problems.apM pend('move must be lower than {}'.format(rps_match['possible_moves'])) if source not in [rps_match['tx0_address'], rps_match['tx1_address']]: problems.append('invalid source address') return None, rps_match, problems if rps_match['tx0_address'] == source: rps_match_status = ['pending', 'pending and resolved'] rps_match_status = ['pending', 'resolved and pending'] move_random_hash = util.dhash(random_bytes + int(move).to_bytes(2M move_random_hash = binascii.hexlify(move_random_hash).decode('utf-8') if rps_match['tx{}_move_random_hash'.format(txn)] != move_random_hash: problems.append('invalid move or random value') return txn, rps_match, problems if rps_match['status'] == 'expired': problems.append('rps match expired') elif rps_match['status'].startswith('concluded'): problems.append('rps match concluded') elif rps_match['status'].startswith('invalid'): s.append('rps match invalid') elif rps_match['status'] not in rps_match_status: problems.append('rps already resolved') return txn, rps_match, problems def compose (db, source, move, random, rps_match_id): tx0_hash, tx1_hash = util.parse_id(rps_match_id) txn, rps_match, problems = validate(db, source, move, random, rps_match_id) if problems: raise exceptions.ComposeError(problems) # Warn if down to the wire. time_left = rps_match['match_expire_index'] - util.CURRENT_BLOCK_INDM if time_left < 4: logger.warning('Only {} blocks until that rps match expires. The conclusion might not make into the blockchain in time.'.format(time_left)) tx0_hash_bytes = binascii.unhexlify(bytes(tx0_hash, 'utf-8')) tx1_hash_bytes = binascii.unhexlify(bytes(tx1_hash, 'utf-8')) random_bytes = binascii.unhexlify(bytes(random, 'utf-8')) data = message_type.pack(ID) data += struct.pack(FORMAT, move, random_bytes, tx0_hash_bytes, tx1_hash_bytes) return (source, [], data) parse (db, tx, message): cursor = db.cursor() # Unpack message. if len(message) != LENGTH: raise exceptions.UnpackError move, random, tx0_hash_bytes, tx1_hash_bytes = struct.unpack(FORMAT, message) tx0_hash, tx1_hash = binascii.hexlify(tx0_hash_bytes).decode('utf-8'), binascii.hexlify(tx1_hash_bytes).decode('utf-8') rps_match_id = util.make_id(tx0_hash, tx1_hash) random = binascii.hexlify(random).decode('utf-8') status = 'valid' t (exceptions.UnpackError, struct.error) as e: move, random, tx0_hash, tx1_hash, rps_match_id = None, None, None, None, None status = 'invalid: could not unpack' if status == 'valid': txn, rps_match, problems = validate(db, tx['source'], move, random, rps_match_id) if problems: rps_match = None status = 'invalid: ' + '; '.join(problems) # Add parsed transaction to message-type rpsresolves_bindings = { 'tx_index': tx['tx_M 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'move': move, 'random': random, 'rps_match_id': rps_match_id, 'status': status if status == 'valid': rps_match_status = 'concluded' if rps_match['status'] == 'pending': rps_match_status = 'resolved and pending' if txn==0 else 'pending and resolved' if rps_match_status == 'concluded': counter_txn = 0 if txn ==M counter_source = rps_match['tx{}_address'.format(counter_txn)] sql = '''SELECT * FROM rpsresolves WHERE rps_match_id = ? AND source = ? AND status = ?''' counter_games = list(cursor.execute(sql, (rps_match_id, counter_source, 'valid'))) assert len(counter_games) == 1 counter_game = counter_games[0] winner = resolve_game(db, rpsresolves_bindings, counter_game) if winner == 0: rps_match_status = 'concluded:M elif winner == counter_game['tx_index']: rps_match_status = 'concluded: {} player wins'.format('first' if counter_txn == 0 else 'second') rps_match_status = 'concluded: {} player wins'.format('first' if txn == 0 else 'second') rps.update_rps_match_status(db, rps_match, rps_match_status, tx['block_index']) sql = '''INSERT INTO rpsresolves VALUES (:tx_index, :tx_hash, :block_index, :source, :move, :random, :rps_match_id, :status)''' cursor.execute(sql, rpsresolves_bindings) # https://en.wikipedia.org/wiki/Rock-paper-scissors#Additional_weapons: def resolve_game(db, resovlerps1, resovlerps2): move1 = resovlerps1['move'] move2 = resovlerps2['move'] same_parity = (move1 % 2) == (move2 % 2) if (same_parity and move1 < move2) or (not same_parity and move1 > move2): return resovlerps1['tx_index'] elif (same_parity and move1 > move2) or (not same_parity and move1 < move2): return resovlerps2M # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 from counterpartylib.lib.messages.versions import send1 from counterpartylib.lib.messages.versions import enhanced_send from counterpartylib.lib.messages.versions import mpma from counterpartylib.lib import util from counterpartylib.lib import exceptions from counterpartylib.lib import config def initialise (db): cursor = db.cursor() cursor.execute('''CM REATE TABLE IF NOT EXISTS sends( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, destination TEXT, asset TEXT, quantity INTEGER, status TEXT, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') lumn['name'] for column in cursor.execute('''PRAGMA table_info(sends)''')] # If CIP10 activated, Create Sends copy, copy old data, drop old table, rename new table, recreate indexes t do `ALTER TABLE IF COLUMN NOT EXISTS` nor can drop UNIQUE constraints if 'msg_index' not in columns: if 'memo' not in columns: cursor.execute('''CREATE TABLE IF NOT EXISTS new_sends( tx_index INTEGER, tx_hash TEXT, block_index INTEGER, source TEXT, destination TEXT, asset TEXT, quantity INTEGER, status TEXT, msg_index INTEGER DEFAULT 0, PRIMARY KEY (tx_index, msg_index), FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index), UNIQUE (tx_hash, msg_index) ON CONFLICT FAIL) ''') cursor.execute('''INSERT INTO new_sends(tx_index, tx_hash, block_index, source, destination, asset, quantity, status) SELECT tx_index, tx_hash, block_index, source, destination, asset, quantity, status FROM sends''', {}) cursor.execute('''CREATE TABLE IF NOT EXISTS new_sends( tx_index INTEGER, tx_hash TEXM block_index INTEGER, source TEXT, destination TEXT, asset TEXT, quantity INTEGER, status TEXT, memo BLOB, msg_index INTEGER DEFAULT 0, PRIMARY KEY (tx_index, msg_index), FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index), UNIQUE (tx_hash, msg_index) ON CONFLICT FAIL) cursor.execute('''INSERT INTO new_sends (tx_index, tx_hash, block_index, source, destination, asset, quantity, status, memo) SELECT tx_index, tx_hash, block_index, source, destination, asset, quantity, status, memo FROM sends''', {}) cursor.execute('DROP TABLE sends') cursor.execute('ALTER TABLE new_sends RENAME TO sends') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON sends (block_index) cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON sends (source) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS destination_idx ON sends (destination) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS asset_idx ON sends (asset) ''') # Adds a memo to sends t do `ALTER TABLE IF COLUMN NOT EXISTS`. if 'memo' not in columns:M cursor.execute('''ALTER TABLE sends ADD COLUMN memo BLOB''') cursor.execute('''CREATE INDEX IF NOT EXISTS memo_idx ON sends (memo) ''') def unpack(db, message, block_index): return send1.unpack(db, message, block_index) def validate (db, source, destination, asset, quantity, block_index): return send1.validate(db, source, destination, asset, quantity, block_index) def compose (db, source, destination, asset, quantity, memo=None, memo_is_hex=False, usM e_enhanced_send=None): # special case - enhanced_send replaces send by default when it is enabled # but it can be explicitly disabled with an API parameter if util.enabled('enhanced_sends'): # Another special case, if destination, asset and quantity are arrays, it's an MPMA send if isinstance(destination, list) and isinstance(asset, list) and isinstance(quantity, list): if util.enabled('mpma_sends'): if len(destination) == len(asset) and len(asset) == len(quM # Sending memos in a MPMA message can be done by several approaches: # 1. Send a list of memos, there must be one for each send and they correspond to the sends by index # - In this case memo_is_hex should be a list with the same cardinality # 2. Send a dict with the message specific memos and the message wide memo (same for the hex specifier): # - Each dict should have 2 members: # M + list: same as case (1). An array that specifies the memo for each send # + msg_wide: the memo for the whole message. This memo will be used for sends that don't have a memo specified. Same as in (3) # 3. Send one memo (either bytes or string) and True/False in memo_is_hex. This will be interpreted as a message wide memo. if (len(destination) > config.MPMA_LIMIT): raise exceptions.ComposeError('mpma sends have a maximum of 'M +str(config.MPMA_LIMIT)+' sends') if isinstance(memo, list) and isinstance(memo_is_hex, list): # (1) implemented here if len(memo) != len(memo_is_hex): raise exceptions.ComposeError('memo and memo_is_hex lists should have the same length') elif len(memo) != len(destination): raise exceptions.ComposeError('memo/memo_is_hex lists should have the same M return mpma.compose(db, source, util.flat(zip(asset, destination, quantity, memo, memo_is_hex)), None, None) elif isinstance(memo, dict) and isinstance(memo_is_hex, dict): # (2) implemented here if not('list' in memo and 'list' in memo_is_hex and 'msg_wide' in memo and 'msg_wide' in memo_is_hex): raise exceptions.ComposeError('when specifying memo/memo_is_hex as a dict, they musM t contain keys "list" and "msg_wide"') elif len(memo['list']) != len(memo_is_hex['list']): raise exceptions.ComposeError('length of memo.list and memo_is_hex.list must be equal') elif len(memo['list']) != len(destination): raise exceptions.ComposeError('length of memo.list/memo_is_hex.list must be equal to the amount of sends') return mpma.compose(db, source, util.flat(zip(asset, destinatM ion, quantity, memo['list'], memo_is_hex['list'])), memo['msg_wide'], memo_is_hex['msg_wide']) else: # (3) the default case return mpma.compose(db, source, util.flat(zip(asset, destination, quantity)), memo, memo_is_hex) else: raise exceptions.ComposeError('destination, asset and quantity arrays must have the same amount of elements') raise exceptions.ComposeError('mpma sends areM elif use_enhanced_send is None or use_enhanced_send == True: return enhanced_send.compose(db, source, destination, asset, quantity, memo, memo_is_hex) elif memo is not None or use_enhanced_send == True: raise exceptions.ComposeError('enhanced sends are not enabled') return send1.compose(db, source, destination, asset, quantity) def parse (db, tx, message): # TODO: *args return send1.parse(db, tx, message) # vim: tabstop=8 expandtab shiftwidth=4 softtabstM from counterpartylib.lib import exceptions from counterpartylib.lib import config from counterpartylib.lib import util from counterpartylib.lib import log from counterpartylib.lib import message_type from counterpartylib.lib import address from counterpartylib.lib.exceptions import * MAX_MEMO_LENGTH = 34 # Could be higher, but we will keep it consistent with enhanced send ANTISPAM_FEE_DECIMAL = 0.5 ANTISPAM_FEE_DECIMAL * config.UNIT FLAG_BINARY_MEMO = 4 FLAGS_ALL = FLAG_BINARY_MEMO | FLAG_BALANCES | FLAG_OWNERSHIP cursor = db.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS sweeps( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, destination TEXT, flags INTEGER, status TEXT, memo BLOB, fee_paid INTEGER, FOREIGN KEY (tx_index, tx_hash, block_index) REFERENCES transactions(tx_index, tx_hash, block_index)) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS block_index_idx ON sweeps (block_index) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS source_idx ON sweeps (source) ''') cute('''CREATE INDEX IF NOT EXISTS destination_idx ON sweeps (destination) ''') cursor.execute('''CREATE INDEX IF NOT EXISTS memo_idx ON sweeps (memo) ''') def validate (db, source, destination, flags, memo, block_index): if source == destination: problems.append('destination cannot be the same as source') cursor = db.cursor() cursor.execute('''SELECT * FROM balances WHERE (address = ? ANDM asset = ?)''', (source, 'XCP')) result = cursor.fetchall() if len(result) == 0: problems.append('insufficient XCP balance for sweep. Need %s XCP for antispam fee' % ANTISPAM_FEE_DECIMAL) elif result[0]['quantity'] < ANTISPAM_FEE: problems.append('insufficient XCP balance for sweep. Need %s XCP for antispam fee' % ANTISPAM_FEE_DECIMAL) if flags > FLAGS_ALL: problems.append('invalid flags %i' % flags) elif not(flags & (FLAG_BALANCES | FLAG_OWNERSHIP))M problems.append('must specify which kind of transfer in flags') if memo and len(memo) > MAX_MEMO_LENGTH: problems.append('memo too long') def compose (db, source, destination, flags, memo): if memo is None: elif flags & FLAG_BINARY_MEMO: memo = bytes.fromhex(memo) memo = memo.encode('utf-8') memo = struct.pack(">{}s".format(len(memo)), memo) block_index = util.CURRENT_BLOCK_INDEX problems = validate(dbM , source, destination, flags, memo, block_index) if problems: raise exceptions.ComposeError(problems) short_address_bytes = address.pack(destination) data = message_type.pack(ID) data += struct.pack(FORMAT, short_address_bytes, flags) return (source, [], data) def unpack(db, message, block_index): memo_bytes_length = len(message) - LENGTH if memo_bytes_length < 0: raise exceptions.UnpackError('invalid message length') if memo_byteM s_length > MAX_MEMO_LENGTH: raise exceptions.UnpackError('memo too long') struct_format = FORMAT + ('{}s'.format(memo_bytes_length)) short_address_bytes, flags, memo_bytes = struct.unpack(struct_format, message) if len(memo_bytes) == 0: memo_bytes = None elif not(flags & FLAG_BINARY_MEMO): memo_bytes = memo_bytes.decode('utf-8') # unpack address full_address = address.unpack(short_address_bytes) except (struct.error) as e: logger.warning("sweep send unpack error: {}".format(e)) raise exceptions.UnpackError('could not unpack') 'destination': full_address, 'flags': flags, 'memo': memo_bytes, def parse (db, tx, message): cursor = db.cursor() fee_paid = round(config.UNIT/2) # Unpack message. unpacked = unpack(db, message, tx['block_index']) destination, flags, memo_bytes = unpacked['destination'], unpacked['flags'], unpackedM status = 'valid' except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: destination, flags, memo_bytes = None, None, None status = 'invalid: could not unpack ({})'.format(e) except BalanceError: destination, flags, memo_bytes = None, None, None status = 'invalid: insufficient balance for antispam fee for sweep' except Exception as err: destination, flags, memo_bytes = None, None, None status = 'invalid: could not uM if status == 'valid': problems = validate(db, tx['source'], destination, flags, memo_bytes, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if status == 'valid': util.debit(db, tx['source'], 'XCP', fee_paid, action='sweep fee', event=tx['tx_hash']) except BalanceError: destination, flags, memo_bytes = None, None, None status = 'invalid: insufficient balance for antispam fee for sweep' if status == 'valid': cursor.execute('''SELECT * FROM balances WHERE address = ?''', (tx['source'],)) balances = cursor.fetchall() if flags & FLAG_BALANCES: for balance in balances: util.debit(db, tx['source'], balance['asset'], balance['quantity'], action='sweep', event=tx['tx_hash']) util.credit(db, destination, balance['asset'], balance['quantity'], action='sweep', event=tx['tx_hash']) if flags & FLAG_OWNERSHIP: sweep_posM for balance in balances: cursor.execute('''SELECT * FROM issuances \ WHERE (status = ? AND asset = ?) ORDER BY tx_index ASC''', ('valid', balance['asset'])) issuances = cursor.fetchall() if len(issuances) > 0: last_issuance = issuances[-1] if last_issuance['issuer'] == tx['source']: bindings= { M 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'msg_index': sweep_pos, 'block_index': tx['block_index'], 'asset': balance['asset'], 'quantity': 0, 'divisible': last_issuance['divisible'], 'source': last_issuance['source'], 'issuer': destination, 'transfer': TrM 'callable': last_issuance['callable'], 'call_date': last_issuance['call_date'], 'call_price': last_issuance['call_price'], 'description': last_issuance['description'], 'fee_paid': 0, 'locked': last_issuance['locked'], 'status': status, 'asset_longname': last_issuance['asset_longname'], 'reset': False } sql='insert into issuances values(:tx_index, :tx_hash, :msg_index, :block_index, :asset, :quantity, :divisible, :source, :issuer, :transfer, :callable, :call_date, :call_price, :description, :fee_paid, :locked, :status, :asset_longname, :reset)' cursor.execute(sql, bindings) sweep_pos += 1 bindings = { 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hM 'block_index': tx['block_index'], 'source': tx['source'], 'destination': destination, 'flags': flags, 'status': status, 'memo': memo_bytes, 'fee_paid': fee_paid sql = 'insert into sweeps values(:tx_index, :tx_hash, :block_index, :source, :destination, :flags, :status, :memo, :fee_paid)' cursor.execute(sql, bindings) # Each message gets identified by itsM # The only exception is send.version1 which is 0 the first to fourth byte. # Used IDs for messages: # 1 send.enhanced_send # 21 issuance.subasset # Allocate each new type of message within the "logical" 10 number boundary # Only allocate a 10 number boundary if it makes sense #### enhanced_send.py logger = logging.getLogger(__name__) from counterpartylib.lib import (config, util, exceptions, util, message_type, address) MAX_MEMO_LENGTH = 34 def unpack(db, message, block_index): # account for memo bytes memo_bytes_length = len(message) - LENGTH if memo_bytes_length < 0: raise exceptions.UnpackError('invalid message length') if memo_bytes_length > MAX_MEMO_LENGTH: raise exceptions.UnpackError('memo too long') struct_format = FORMAT + ('{}s'.format(memo_bytes_length)) asset_id, quantity, short_address_bytes, memo_bytes = struct.unpack(struct_format, message) if len(memo_bytes) == 0: memo_bytes = None # unpack address full_address = address.unpack(short_address_bytes) # asset id to name asset = util.generate_asset_name(asset_id, block_index) if asset == config.BTC: raise exceM ptions.AssetNameError('{} not allowed'.format(config.BTC)) except (struct.error) as e: logger.warning("enhanced send unpack error: {}".format(e)) raise exceptions.UnpackError('could not unpack') except (exceptions.AssetNameError, exceptions.AssetIDError) as e: logger.warning("enhanced send invalid asset id: {}".format(e)) raise exceptions.UnpackError('asset id invalid') 'asset': asset, 'quantity': quantity, 'address': full_address, def validate (db, source, destination, asset, quantity, memo_bytes, block_index): if asset == config.BTC: problems.append('cannot send {}'.format(config.BTC)) if not isinstance(quantity, int): problems.append('quantity must be in satoshis') return problems if quantity < 0: problems.append('negative quantity') if quantity == 0: problems.append('zero quantity') if quantity > confM problems.append('integer overflow') # destination is always required if not destination: problems.append('destination is required') if memo_bytes is not None and len(memo_bytes) > MAX_MEMO_LENGTH: problems.append('memo is too long') if util.enabled('options_require_memo'): cursor = db.cursor() results = cursor.execute('SELECT options FROM addresses WHERE address=?', (destination,)) if results: result = results.fetchone() if result and util.active_options(result['options'], config.ADDRESS_OPTION_REQUIRE_MEMO): if memo_bytes is None or (len(memo_bytes) == 0): problems.append('destination requires memo') cursor.close() def compose (db, source, destination, asset, quantity, memo, memo_is_hex): cursor = db.cursor() # Just send BTC? if asset == config.BTC: return (source, M [(destination, quantity)], None) # resolve subassets asset = util.resolve_subasset_longname(db, asset) #quantity must be in int satoshi (not float, string, etc) if not isinstance(quantity, int): raise exceptions.ComposeError('quantity must be an int (in satoshi)') # Only for outgoing (incoming will overburn). balances = list(cursor.execute('''SELECT * FROM balances WHERE (address = ? AND asset = ?)''', (source, asset))) if not balances or balances[0]['quantity'] < quantity: raise exceptions.ComposeError('insufficient funds') # convert memo to memo_bytes based on memo_is_hex setting if memo is None: memo_bytes = b'' elif memo_is_hex: memo_bytes = bytes.fromhex(memo) memo = memo.encode('utf-8') memo_bytes = struct.pack(">{}s".format(len(memo)), memo) block_index = util.CURRENT_BLOCK_INDEX problems = validate(db, source, destination, asset, quantity, memo_bytes, block_index) if problems: raise exceptions.ComposeErM asset_id = util.get_asset_id(db, asset, block_index) short_address_bytes = address.pack(destination) data = message_type.pack(ID) data += struct.pack(FORMAT, asset_id, quantity, short_address_bytes) data += memo_bytes # return an empty array as the second argument because we don't need to send BTC dust to the recipient return (source, [], data) def parse (db, tx, message): cursor = db.cursor() # Unpack message. unpacked = unM pack(db, message, tx['block_index']) asset, quantity, destination, memo_bytes = unpacked['asset'], unpacked['quantity'], unpacked['address'], unpacked['memo'] status = 'valid' except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: asset, quantity, destination, memo_bytes = None, None, None, None status = 'invalid: could not unpack ({})'.format(e) asset, quantity, destination, memo_bytes = None, None, None, None status = 'invalM id: could not unpack' if status == 'valid': # don't allow sends over MAX_INT at all if quantity and quantity > config.MAX_INT: status = 'invalid: quantity is too large' quantity = None if status == 'valid': problems = validate(db, tx['source'], destination, asset, quantity, memo_bytes, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if status == 'valid': # verify balance is present cursor.execute('''SELM ECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], asset)) balances = cursor.fetchall() if not balances or balances[0]['quantity'] < quantity: status = 'invalid: insufficient funds' if status == 'valid': util.debit(db, tx['source'], asset, quantity, action='send', event=tx['tx_hash']) util.credit(db, destination, asset, quantity, action='send', event=tx['tx_hash']) # log invalid transactions if quantity is None: logger.warn("Invalid send from %s with status %s. (%s)" % (tx['source'], status, tx['tx_hash'])) logger.warn("Invalid send of %s %s from %s to %s. status is %s. (%s)" % (quantity, asset, tx['source'], destination, status, tx['tx_hash'])) # Add parsed transaction to message-type 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'destination': destination, 'asset': asset, 'quantity': quantity, 'status': status, 'memo': memo_bytes, if "integer overflow" not in status and "quantity must be in satoshis" not in status: sql = 'insert into sends (tx_index, tx_hash, block_index, source, destination, asset, quantity, status, memo) values(:tx_index, :tx_hash, :block_index, :source, :destination, :asset, :quantity, :status, :memo)' cursor.execute(sql, bindinM logger.warn("Not storing [send] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 from bitcoin.core import key from functools import reduce from itertools import groupby logger = logging.getLogger(__name__) from bitstring import ReadError artylib.lib import (config, util, exceptions, util, message_type, address) from .mpma_util.internals import (_decode_mpmaSendDecode, _encode_mpmaSend) ID = 3 # 0x03 is this specific message type ## expected functions for message version def unpack(db, message, block_index): unpacked = _decode_mpmaSendDecode(message, block_index) except (struct.error) as e: raise exceptions.UnpackError('could not unpack') except (exceptions.AssetNameError, exceptions.AssetIDError) as e: ise exceptions.UnpackError('invalid asset in mpma send') except (ReadError) as e: raise exceptions.UnpackError('truncated data') def validate (db, source, asset_dest_quant_list, block_index): if len(asset_dest_quant_list) == 0: problems.append('send list cannot be empty') if len(asset_dest_quant_list) == 1: problems.append('send list cannot have only one element') if len(asset_dest_quant_list) > 0: # Need to manually unpack tM he tuple to avoid errors on scenarios where no memo is specified grpd = groupby([(t[0], t[1]) for t in asset_dest_quant_list]) lengrps = [len(list(grpr)) for (group, grpr) in grpd] cardinality = max(lengrps) if cardinality > 1: problems.append('cannot specify more than once a destination per asset') cursor = db.cursor() for t in asset_dest_quant_list: # Need to manually unpack the tuple to avoid errors on scenarios where no memo is specified destination = t[1] quantity = t[2] sendMemo = None if len(t) > 3: sendMemo = t[3] if asset == config.BTC: problems.append('cannot send {} to {}'.format(config.BTC, destination)) if not isinstance(quantity, int): problems.append('quantities must be an int (in satoshis) for {} to {}'.format(asset, destination)) if quantity < 0: problems.append('negative quantity for {} to {}'.format(asset, destination)) problems.append('zero quantity for {} to {}'.format(asset, destination)) # For SQLite3 if quantity > config.MAX_INT: problems.append('integer overflow for {} to {}'.format(asset, destination)) # destination is always required if not destination: problems.append('destination is required for {}'.format(asset)) if util.enabled('options_require_memo'): results = cursor.execute('SELECT options FROM addresses WHERE M address=?', (destination,)) if results: result = results.fetchone() if result and result['options'] & config.ADDRESS_OPTION_REQUIRE_MEMO and (sendMemo is None): problems.append('destination {} requires memo'.format(destination)) def compose (db, source, asset_dest_quant_list, memo, memo_is_hex): cursor = db.cursor() out_balances = util.accumulate([(t[0], t[2]) for t in asset_dest_quant_list]) asset, quantity) in out_balances: if util.enabled('mpma_subasset_support'): # resolve subassets asset = util.resolve_subasset_longname(db, asset) if not isinstance(quantity, int): raise exceptions.ComposeError('quantities must be an int (in satoshis) for {}'.format(asset)) balances = list(cursor.execute('''SELECT * FROM balances WHERE (address = ? AND asset = ?)''', (source, asset))) if not balances or balances[0]['quantity'] < quantity: raise exceptions.ComposeError('insufficient funds for {}'.format(asset)) block_index = util.CURRENT_BLOCK_INDEX problems = validate(db, source, asset_dest_quant_list, block_index) if problems: raise exceptions.ComposeError(problems) data = message_type.pack(ID) data += _encode_mpmaSend(db, asset_dest_quant_list, block_index, memo=memo, memo_is_hex=memo_is_hex) return (source, [], data) def parse (db, tx, message): unpacked = unpack(db, message, tM status = 'valid' except (struct.error) as e: status = 'invalid: truncated message' except (exceptions.AssetNameError, exceptions.AssetIDError) as e: status = 'invalid: invalid asset name/id' except (Exception) as e: status = 'invalid: couldn\'t unpack; %s' % e cursor = db.cursor() plain_sends = [] all_credits = [] if status == 'valid': for asset_id in unpacked: asset = util.geM t_asset_name(db, asset_id, tx['block_index']) except (exceptions.AssetNameError) as e: status = 'invalid: asset %s invalid at block index %i' % (asset_id, tx['block_index']) break cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], asset_id)) balances = cursor.fetchall() if not balances: status = 'invalid: insufficient funds for asset %s, addressM %s has no balance' % (asset_id, tx['source']) break credits = unpacked[asset_id] total_sent = reduce(lambda p, t: p + t[1], credits, 0) if balances[0]['quantity'] < total_sent: status = 'invalid: insufficient funds for asset %s, needs %i' % (asset_id, total_sent) break if status == 'valid': plain_sends += map(lambda t: util.py34TupleAppend(asset_id, t), credits) all_credits += map(lamM bda t: {"asset": asset_id, "destination": t[0], "quantity": t[1]}, credits) all_debits.append({"asset": asset_id, "quantity": total_sent}) if status == 'valid': problems = validate(db, tx['source'], plain_sends, tx['block_index']) if problems: status = 'invalid:' + '; '.join(problems) if status == 'valid': for op in all_credits: util.credit(db, op['destination'], op['asset'], op['quantity'], action='mpma send', event=tx['tx_hash']) for op in alM util.debit(db, tx['source'], op['asset'], op['quantity'], action='mpma send', event=tx['tx_hash']) # Enumeration of the plain sends needs to be deterministic, so we sort them by asset and then by address plain_sends = sorted(plain_sends, key=lambda x: ''.join([x[0], x[1]])) for i, op in enumerate(plain_sends): if len(op) > 3: memo_bytes = op[3] memo_bytes = None bindings = { 'tx_M index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'asset': op[0], 'destination': op[1], 'quantity': op[2], 'status': status, 'memo': memo_bytes, 'msg_index': i sql = 'insert into sends (tx_index, tx_hash, block_index, source, destination, asset, quantity, status, memo, msg_index) values(:txM _index, :tx_hash, :block_index, :source, :destination, :asset, :quantity, :status, :memo, :msg_index)' cursor.execute(sql, bindings) if status != 'valid': logger.warn("Not storing [mpma] tx [%s]: %s" % (tx['tx_hash'], status)) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 """Create and parse 'send'-type messages.""" logger = logging.getLogger(__name__) from ... import (confiM g, exceptions, util, message_type) def unpack(db, message, block_index): # Only used for `unpack` API call at the moment. asset_id, quantity = struct.unpack(FORMAT, message) asset = util.get_asset_name(db, asset_id, block_index) except struct.error: raise exceptions.UnpackError('could not unpack') except AssetNameError: raise exceptions.UnpackError('asset id invalid') 'asset': asset, 'quantity': quantity def validate (db, source, destination, asset, quantity, block_index): if asset == config.BTC: problems.append('cannot send bitcoins') # Only for parsing. if not isinstance(quantity, int): problems.append('quantity must be in satoshis') return problems if quantity < 0: problems.append('negative quantity') if quantity > config.MAX_INT: problems.append('integer M if util.enabled('send_destination_required'): # Protocol change. if not destination: problems.append('destination is required') if util.enabled('options_require_memo'): # Check destination address options cursor = db.cursor() results = cursor.execute('SELECT options FROM addresses WHERE address=?', (destination,)) result = results.fetchone() if result and util.active_options(result['options'], config.ADDREM SS_OPTION_REQUIRE_MEMO): problems.append('destination requires memo') cursor.close() def compose (db, source, destination, asset, quantity): cursor = db.cursor() # Just send BTC? if asset == config.BTC: return (source, [(destination, quantity)], None) # resolve subassets asset = util.resolve_subasset_longname(db, asset) #quantity must be in int satoshi (not float, string, etc) if not isinstance(quantity, int): raise exceptiM ons.ComposeError('quantity must be an int (in satoshi)') # Only for outgoing (incoming will overburn). balances = list(cursor.execute('''SELECT * FROM balances WHERE (address = ? AND asset = ?)''', (source, asset))) if not balances or balances[0]['quantity'] < quantity: raise exceptions.ComposeError('insufficient funds') block_index = util.CURRENT_BLOCK_INDEX problems = validate(db, source, destination, asset, quantity, block_index) if problems: raise exceptions.ComposeError(problM asset_id = util.get_asset_id(db, asset, block_index) data = message_type.pack(ID) data += struct.pack(FORMAT, asset_id, quantity) return (source, [(destination, None)], data) def parse (db, tx, message): cursor = db.cursor() # Unpack message. if len(message) != LENGTH: raise exceptions.UnpackError asset_id, quantity = struct.unpack(FORMAT, message) asset = util.get_asset_name(db, asset_id, tx['block_index']) except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: asset, quantity = None, None status = 'invalid: could not unpack' if status == 'valid': cursor.execute('''SELECT * FROM balances \ WHERE (address = ? AND asset = ?)''', (tx['source'], asset)) balances = cursor.fetchall() if not balances: status = 'invalid: insufficient funds' elif balances[0]['quantitM quantity = min(balances[0]['quantity'], quantity) quantity = min(quantity, config.MAX_INT) if status == 'valid': problems = validate(db, tx['source'], tx['destination'], asset, quantity, tx['block_index']) if problems: status = 'invalid: ' + '; '.join(problems) if status == 'valid': util.debit(db, tx['source'], asset, quantity, action='send', event=tx['tx_hash']) util.credit(db, tx['destination'], assetM , quantity, action='send', event=tx['tx_hash']) # Add parsed transaction to message-type 'tx_index': tx['tx_index'], 'tx_hash': tx['tx_hash'], 'block_index': tx['block_index'], 'source': tx['source'], 'destination': tx['destination'], 'asset': asset, 'quantity': quantity, 'status': status, if "integer overflow" not in status and "quantity must be in satoshis" not in status: sql = 'insert into seM nds (tx_index, tx_hash, block_index, source, destination, asset, quantity, status, memo) values(:tx_index, :tx_hash, :block_index, :source, :destination, :asset, :quantity, :status, NULL)' cursor.execute(sql, bindings) logger.warn("Not storing [send] tx [%s]: %s" % (tx['tx_hash'], status)) logger.debug("Bindings: %s" % (json.dumps(bindings), )) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 from bitcoin.core import key from functools import reduce from itertools import groupby from bitstring import BitArray, BitStream, ConstBitStream, ReadError logger = logging.getLogger(__name__) from counterpartylib.lib import (config, util, exceptions, util, message_type, address) ## encoding functions def _encode_constructBaseLUT(snds): # t is a tuple of the form (asset, addr, amnt [, memo, is_hex]) return sorted(list(set([t[1] for t in snM ds]))) # Sorted to make list determinist def _encode_constructBaseAssets(sends): # t is a tuple of the form (asset, addr, amnt [, memo, is_hex]) return sorted(list(set([t[0] for t in sends]))) # Sorted to make list determinist def _encode_constructLUT(sends): baseLUT = _encode_constructBaseLUT(sends) # What's this? It calculates the minimal number of bits needed to represent an item index inside the baseLUT lutNbits = math.ceil(math.log2(len(baseLUT))) "nbits": lutNbits,M "addrs": baseLUT def _encode_compressLUT(lut): return b''.join([struct.pack('>H', len(lut['addrs']))] + address.pack(addr) for addr in lut['addrs'] def _encode_memo(memo=None, is_hex=False): '''Tightly pack a memo as a Bit array''' if memo is not None: # signal a 1 bit for existence of the memo barr = BitArray('0b1') # signal a 1 bit for hex encoded memos barr.append('0b1') if type(memo) is str: # append the string as hex-string barr.append('uint:6=%i' % (len(memo) >> 1)) memo = '0x%s' % memo barr.append('uint:6=%i' % len(memo)) barr.append(memo) # signal a 0 bit for a string encoded memo barr.append('0b0') barr.append('uint:6=%i' % len(memo)) barr.append(BitArray(memo.encode('utf-8'))) # if the memo is M None, return just a 0 bit return BitArray('0b0') def _safe_tuple_index(t, i): '''Get an element from a tuple, returning None if it's out of bounds''' def _encode_constructSendList(send_asset, lut, sends): # t is a tuple of the form (asset, addr, amnt, memo, is_hex) # if there's no memo specified, memo and is_hex are None (lut['addrs'].index(t[1]), t[2], _safe_tuple_index(t, 3), _safe_tuple_index(t, 4)) for t in sends if t[0] == send_asset def _solve_asset(db, assetName, block_index): asset = util.resolve_subasset_longname(db, assetName) return util.get_asset_id(db, asset, block_index) def _encode_compressSendList(db, nbits, send, block_index): r.append('uintbe:64=%i' % _solve_asset(db, send['assetName'], block_index)) r.append('uint:%i=%i' % (nbits, len(send['sendList'])-1)) for sendItem in send['sendList']: idx = sendItem[0] r.append('uint:%i=%i' % (nbits, idx)) r.append('uintbe:64=%i' % amnt) memoStr = _encode_memo(memo=sendItem[2], is_hex=sendItem[3]) memoStr = BitArray('0b0') r.append(memoStr) def _encode_constructSends(sends): lut = _encode_constructLUT(sends) assets = _encode_constructBaseAssets(sends) "assetName": asset, "sendList": _encode_constructSendList(asset, lutM for asset in assets "sendLists": sendLists def _encode_compressSends(db, mpmaSend, block_index, memo=None, memo_is_hex=False): compressedLUT = _encode_compressLUT(mpmaSend['lut']) memo_arr = _encode_memo(memo, memo_is_hex).bin isends = '0b' + memo_arr + ''.join([ ''.join(['1', _encode_compressSendList(db, mpmaSend['lut']['nbits'], sendList, block_index).bin]) for sendList in mpmaSend['sendLists'] bstr = ''.join([isends, '0']) pad = '0' * ((8 - (len(bstr) - 2)) % 8) # That -2 is because the prefix 0b is there barr = BitArray(bstr + pad) return b''.join([ compressedLUT, def _encode_mpmaSend(db, sends, block_index, memo=None, memo_is_hex=False): mpma = _encode_constructSends(sends) send = _encode_compressSends(db, mpma, block_index, memo=memo, memo_is_hex=memo_is_hex) ## decoding functions def _decode_decodeLUT(data): ,) = struct.unpack('>H', data[0:2]) if numAddresses == 0: raise exceptions.DecodeError('address list can\'t be empty') addressList = [] bytesPerAddress = 21 for i in range(0, numAddresses): addr_raw = data[p:p+bytesPerAddress] addressList.append(address.unpack(addr_raw)) p += bytesPerAddress lutNbits = math.ceil(math.log2(numAddresses)) return addressList, lutNbits, data[p:] def _decode_decodeSendList(stream, nbits, lut, block_index): id = stream.read('uintbe:64') numRecipients = stream.read('uint:%i' % nbits) rangeLimit = numRecipients + 1 numRecipients = 1 rangeLimit = numRecipients asset = util.generate_asset_name(asset_id, block_index) for i in range(0, rangeLimit): if nbits > 0: idx = stream.read('uint:%i' % nbits) addr = lut[idx] amount = stream.read('uintbe:64') memo, is_hex M = _decode_memo(stream) if memo is not None: sendList.append((addr, amount, memo, is_hex)) sendList.append((addr, amount)) return asset, sendList def _decode_decodeSends(stream, nbits, lut, block_index): #stream = ConstBitStream(data) while stream.read('bool'): asset, sendList = _decode_decodeSendList(stream, nbits, lut, block_index) sends[asset] = sendList def _decode_memo(stream): if stream.read('booM is_hex = stream.read('bool') mlen = stream.read('uint:6') data = stream.read('bytes:%i' % mlen) if not(is_hex): # is an utf8 string data = data.decode('utf-8') return data, is_hex return None, None def _decode_mpmaSendDecode(data, block_index): lut, nbits, remain = _decode_decodeLUT(data) stream = ConstBitStream(remain) memo, is_hex = _decode_memo(stream) sends = _decode_decodeSends(stream, nbits, lut, block_inM, if memo is not None: for asset in sends: sendList = sends[asset] for idx, send in enumerate(sendList): if len(send) == 2: sendList[idx] = (send[0], send[1], memo, is_hex) AUUUUUUUUUUUHUUUUUUUUUUUUUUUUUUi !22222222222222222222222222222222222222222222222222 TUUUUUUUUUUUUUUUUUUUUUUUUUUUU FPUUUUUUUUUUUUUUUUUUUUUUUUUUUUU text/plain;charset=utf-8 SjLPQuesto dovrebbe essere il limite per gli 80 caratteri di un testo op_return ciao FjDOUT:2C73338A350D8C3395F29B96DB61A2295FC00D2FA324697D20F40623CBF42E54 3e48013a564f5d725e88da2709abb6c7G0D 4ae201a374e28e0b41697e7a5cc8a9baH0E ()*89:HIJWXYZghijwxyz ()*789:FGHIJUVWXYZdefghijstuvwxyz c/Foundry USA Pool #dropgold/ EjCs:ETH.ETH:0x18A99CF66D9d80837Ee21734fA5C3d4D4686bb1A:261054731:ss:0 DjB=:ETH.ETH:0x6b749E0134288737dfc4827ba6D90A54ab7346F6:12723469:te:0 IjGREFUND:AB61703B275F8235DF30734970CB8FDA87E5CF161D6CF5E7F56A4BC5159ADFE6 Copyright Apple Inc., 2023 %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz &'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz )4)))))4>444444>>>>>>>>KKKKKKWWWWWbbbbbbbbbb +fE9Effffffffffffffffffffffffffffffffffffffffffffffffff http://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 9.0-c000 79.171c27fab, 2022/08/16-22:35:41 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmpMM:OriginalDocumentID="xmpM .did:8187f80b-58da-b441-beb6-f4facd64d755" xmpMM:DocumentID="xmp.did:49D755B5A32D11ED9141BCEBB78737A9" xmpMM:InstanceID="xmp.iid:49D755B4A32D11ED9141BCEBB78737A9" xmp:CreatorTool="Adobe Photoshop 24.1 (Windows)"> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:cee66003-89ea-6e42-8659-2c4688cd6775" stRef:documentID="xmp.did:8187f80b-58da-b441-beb6-f4facd64d755"/> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="r"?> &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& This is better than a JPG MjK=:BNB.BUSD-BD1:bnb1aaahj4ye2rgavhqfm9cwgzem2tcs9xpt2f2n94:466681064925:t:30] Bj@=:ETH.ETH:0x6eb4978c5d07379Bc7759F21f30B2b53EDBA0491:455695:te:0 Ahttp://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.5.0"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" photoshop:ColorMode="3" photoshop:ICCProfile="M sRGB IEC61966-2.1" xmp:ModifyDate="2023-02-02T17:30:32-05:00" xmp:MetadataDate="2023-02-02T17:30:32-05:00"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="produced" stEvt:softwareAgent="Affinity Photo 1.10.6" stEvt:when="2023-02-02T17:30:32-05:00"/> </rdf:Seq> </xmpMM:History> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> +?7BA>7<;ENcTEI^K;<VvW^gjopoCSz 3kG<Gkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk Ahttp://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.5.0"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" photoshop:ColorMode="3" photoshop:M ICCProfile="sRGB IEC61966-2.1" xmp:ModifyDate="2023-02-02T17:30:06-05:00" xmp:MetadataDate="2023-02-02T17:30:06-05:00"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="produced" stEvt:softwareAgent="Affinity Photo 1.10.6" stEvt:when="2023-02-02T17:30:06-05:00"/> </rdf:Seq> </xmpMM:History> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> +?7BA>7<;ENcTEI^K;<VvW^gjopoCM 3kG<Gkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk c/SBICrypto.com Pool/ MjK=:BNB.BUSD-BD1:bnb1aaahj4ye2rgavhqfm9cwgzem2tcs9xpt2f2n94:467933815166:t:30 SjLPs:ETH.USDC-3606EB48:0x18A99CF66D9d80837Ee21734fA5C3d4D4686bb1A:214007541697:ss:0 9j7+:BTC/BTC::bc1q3f787hr38pmal87yxtpq8tng09q60ljjqqd759:0V Aj?=:BNB.BNB:bnb1em5ake82afp2u6xtrnl9x879689gra3ezku0cz:17364:te:0 Aj?=:ETH.ETH:0x6eb4978c5d07379Bc7759F21f30B2b53EDBA0491:39697:te:0 CjA=:BNB.BNB:bnb15cy0flkvuhuvsjpsp0tphsauprxguuzj8ekuqv:2782480:te:0 EjC=:BNB.BTCB-1DE:bnb1l65zmzsteq2swtncp74f3etcmmsjfsqu7hdrkh:7482:te:0 IjGREFUND:CEEED9FCC85F8260DF2604F5C4DD4F7F7582602EEA9E2880D11B1E5E0E4A5278 FjDOUT:7FED7BBEDB95F8D99A6E191E785B3DCAAF3054152FDC6944CF952C2BD6090EE7 FjDOUT:21FECF0DFDDCE4C43C593F46B9C53203B3B81A5A0200F648C2A60E1FD2D9F237 FjDOUT:9D1038CC280A43152E0414C3BC25CD7B000DFA4CA3C336A8A1E27DCA8C2F6EC5 IjGREFUND:04BB70BADD38ACFB6DB78A85928267CB76D9F38DE42C25E1F10F9EC37C6C38A7 Bj@=:ETH.ETH:0x2E3E96d59d3052A1B73B579f777c5241441A9E93:194320:te:0 text/plain;charset=utf-8 INDUSTRIAL SOCIETY AND ITS FUTURE 1. The Industrial Revolution and its consequences have been a disaster for the human race. They have greatly increased the life-expectancy of those of us countries, but they have destabilized society, have made life unfulfilling, have subjected human beings to indignities, have led to widespread psychological suffering (in the Third World to physical suffering as well) and have inflicted severe damage on the natural worlM continued development of technology will worsen the situation. It will certainly subject human beings to greater indignities and inflict greater damage on the natural world, it will probably lead to greater social disruption and psychological suffering, and it may lead to increased physical suffering even in 2. The industrial-technological system may survive or it may break down. If it survives, it MAY eventually achieve a low level of physical and psychologM suffering, but only after passing through a long and very painful period of adjustment and only at the cost of permanently reducing human beings and many other living organisms to engineered products and mere cogs in the social machine. Furthermore, if the system survives, the consequences will be inevitable: There is no way of reforming or modifying the system so as to prevent it from depriving people of dignity and autonomy. 3. If the system breaks down the consequences will still beM the bigger the system grows the more disastrous the results of its breakdown will be, so if it is to break down it had best break down sooner rather than 4. We therefore advocate a revolution against the industrial system. This revolution may or may not make use of violence; it may be sudden or it may be a relatively gradual process spanning a few decades. We can that. But we do outline in a very general way the measures that those who he industrial system should take in order to prepare the way for a revolution against that form of society. This is not to be a POLITICAL revolution. Its object will be to overthrow not governments but the economic and technological basis of the present society. 5. In this article we give attention to only some of the negative developments that have grown out of the industrial-technological system. Other such developments we mention only briefly or ignore altogether. This does not mean we regard these other developments as unimportant. For practical reasons we have to confine our discussion to areas that have received insufficient public attention or in which we have something new to say. For example, since there are well-developed environmental and wilderness movements, we have written very little about environmental degradation or the destruction of wild nature, even though we consider these to be highly important. THE PSYCHOLOGY OF MODERN LEFTISM 6. Almost everyone will M agree that we live in a deeply troubled society. One of the most widespread manifestations of the craziness of our world is leftism, so a discussion of the psychology of leftism can serve as an introduction to the discussion of the problems of modern society in general. 7. But what is leftism? During the first half of the 20th century leftism could have been practically identified with socialism. Today the movement is fragmented and it is not clear who can properly be called a leftist. When we M speak of leftists in this article we have in mind mainly socialists, types, feminists, gay and disability activists, animal rights activists and the like. But not everyone who is associated with one of these movements is a leftist. What we are trying to get at in discussing leftism is not so much movement or an ideology as a psychological type, or rather a collection of related types. Thus, what we will emerge more clearly inM the course of our discussion of leftist psychology. (Also, see paragraphs 227-230.) 8. Even so, our conception of leftism will remain a good deal less clear than we would wish, but there doesn t seem to be any remedy for this. All we are trying to do here is indicate in a rough and approximate way the two psychological tendencies that we believe are the main driving force of modern leftism. We by no means claim to be telling the WHOLE truth about leftist psychology. Also, our discussion M is meant to apply to modern leftism only. We leave open the question of the extent to which our discussion could be applied to the leftists of the 19th and early 20th centuries. 9. The two psychological tendencies that underlie modern leftism we call feelings of inferiority Feelings of inferiority are characteristic of modern leftism as a whole, while oversocialization is characteristic only of a certain segment of modern leftism; but this segment FEELINGS OF INFERIORITY feelings of inferiority we mean not only inferiority feelings in the strict sense but a whole spectrum of related traits; low self-esteem, feelings of powerlessness, depressive tendencies, defeatism, guilt, self- hatred, etc. We argue that modern leftists tend to have some such feelings (possibly more or less repressed) and that these feelings are decisive in determining the direction of modern leftism. 11. When someone interprets as deM rogatory almost anything that is said about him (or about groups with whom he identifies) we conclude that he has inferiority feelings or low self-esteem. This tendency is pronounced among minority rights activists, whether or not they belong to the minority groups whose rights they defend. They are hypersensitive about the words used to designate minorities and about anything that is said concerning minorities. Asian, a disabled person or a woman originally had no derogatory were merely the feminine equivalents of The negative connotations have been attached to these terms by the activists themselves. Some animal rights activists have gone so far as to reject the word and insist on its replacement by Leftish anthropologists go to great lengths to avoid anything about primitive peoples that could conceivably be interpreted as negative. They want to replace the world They seem almost paranoid about anything that might suggest that any primitive culture is inferior to our own. (We do not mean to imply that primitive cultures ARE inferior to ours. We merely point out the hypersensitivity of leftish anthropologists.) 12. Those who are most sensitive about politically incorrect not the average black ghetto- dweller, Asian immigrant, abused woman or disabled person, but a minority of activists, many of whom do not even group but come from privileged strata of society. Political correctness has its stronghold among university professors, who have secure employment with comfortable salaries, and the majority of whom are heterosexual white males from middle- to upper-middle-class families. 13. Many leftists have an intense identificationM with the problems of groups that have an image of being weak (women), defeated (American Indians), repellent (homosexuals) or otherwise inferior. The leftists themselves feel that these groups are inferior. They would never admit to themselves that they have such feelings, but it is precisely because they do see these groups as inferior that they identify with their problems. (We do not mean to suggest that women, Indians, etc. ARE inferior; we are only making a point about leftiM 14. Feminists are desperately anxious to prove that women are as strong and as capable as men. Clearly they are nagged by a fear that women may NOT be as strong and as capable as men. 15. Leftists tend to hate anything that has an image of being strong, good and successful. They hate America, they hate Western civilization, they hate white males, they hate rationality. The reasons that leftists give for hating the West, etc. clearly do not correspond with their real motiveM They SAY they hate the West because it is warlike, imperialistic, sexist, ethnocentric and so forth, but where these same faults appear in socialist countries or in primitive cultures, the leftist finds excuses for them, or at best he GRUDGINGLY admits that they exist; whereas he ENTHUSIASTICALLY points out (and often greatly exaggerates) these faults where they appear in Western civilization. Thus it is clear that these faults are not the s real motive for hating AmeM rica and the West. He hates America and the West because they are strong and successful. etc., play little role in the liberal and leftist vocabulary. The leftist is anti-individualistic, pro-collectivist. He wants society to s problems for them, satisfy everyone s needs for them, take care of them. He is not the sort of person who has an inner sense of fidence in his ability to solve his own problems and satisfy his own needs. The leftist is antagonistic to the concept of competition because, deep inside, he feels like a loser. 17. Art forms that appeal to modern leftish intellectuals tend to focus on sordidness, defeat and despair, or else they take an orgiastic tone, throwing off rational control as if there were no hope of accomplishing anything through rational calculation and all that was left was to immerse oneself in the sensM ations of the moment. 18. Modern leftish philosophers tend to dismiss reason, science, objective reality and to insist that everything is culturally relative. It is true that one can ask serious questions about the foundations of scientific knowledge and about how, if at all, the concept of objective reality can be defined. But it is obvious that modern leftish philosophers are not simply cool-headed logicians systematically analyzing the foundations of knowledge. They are deeply invoM lved emotionally in their attack on truth and reality. They attack these concepts because of their own psychological needs. For one thing, their attack is an outlet for hostility, and, to the extent that it is successful, it satisfies the drive for power. More importantly, the leftist hates science and rationality because they classify certain beliefs as true (i.e., successful, superior) and other beliefs as false (i.e., failed, inferior). The leftist s feelings of inferiority run soM cannot tolerate any classification of some things as successful or superior and other things as failed or inferior. This also underlies the rejection by many leftists of the concept of mental illness and of the utility of IQ tests. Leftists are antagonistic to genetic explanations of human abilities or behavior because such explanations tend to make some persons appear superior or inferior to others. Leftists prefer to give society the credit or blame for an individuM s ability or lack of it. Thus if a person is it is not his fault, but society s, because he has not been brought up properly. 19. The leftist is not typically the kind of person whose feelings of inferiority make him a braggart, an egotist, a bully, a self-promoter, a ruthless competitor. This kind of person has not wholly lost faith in himself. He has a deficit in his sense of power and self-worth, but he can still conceive of himself as having the capacity to bM efforts to make himself strong produce his unpleasant behavior. [1] But the leftist is too far gone for that. His feelings of inferiority are so ingrained that he cannot conceive of himself as individually strong and valuable. Hence the collectivism of the leftist. He can feel strong only as a member of a large organization or a mass movement with which he identifies 20. Notice the masochistic tendency of leftist tactics. Leftists protest by in front of vehicles, they intentionally provoke police or racists to abuse them, etc. These tactics may often be effective, but many leftists use them not as a means to an end but because they PREFER masochistic tactics. Self-hatred is a leftist trait. 21. Leftists may claim that their activism is motivated by compassion or by moral principles, and moral principle does play a role for the leftist of the oversocialized type. But compassion and moral principle cannot be the s for leftist activism. Hostility is too prominent a component of leftist behavior; so is the drive for power. Moreover, much leftist behavior is not rationally calculated to be of benefit to the people whom the leftists claim to be trying to help. For example, if one believes that affirmative action is good for black people, does it make sense to demand affirmative action in hostile or dogmatic terms? Obviously it would be more productive to take a diplomatic and conciliatory approachM that would make at least verbal and symbolic concessions to white people who think that affirmative action discriminates against them. But leftist activists do not take such an approach because it would not satisfy their emotional needs. Helping black people is not their real goal. Instead, race problems serve as an excuse for them to express their own hostility and frustrated need for power. In doing so they actually harm black people, because the activists hostile attitude tM oward the white majority tends to intensify race hatred. 22. If our society had no social problems at all, the leftists would have to INVENT problems in order to provide themselves with an excuse for making 23. We emphasize that the foregoing does not pretend to be an accurate description of everyone who might be considered a leftist. It is only a rough indication of a general tendency of leftism. 24. Psychologists use the term to designate the pM children are trained to think and act as society demands. A person is said to be well socialized if he believes in and obeys the moral code of his society and fits in well as a functioning part of that society. It may seem senseless to say that many leftists are oversocialized, since the leftist is perceived as a rebel. Nevertheless, the position can be defended. Many leftists are not such rebels as they seem. 25. The moral code of our society is so demanding that no oM and act in a completely moral way. For example, we are not supposed to hate anyone, yet almost everyone hates somebody at some time or other, whether he admits it to himself or not. Some people are so highly socialized that the attempt to think, feel and act morally imposes a severe burden on them. In order to avoid feelings of guilt, they continually have to deceive themselves about their own motives and find moral explanations for feelings and actions that in M reality have a non-moral origin. We use the term to describe such people. [2] 26. Oversocialization can lead to low self-esteem, a sense of powerlessness, defeatism, guilt, etc. One of the most important means by which our society socializes children is by making them feel ashamed of behavior or speech that is contrary to society s expectations. If this is overdone, or if a particular child is especially susceptible to such feelings, he ends by feeling ashamed oM f HIMSELF. Moreover the thought and the behavior of the oversocialized person are more restricted by society s expectations than are those of the lightly socialized person. The majority of people engage in a significant amount of naughty behavior. They lie, they commit petty thefts, they break traffic laws, they goof off at work, they hate someone, they say spiteful things or they use some underhanded trick to get ahead of the other guy. The oversocialized person cannot do these thinM does do them he generates in himself a sense of shame and self-hatred. The oversocialized person cannot even experience, without guilt, thoughts or feelings that are contrary to the accepted morality; he cannot think thoughts. And socialization is not just a matter of morality; we are socialized to conform to many norms of behavior that do not fall under the heading of morality. Thus the oversocialized person is kept on a psychological leash and spends hM is life running on rails that society has laid down for him. In many oversocialized people this results in a sense of constraint and powerlessness that can be a severe hardship. We suggest that oversocialization is among the more serious cruelties that human beings inflict on one another. 27. We argue that a very important and influential segment of the modern left is oversocialized and that their oversocialization is of great importance in determining the direction of modern leftism.M Leftists of the oversocialized type tend to be intellectuals or members of the upper-middle class. Notice that university intellectuals [3] constitute the most highly socialized segment of our society and also the most left-wing segment. 28. The leftist of the oversocialized type tries to get off his psychological leash and assert his autonomy by rebelling. But usually he is not strong enough to rebel against the most basic values of society. Generally speaking, the goals of today s leftists are NOT in conflict with the accepted morality. On the contrary, the left takes an accepted moral principle, adopts it as its own, and then accuses mainstream society of violating that principle. Examples: racial equality, equality of the sexes, helping poor people, peace as opposed to war, nonviolence generally, freedom of expression, kindness to animals. More fundamentally, the duty of the individual to serve society and the duty of society to take care of the ual. All these have been deeply rooted values of our society (or at least of its middle and upper classes [4] for a long time. These values are explicitly or implicitly expressed or presupposed in most of the material presented to us by the mainstream communications media and the educational system. Leftists, especially those of the oversocialized type, usually do not rebel against these principles but justify their hostility to society by claiming (with some degree of truth) that sociM ety is not living up to these 29. Here is an illustration of the way in which the oversocialized leftist shows his real attachment to the conventional attitudes of our society while pretending to be in rebellion against it. Many leftists push for affirmative action, for moving black people into high-prestige jobs, for improved education in black schools and more money for such schools; the way of life they regard as a social disgrace. They wanM integrate the black man into the system, make him a business executive, a lawyer, a scientist just like upper-middle-class white people. The leftists will reply that the last thing they want is to make the black man into a copy of the white man; instead, they want to preserve African American culture. But in what does this preservation of African American culture consist? It can hardly consist in anything more than eating black-style food, listening to black-style music, wearM clothing and going to a black- style church or mosque. In other words, it can express itself only in superficial matters. In all ESSENTIAL respects most leftists of the oversocialized type want to make the black man conform to white, middle-class ideals. They want to make him study technical subjects, become an executive or a scientist, spend his life climbing the status ladder to prove that black people are as good as white. They want to make black fathers they want black gangs to become nonviolent, etc. But these are exactly the values of the industrial-technological system. The system couldn t care less what kind of music a man listens to, what kind of clothes he wears or what religion he believes in as long as he studies in school, holds a respectable job, climbs the status ladder, is a parent, is nonviolent and so forth. In effect, however much he may deny it, the oversocialized leftist wants to ntegrate the black man into the system and make him adopt its values. 30. We certainly do not claim that leftists, even of the oversocialized type, NEVER rebel against the fundamental values of our society. Clearly they sometimes do. Some oversocialized leftists have gone so far as to rebel against one of modern society s most important principles by engaging in physical violence. By their own account, violence is for them a form of In other words, by committing violencM e they break through the psychological restraints that have been trained into them. Because they are oversocialized these restraints have been more confining for them than for others; hence their need to break free of them. But they usually justify their rebellion in terms of mainstream values. If they engage in violence they claim to be fighting against racism or the like. 31. We realize that many objections could be raised to the foregoing thumbnail sketch of leftist psychology. TheM real situation is complex, and anything like a complete description of it would take several volumes even if the necessary data were available. We claim only to have indicated very roughly the two most important tendencies in the psychology of modern leftism. 32. The problems of the leftist are indicative of the problems of our society as a whole. Low self-esteem, depressive tendencies and defeatism are not restricted to the left. Though they are especially noticeable in the left, ey are widespread in our society. And today s society tries to socialize us to a greater extent than any previous society. We are even told by experts how to eat, how to exercise, how to make love, how to raise our kids 33. Human beings have a need (probably based in biology) for something that we This is closely related to the need for power (which is widely recognized) but is not quite the same thing. The power ocess has four elements. The three most clear-cut of these we call goal, effort and attainment of goal. (Everyone needs to have goals whose attainment requires effort, and needs to succeed in attaining at least some of his goals.) The fourth element is more difficult to define and may not be necessary for everyone. We call it autonomy and will discuss it later (paragraphs 42-44). 34. Consider the hypothetical case of a man who can have anything he wants just by wishing for it. Such a M man has power, but he will develop serious psychological problems. At first he will have a lot of fun, but by and by he will become acutely bored and demoralized. Eventually he may become clinically depressed. History shows that leisured aristocracies tend to become decadent. This is not true of fighting aristocracies that have to struggle to maintain their power. But leisured, secure aristocracies that have no need to exert themselves usually become bored, hedonistic and zed, even though they have power. This shows that power is not enough. One must have goals toward which to exercise one 35. Everyone has goals; if nothing else, to obtain the physical necessities of life: food, water and whatever clothing and shelter are made necessary by the climate. But the leisured aristocrat obtains these things without effort. Hence his boredom and demoralization. 36. Nonattainment of important goals results in death if the goals are physical and in frustration if nonattainment of the goals is compatible with survival. Consistent failure to attain goals throughout life results in defeatism, low self-esteem or depression. 37, Thus, in order to avoid serious psychological problems, a human being needs goals whose attainment requires effort, and he must have a reasonable rate of success in attaining his goals. SURROGATE ACTIVITIES 38. But not every leisured aristocrat becomes bored and demoralized. For example, the emperor HirohM ito, instead of sinking into decadent hedonism, devoted himself to marine biology, a field in which he became distinguished. When people do not have to exert themselves to satisfy their physical needs they often set up artificial goals for themselves. In many cases they then pursue these goals with the same energy and emotional involvement that they otherwise would have put into the search for physical necessities. Thus the aristocrats of the Roman Empire had their literary pretensionsM European aristocrats a few centuries ago invested tremendous time and energy in hunting, though they certainly didn t need the meat; other aristocracies have competed for status through elaborate displays of wealth; and a few aristocrats, like Hirohito, have turned to science. 39. We use the term to designate an activity that is directed toward an artificial goal that people set up for themselves merely in order to have some goal to work toward, or leM t us say, merely for the that they get from pursuing the goal. Here is a rule of thumb for the identification of surrogate activities. Given a person who devotes much time and energy to the pursuit of goal X, ask yourself this: If he had to devote most of his time and energy to satisfying his biological needs, and if that effort required him to use his physical and mental faculties in a varied and interesting way, would he feel seriously deprived becaM use he did not attain goal X? If the answer is no, s pursuit of goal X is a surrogate activity. Hirohito studies in marine biology clearly constituted a surrogate activity, since it is pretty certain that if Hirohito had had to spend his time working at interesting non-scientific tasks in order to obtain the necessities of life, he would not have felt deprived because he didn t know all about the anatomy and life-cycles of marine animals. On the other hand the pM love (for example) is not a surrogate activity, because most people, even if their existence were otherwise satisfactory, would feel deprived if they passed their lives without ever having a relationship with a member of the opposite sex. (But pursuit of an excessive amount of sex, more than one really needs, can be a surrogate activity.) 40. In modern industrial society only minimal effort is necessary to satisfy s physical needs. It is enough to go through a M training program to acquire some petty technical skill, then come to work on time and exert the very modest effort needed to hold a job. The only requirements are a moderate amount of intelligence and, most of all, simple OBEDIENCE. If one has those, society takes care of one from cradle to grave. (Yes, there is an underclass that cannot take the physical necessities for granted, but we are speaking here of mainstream society.) Thus it is not surprising that modern society is fullM of surrogate activities. These include scientific work, athletic achievement, humanitarian work, artistic and literary creation, climbing the corporate ladder, acquisition of money and material goods far beyond the point at which they cease to give any additional physical satisfaction, and social activism when it addresses issues that are not important for the activist personally, as in the case of white activists who work for the rights of nonwhite minorities. These are not always PUM surrogate activities, since for many people they may be motivated in part by needs other than the need to have some goal to pursue. Scientific work may be motivated in part by a drive for prestige, artistic creation by a need to express feelings, militant social activism by hostility. But for most people who pursue them, these activities are in large part surrogate activities. For example, the majority of scientists will probably agree that the heir work is more important than the money and prestige they earn. 41. For many if not most people, surrogate activities are less satisfying than the pursuit of real goals (that is, goals that people would want to attain even if their need for the power process were already fulfilled). One indication of this is the fact that, in many or most cases, people who are deeply involved in surrogate activities are never satisfied, never at rest. Thus the money-maker constantly strives for morM e and more wealth. The scientist no sooner solves one problem than he moves on to the next. The long-distance runner drives himself to run always farther and faster. Many people who pursue surrogate activities will say that they get far more fulfillment from these activities than they do from the of satisfying their biological needs, but that is because in our society the effort needed to satisfy the biological needs has been reduced to triviality. More impoM rtantly, in our society people do not satisfy their biological needs AUTONOMOUSLY but by functioning as parts of an immense social machine. In contrast, people generally have a great deal of autonomy in pursuing their surrogate activities. 42. Autonomy as a part of the power process may not be necessary for every individual. But most people need a greater or lesser degree of autonomy in working toward their goals. Their efforts must be undertaken on their own initiative and M must be under their own direction and control. Yet most people do not have to exert this initiative, direction and control as single individuals. It is usually enough to act as a member of a SMALL group. Thus if half a dozen people discuss a goal among themselves and make a successful joint effort to attain that goal, their need for the power process will be served. But if they work under rigid orders handed down from above that leave them no room for autonomous decision and initiativeM for the power process will not be served. The same is true when decisions are made on a collective basis if the group making the collective decision is so large that the role of each individual is insignificant. [5] 43. It is true that some individuals seem to have little need for autonomy. Either their drive for power is weak or they satisfy it by identifying themselves with some powerful organization to which they belong. And then there are unthinking, animal typesM who seem to be satisfied with a purely physical sense of power (the good combat soldier, who gets his sense of power by developing fighting skills that he is quite content to use in blind obedience to his superiors). 44. But for most people it is through the power process having a goal, making an AUTONOMOUS effort and attaining the goal that self-esteem, self-confidence and a sense of power are acquired. When one does not have adequate opportunity to go through the power process M the consequences are (depending on the individual and on the way the power process is disrupted) boredom, demoralization, low self-esteem, inferiority feelings, defeatism, depression, anxiety, guilt, frustration, hostility, spouse or child abuse, insatiable hedonism, abnormal sexual behavior, sleep disorders, eating disorders, etc. [6] SOURCES OF SOCIAL PROBLEMS 45. Any of the foregoing symptoms can occur in any society, but in modern industrial society they are present on a massive M to mention that the world today seems to be going crazy. This sort of thing is not normal for human societies. There is good reason to believe that primitive man suffered from less stress and frustration and was better satisfied with his way of life than modern man is. It is true that not all was sweetness and light in primitive societies. Abuse of women was common among the Australian aborigines, transexuality was fairly common among some can Indian tribes. But it does appear that GENERALLY SPEAKING the kinds of problems that we have listed in the preceding paragraph were far less common among primitive peoples than they are in modern society. 46. We attribute the social and psychological problems of modern society to the fact that that society requires people to live under conditions radically different from those under which the human race evolved and to behave in ways that conflict with the patterns of behavior that the M developed while living under the earlier conditions. It is clear from what we have already written that we consider lack of opportunity to properly experience the power process as the most important of the abnormal conditions to which modern society subjects people. But it is not the only one. Before dealing with disruption of the power process as a source of social problems we will discuss some of the other sources. 47. Among the abnormal conditions present in modern indusM excessive density of population, isolation of man from nature, excessive rapidity of social change and the breakdown of natural small-scale communities such as the extended family, the village or the tribe. 48. It is well known that crowding increases stress and aggression. The degree of crowding that exists today and the isolation of man from nature are consequences of technological progress. All pre-industrial societies were predominantly rural. The Industrial RevoM lution vastly increased the size of cities and the proportion of the population that lives in them, and modern agricultural technology has made it possible for the Earth to support a far denser population than it ever did before. (Also, technology exacerbates the effects of crowding because it puts increased disruptive powers in people hands. For example, a variety of noise- making devices: power mowers, radios, motorcycles, etc. If the use of these devices is unrestricted, ople who want peace and quiet are frustrated by the noise. If their use is restricted, people who use the devices are frustrated by the regulations. But if these machines had never been invented there would have been no conflict and no frustration generated by them.) 49. For primitive societies the natural world (which usually changes only slowly) provided a stable framework and therefore a sense of security. In the modern world it is human society that dominates nature rather than the other way around, and modern society changes very rapidly owing to technological change. Thus there is no stable framework. 50. The conservatives are fools: They whine about the decay of traditional values, yet they enthusiastically support technological progress and economic growth. Apparently it never occurs to them that you can rapid, drastic changes in the technology and the economy of a society without causing rapid changes in all other aspects of the society as well, and that such rapid changes inevitably break down traditional values. 51. The breakdown of traditional values to some extent implies the breakdown of the bonds that hold together traditional small-scale social groups. The disintegration of small-scale social groups is also promoted by the fact that modern conditions often require or tempt individuals to move to new locations, separating themselves from their communities. Beyond that, a technological society HAS TO weaken family ties and loM it is to function efficiently. In modern society an individual must be first to the system and only secondarily to a small-scale community, because if the internal loyalties of small-scale communities were stronger than loyalty to the system, such communities would pursue their own advantage at the expense of the system. 52. Suppose that a public official or a corporation executive appoints his cousin, his friend or his co- religionist to a position rathM appointing the person best qualified for the job. He has permitted personal loyalty to supersede his loyalty to the system, and that is both of which are terrible sins in modern society. Would-be industrial societies that have done a poor job of subordinating personal or local loyalties to loyalty to the system are usually very inefficient. (Look at Latin America.) Thus an advanced industrial society can tolerate only those small-scaM le communities that are emasculated, tamed and made into tools of the system. [7] 53. Crowding, rapid change and the breakdown of communities have been widely recognized as sources of social problems. But we do not believe they are enough to account for the extent of the problems that are seen today. 54. A few pre-industrial cities were very large and crowded, yet their inhabitants do not seem to have suffered from psychological problems to the same extent as modern man. In America today M there still are uncrowded rural areas, and we find there the same problems as in urban areas, though the problems tend to be less acute in the rural areas. Thus crowding does not seem to be the decisive factor. 55. On the growing edge of the American frontier during the 19th century, the mobility of the population probably broke down extended families and small-scale social groups to at least the same extent as these are broken down today. In fact, many nuclear families lived by choicM isolation, having no neighbors within several miles, that they belonged to no community at all, yet they do not seem to have developed problems as 56. Furthermore, change in American frontier society was very rapid and deep. A man might be born and raised in a log cabin, outside the reach of law and order and fed largely on wild meat; and by the time he arrived at old age he might be working at a regular job and living in an ordered community with w enforcement. This was a deeper change than that which typically occurs in the life of a modern individual, yet it does not seem to have led to psychological problems. In fact, 19th century American society had an optimistic and self-confident tone, quite unlike that of today 57. The difference, we argue, is that modern man has the sense (largely justified) that change is IMPOSED on him, whereas the 19th century frontiersman had the sense (also largely justified) thaM t he created change himself, by his own choice. Thus a pioneer settled on a piece of land of his own choosing and made it into a farm through his own effort. In those days an entire county might have only a couple of hundred inhabitants and was a far more isolated and autonomous entity than a modern county is. Hence the pioneer farmer participated as a member of a relatively small group in the creation of a new, ordered community. One may well question whether the creation of thisM community was an improvement, but at any rate it satisfied s need for the power process. 58. It would be possible to give other examples of societies in which there has been rapid change and/or lack of close community ties without the kind of massive behavioral aberration that is seen in today s industrial society. We contend that the most important cause of social and psychological problems in modern society is the fact that people have insufficient opportunity to through the power process in a normal way. We don modern society is the only one in which the power process has been disrupted. Probably most if not all civilized societies have interfered with the power process to a greater or lesser extent. But in modern industrial society the problem has become particularly acute. Leftism, at least in its recent (mid- to late-20th century) form, is in part a symptom of deprivation with respect to the power process. OF THE POWER PROCESS IN MODERN SOCIETY 59. We divide human drives into three groups: (1) those drives that can be satisfied with minimal effort; (2) those that can be satisfied but only at the cost of serious effort; (3) those that cannot be adequately satisfied no matter how much effort one makes. The power process is the process of satisfying the drives of the second group. The more drives there are in the third group, the more there is frustration, anger, eventually defeatism, 60. In modern industrial society natural human drives tend to be pushed into the first and third groups, and the second group tends to consist increasingly of artificially created drives. 61. In primitive societies, physical necessities generally fall into group 2: They can be obtained, but only at the cost of serious effort. But modern society tends to guaranty the physical necessities to everyone [9] in exchange for only minimal effort, hence physical needs are pushed into M 1. (There may be disagreement about whether the effort needed to hold a job ; but usually, in lower- to middle- level jobs, whatever effort is required is merely that of OBEDIENCE. You sit or stand where you are told to sit or stand and do what you are told to do in the way you are told to do it. Seldom do you have to exert yourself seriously, and in any case you have hardly any autonomy in work, so that the need for the power process is not . Social needs, such as sex, love and status, often remain in group 2 in modern society, depending on the situation of the individual. [10] But, except for people who have a particularly strong drive for status, the effort required to fulfill the social drives is insufficient to satisfy adequately the need for the power process. 63. So certain artificial needs have been created that fall into group 2, hence serve the need for the power process. Advertising and marketing techniques e been developed that make many people feel they need things that their grandparents never desired or even dreamed of. It requires serious effort to earn enough money to satisfy these artificial needs, hence they fall into group 2. (But see paragraphs 80-82.) Modern man must satisfy his need for the power process largely through pursuit of the artificial needs created by the advertising and marketing industry [11], and through surrogate 64. It seems that for many people, mM aybe the majority, these artificial forms of the power process are insufficient. A theme that appears repeatedly in the writings of the social critics of the second half of the 20th century is the sense of purposelessness that afflicts many people in modern society. (This purposelessness is often called by other names such as middle-class vacuity. ) We suggest that the so-called actually a search for a sense of purpose, often for commitmentM surrogate activity. It may be that existentialism is in large part a response to the purposelessness of modern life. [12] Very widespread in modern society is the search for But we think that for the majority of people an activity whose main goal is fulfillment (that is, a surrogate activity) does not bring completely satisfactory fulfillment. In other words, it does not fully satisfy the need for the power process. (See paragraph 41.) That need cM an be fully satisfied only through activities that have some external goal, such as physical necessities, sex, love, status, 65. Moreover, where goals are pursued through earning money, climbing the status ladder or functioning as part of the system in some other way, most people are not in a position to pursue their goals AUTONOMOUSLY. Most workers are s employee and, as we pointed out in paragraph 61, must spend their days doing what they are told to do M in the way they are told to do it. Even people who are in business for themselves have only limited autonomy. It is a chronic complaint of small-business persons and entrepreneurs that their hands are tied by excessive government regulation. Some of these regulations are doubtless unnecessary, but for the most part government regulations are essential and inevitable parts of our extremely complex society. A large portion of small business today operates on the franchise was reported in the Wall Street Journal a few years ago that many of the franchise-granting companies require applicants for franchises to take a personality test that is designed to EXCLUDE those who have creativity and initiative, because such persons are not sufficiently docile to go along obediently with the franchise system. This excludes from small business many of the people who most need autonomy. 66. Today people live more by virtue of what the system does FOR them or TO them than by virtue of what they do for themselves. And what they do for themselves is done more and more along channels laid down by the system. Opportunities tend to be those that the system provides, the opportunities must be exploited in accord with rules and regulations [13], and techniques prescribed by experts must be followed if there is to be a chance of 67. Thus the power process is disrupted in our society through a deficiency of real goals and a deficiency of autonomM y in the pursuit of goals. But it is also disrupted because of those human drives that fall into group 3: the drives that one cannot adequately satisfy no matter how much effort one makes. One of these drives is the need for security. Our lives depend on decisions made by other people; we have no control over these decisions and usually we do not even know the people who make them. ( in which relatively few people Philip B. Heymann of Harvard Law School, quoted by Anthony Lewis, New York Times, April 21, 1995.) Our lives depend on whether safety standards at a nuclear power plant are properly maintained; on how much pesticide is allowed to get into our food or how much pollution into our air; on how skillful (or incompetent) our doctor is; whether we lose or get a job may depend on decisions made by government economists or corporation executives; and so forth. Most individuals are noM t in a position to secure themselves against these threats to more [than] a very limited extent. The s search for security is therefore frustrated, which leads to a sense of powerlessness. 68. It may be objected that primitive man is physically less secure than modern man, as is shown by his shorter life expectancy; hence modern man suffers from less, not more than the amount of insecurity that is normal for human beings. But psychological security does not closely corresM security. What makes us FEEL secure is not so much objective security as a sense of confidence in our ability to take care of ourselves. Primitive man, threatened by a fierce animal or by hunger, can fight in self-defense or travel in search of food. He has no certainty of success in these efforts, but he is by no means helpless against the things that threaten him. The modern individual on the other hand is threatened by many things against which he is helplessM : nuclear accidents, carcinogens in food, environmental pollution, war, increasing taxes, invasion of his privacy by large organizations, nationwide social or economic phenomena that may disrupt his way of life. 69. It is true that primitive man is powerless against some of the things that threaten him; disease for example. But he can accept the risk of disease stoically. It is part of the nature of things, it is no one it is the fault of some imaginary, impersonal dM emon. But threats to the modern individual tend to be MAN-MADE. They are not the results of chance but are IMPOSED on him by other persons whose decisions he, as an individual, is unable to influence. Consequently he feels frustrated, humiliated and angry. 70. Thus primitive man for the most part has his security in his own hands (either as an individual or as a member of a SMALL group) whereas the security of modern man is in the hands of persons or organizations that are mote or too large for him to be able personally to influence them. So s drive for security tends to fall into groups 1 and 3; in some areas (food, shelter etc.) his security is assured at the cost of only trivial effort, whereas in other areas he CANNOT attain security. (The foregoing greatly simplifies the real situation, but it does indicate in a rough, general way how the condition of modern man differs from that of 71. People have many transitory driveM s or impulses that are necessarily frustrated in modern life, hence fall into group 3. One may become angry, but modern society cannot permit fighting. In many situations it does not even permit verbal aggression. When going somewhere one may be in a hurry, or one may be in a mood to travel slowly, but one generally has no choice but to move with the flow of traffic and obey the traffic signals. One may s work in a different way, but usually one can work only rding to the rules laid down by one s employer. In many other ways as well, modern man is strapped down by a network of rules and regulations (explicit or implicit) that frustrate many of his impulses and thus interfere with the power process. Most of these regulations cannot be dispensed with, because they are necessary for the functioning of industrial 72. Modern society is in certain respects extremely permissive. In matters that are irrelevant to the functioning of the M system we can generally do what we please. We can believe in any religion we like (as long as it does not encourage behavior that is dangerous to the system). We can go to bed with anyone we like (as long as we practice ). We can do anything we like as long as it is UNIMPORTANT. But in all IMPORTANT matters the system tends increasingly to regulate our behavior. 73. Behavior is regulated not only through explicit rules and not only by the government. Control is often exeM rcised through indirect coercion or through psychological pressure or manipulation, and by organizations other than the government, or by the system as a whole. Most large organizations use some form of propaganda [14] to manipulate public attitudes or behavior. Propaganda is not limited to and advertisements, and sometimes it is not even consciously intended as propaganda by the people who make it. For instance, the content of entertainment programming is a powerful M propaganda. An example of indirect coercion: There is no law that says we have to go to work every day and follow our employer s orders. Legally there is nothing to prevent us from going to live in the wild like primitive people or from going into business for ourselves. But in practice there is very little wild country left, and there is room in the economy for only a limited number of small business owners. Hence most of us can survive only 74. We suggest that modern man s obsession with longevity, and with maintaining physical vigor and sexual attractiveness to an advanced age, is a symptom of unfulfillment resulting from deprivation with respect to the power process. also is such a symptom. So is the lack of interest in having children that is fairly common in modern society but almost unheard-of in primitive societies. 75. In primitive societies life is a succession of stages. The needs and purposes of one stage having been fulfilled, there is no particular reluctance about passing on to the next stage. A young man goes through the power process by becoming a hunter, hunting not for sport or for fulfillment but to get meat that is necessary for food. (In young women the process is more complex, with greater emphasis on social power; we won here.) This phase having been successfully passed through, the young man has no reluctance about settling down toM the responsibilities of raising a family. (In contrast, some modern people indefinitely postpone having children because they are too busy seeking some kind of suggest that the fulfillment they need is adequate experience of the power with real goals instead of the artificial goals of surrogate activities.) Again, having successfully raised his children, going through the power process by providing them with the physical necessities, the man feels that his work is done and he is prepared to accept old age (if he survives that long) and death. Many modern people, on the other hand, are disturbed by the prospect of physical deterioration and death, as is shown by the amount of effort they expend trying to maintain their physical condition, appearance and health. We argue that this is due to unfulfillment resulting from the fact that they have never put their physical powers to any practical use, have never gone through tM process using their bodies in a serious way. It is not the primitive man, who has used his body daily for practical purposes, who fears the deterioration of age, but the modern man, who has never had a practical use for his body beyond walking from his car to his house. It is the man whose need for the power process has been satisfied during his life who is best prepared to accept the end of that life. 76. In response to the arguments of this section someone will say, find a way to give people the opportunity to go through the power process. For such people the value of the opportunity is destroyed by the very fact that society gives it to them. What they need is to find or make their own opportunities. As long as the system GIVES them their opportunities it still has them on a leash. To attain autonomy they must get off that leash. HOW SOME PEOPLE ADJUST 77. Not everyone in industrial-technological society suffers from psychological roblems. Some people even profess to be quite satisfied with society as it is. We now discuss some of the reasons why people differ so greatly in their response to modern society. 78. First, there doubtless are differences in the strength of the drive for power. Individuals with a weak drive for power may have relatively little need to go through the power process, or at least relatively little need for autonomy in the power process. These are docile types who would have been plantation darkies in the Old South. (We don t mean to sneer at the of the Old South. To their credit, most of the slaves were NOT content with their servitude. We do sneer at people who ARE content with servitude.) 79. Some people may have some exceptional drive, in pursuing which they satisfy their need for the power process. For example, those who have an unusually strong drive for social status may spend their whole lives climbing the status ladder withM out ever getting bored with that game. 80. People vary in their susceptibility to advertising and marketing techniques. Some are so susceptible that, even if they make a great deal of money, they cannot satisfy their constant craving for the the shiny new toys that the marketing industry dangles before their eyes. So they always feel hard-pressed financially even if their income is large, and their cravings 81. Some people have low susceptibility to advertising and marketiM These are the people who aren t interested in money. Material acquisition does not serve their need for the power process. 82. People who have medium susceptibility to advertising and marketing techniques are able to earn enough money to satisfy their craving for goods and services, but only at the cost of serious effort (putting in overtime, taking a second job, earning promotions, etc.). Thus material acquisition serves their need for the power process. But it does M not necessarily follow that their need is fully satisfied. They may have insufficient autonomy in the power process (their work may consist of following orders) and some of their drives may be frustrated (e.g., security, aggression). (We are guilty of oversimplification in paragraphs 80- 82 because we have assumed that the desire for material acquisition is entirely a creation of the advertising and marketing industry. Of course it s not that simple. [11] 83. Some people partly satiM sfy their need for power by identifying themselves with a powerful organization or mass movement. An individual lacking goals or power joins a movement or an organization, adopts its goals as his own, then works toward those goals. When some of the goals are attained, the individual, even though his personal efforts have played only an insignificant part in the attainment of the goals, feels (through his identification with the movement or organization) as if he had gone through he power process. This phenomenon was exploited by the fascists, nazis and communists. Our society uses it too, though less crudely. Example: Manuel Noriega was an irritant to the U.S. (goal: punish Noriega). The U.S. invaded Panama (effort) and punished Noriega (attainment of goal). Thus the U.S. went through the power process and many Americans, because of their identification with the U.S., experienced the power process vicariously. Hence the widespread public approval of the PanamaM invasion; it gave people a sense of power. [15] We see the same phenomenon in armies, corporations, political parties, humanitarian organizations, religious or ideological movements. In particular, leftist movements tend to attract people who are seeking to satisfy their need for power. But for most people identification with a large organization or a mass movement does not fully satisfy the need 84. Another way in which people satisfy their need for the power process is through surrogate activities. As we explained in paragraphs 38-40, a surrogate activity is an activity that is directed toward an artificial goal that the individual pursues for the sake of the gets from pursuing the goal, not because he needs to attain the goal itself. For instance, there is no practical motive for building enormous muscles, hitting a little ball into a hole or acquiring a complete series of postage stamps. Yet many people in our society M devote themselves with passion to bodybuilding, golf or stamp-collecting. Some people are more than others, and therefore will more readily attach importance to a surrogate activity simply because the people around them treat it as important or because society tells them it is important. That is why some people get very serious about essentially trivial activities such as sports, or bridge, or chess, or arcane scholarly pursuits, whereas others who are more clM ear-sighted never see these things as anything but the surrogate activities that they are, and consequently never attach enough importance to them to satisfy their need for the power process in that way. It only remains to point out that in many cases a person a living is also a surrogate activity. Not a PURE surrogate activity, since part of the motive for the activity is to gain the physical necessities and (for some people) social status and the luxuries that adveM want. But many people put into their work far more effort than is necessary to earn whatever money and status they require, and this extra effort constitutes a surrogate activity. This extra effort, together with the emotional investment that accompanies it, is one of the most potent forces acting toward the continual development and perfecting of the system, with negative consequences for individual freedom (see paragraph 131). Especially, for the most creativeM scientists and engineers, work tends to be largely a surrogate activity. This point is so important that it deserves a separate discussion, which we shall give in a moment (paragraphs 87-92). 85. In this section we have explained how many people in modern society do satisfy their need for the power process to a greater or lesser extent. But we think that for the majority of people the need for the power process is not fully satisfied. In the first place, those who have an insatiable driveM for status, or who get firmly on a surrogate activity, or who identify strongly enough with a movement or organization to satisfy their need for power in that way, are exceptional personalities. Others are not fully satisfied with surrogate activities or by identification with an organization (see paragraphs 41, 64). In the second place, too much control is imposed by the system through explicit regulation or through socialization, which results in a deficiency of auM tonomy, and in frustration due to the impossibility of attaining certain goals and the necessity of restraining too many impulses. 86. But even if most people in industrial-technological society were well satisfied, we (FC) would still be opposed to that form of society, because (among other reasons) we consider it demeaning to fulfill one power process through surrogate activities or through identification with an organization, rather than through pursuit of real goaM THE MOTIVES OF SCIENTISTS 87. Science and technology provide the most important examples of surrogate activities. Some scientists claim that they are motivated by But it is easy to see that neither of these can be the principal motive of most scientists. As for that notion is simply absurd. Most scientists work on highly specialized problems that are not the object of any normal curiosity. For example, is an astronomer, a mathematician or an entomologist curious about the properties of isopropyltrimethylmethane? Of course not. Only a chemist is curious about such a thing, and he is curious about it only because chemistry is his surrogate activity. Is the chemist curious about the appropriate classification of a new species of beetle? No. That question is of interest only to the entomologist, and he is interested in it only because entomology is his surrogate activity. If the chemist andM the entomologist had to exert themselves seriously to obtain the physical necessities, and if that effort exercised their abilities in an interesting way but in some nonscientific pursuit, then they wouldn t give a damn about isopropyltrimethylmethane or the classification of beetles. Suppose that lack of funds for postgraduate education had led the chemist to become an insurance broker instead of a chemist. In that case he would have been very interested in insurance ut would have cared nothing about isopropyltrimethylmethane. In any case it is not normal to put into the satisfaction of mere curiosity the amount of time and effort that scientists put into their work. The explanation for the scientists t work any better. Some scientific work has no conceivable relation to the welfare of the human most of archaeology or comparative linguistics M for example. Some other areas of science present obviously dangerous possibilities. Yet scientists in these areas are just as enthusiastic about their work as those who develop vaccines or study air pollution. Consider the case of Dr. Edward Teller, who had an obvious emotional involvement in promoting nuclear power plants. Did this involvement stem from a desire to benefit humanity? If so, t Dr. Teller get emotional about other such a humanitarian then why did he help to develop the H- bomb? As with many other scientific achievements, it is very much open to question whether nuclear power plants actually do benefit humanity. Does the cheap electricity outweigh the accumulating waste and the risk of accidents? Dr. Teller saw only one side of the question. Clearly his emotional involvement with nuclear power arose not from a desire to a personal fulfillment he got from his work M and from seeing it put to 89. The same is true of scientists generally. With possible rare exceptions, their motive is neither curiosity nor a desire to benefit humanity but the need to go through the power process: to have a goal (a scientific problem to solve), to make an effort (research) and to attain the goal (solution of the problem.) Science is a surrogate activity because scientists work mainly for the fulfillment they get out of the work itself. s not that simple. Other motives do play a role for many scientists. Money and status for example. Some scientists may be persons of the type who have an insatiable drive for status (see paragraph 79) and this may provide much of the motivation for their work. No doubt the majority of scientists, like the majority of the general population, are more or less susceptible to advertising and marketing techniques and need money to satisfy their craving for goods and services. Thus sciM surrogate activity. But it is in large part a surrogate activity. 91. Also, science and technology constitute a power mass movement, and many scientists gratify their need for power through identification with this mass movement (see paragraph 83). 92. Thus science marches on blindly, without regard to the real welfare of the human race or to any other standard, obedient only to the psychological needs of the scientists and of the government officials and corporation executives who provide the funds for research. THE NATURE OF FREEDOM 93. We are going to argue that industrial-technological society cannot be reformed in such a way as to prevent it from progressively narrowing the sphere of human freedom. But, because is a word that can be interpreted in many ways, we must first make clear what kind of freedom we are concerned with. we mean the opportunity to go through the power process, with real goals not the arM tificial goals of surrogate activities, and without interference, manipulation or supervision from anyone, especially from any large organization. Freedom means being in control (either as an individual or as a member of a SMALL group) of the life-and-death issues of one existence; food, clothing, shelter and defense against whatever threats there may be in one s environment. Freedom means having power; not the power to control other people but the power to control the circumstanM own life. One does not have freedom if anyone else (especially a large organization) has power over one, no matter how benevolently, tolerantly and permissively that power may be exercised. It is important not to confuse freedom with mere permissiveness (see paragraph 72). 95. It is said that we live in a free society because we have a certain number of constitutionally guaranteed rights. But these are not as important as they seem. The degree of personal freedom that eM xists in a society is determined more by the economic and technological structure of the society than by its laws or its form of government. [16] Most of the Indian nations of New England were monarchies, and many of the cities of the Italian Renaissance were controlled by dictators. But in reading about these societies one gets the impression that they allowed far more personal freedom than our society does. In part this was because they lacked efficient mechanisms for enforcing M s will: There were no modern, well-organized police forces, no rapid long-distance communications, no surveillance cameras, no dossiers of information about the lives of average citizens. Hence it was relatively easy to evade control. 96. As for our constitutional rights, consider for example that of freedom of the press. We certainly don t mean to knock that right; it is very important tool for limiting concentration of political power and for keeping those who political power in line by publicly exposing any misbehavior on their part. But freedom of the press is of very little use to the average citizen as an individual. The mass media are mostly under the control of large organizations that are integrated into the system. Anyone who has a little money can have something printed, or can distribute it on the Internet or in some such way, but what he has to say will be swamped by the vast volume of material put out by the media, hence it will M practical effect. To make an impression on society with words is therefore almost impossible for most individuals and small groups. Take us (FC) for example. If we had never done anything violent and had submitted the present writings to a publisher, they probably would not have been accepted. If they had been been accepted and published, they probably would not have attracted many readers, because it s more fun to watch the entertainment put out by the media than to reaM d a sober essay. Even if these writings had had many readers, most of these readers would soon have forgotten what they had read as their minds were flooded by the mass of material to which the media expose them. In order to get our message before the public with some chance of making a lasting impression, we ve had to kill people. 97. Constitutional rights are useful up to a point, but they do not serve to guarantee much more than what might be called the bourgeois conception of eedom. According to the bourgeois conception, a man is essentially an element of a social machine and has only a certain set of prescribed and delimited freedoms; freedoms that are designed to serve the needs of the social machine more than those of the individual. Thus the bourgeois man has economic freedom because that promotes growth and progress; he has freedom of the press because public criticism restrains misbehavior by political leaders; he has a rightM to a fair trial because imprisonment at the whim of the powerful would be bad for the system. This was clearly the attitude of Simon Bolivar. To him, people deserved liberty only if they used it to promote progress (progress as conceived by the bourgeois). Other bourgeois thinkers have taken a similar view of freedom as a mere means to collective ends. Chester C. Tan, Chinese Political Thought in the Twentieth page 202, explains the philosophy of the Kuomintang leader HM An individual is granted rights because he is a member of society and his community life requires such rights. By community Hu meant the whole society of the nation. And on page 259 Tan states that according to Carsum Chang (Chang Chun-mai, head of the State Socialist Party in China) freedom had to be used in the interest of the state and of the people as a whole. But what kind of freedom does one have if one can use it only as someone else prescribes? FC eption of freedom is not that of Bolivar, Hu, Chang or other bourgeois theorists. The trouble with such theorists is that they have made the development and application of social theories their surrogate activity. Consequently the theories are designed to serve the needs of the theorists more than the needs of any people who may be unlucky enough to live in a society on which the theories are imposed. 98. One more point to be made in this section: It should not be assumed that n has enough freedom just because he SAYS he has enough. Freedom is restricted in part by psychological controls of which people are unconscious, and moreover many people s ideas of what constitutes freedom are governed more by social convention than by their real needs. For s likely that many leftists of the oversocialized type would say that most people, including themselves, are socialized too little rather than too much, yet the oversocialized leftist pays a heavy pM price for his high level of socialization. SOME PRINCIPLES OF HISTORY 99. Think of history as being the sum of two components: an erratic component that consists of unpredictable events that follow no discernible pattern, and a regular component that consists of long-term historical trends. Here we are concerned with the long-term trends. 100. FIRST PRINCIPLE. If a SMALL change is made that affects a long-term historical trend, then the effect of that change will almost alwM the trend will soon revert to its original state. (Example: A reform movement designed to clean up political corruption in a society rarely has more than a short-term effect; sooner or later the reformers relax and corruption creeps back in. The level of political corruption in a given society tends to remain constant, or to change only slowly with the evolution of the society. Normally, a political cleanup will be permanent only if accompanied by widespM read social changes; a SMALL change in the t be enough.) If a small change in a long-term historical trend appears to be permanent, it is only because the change acts in the direction in which the trend is already moving, so that the trend is not altered by only pushed a step ahead. 101. The first principle is almost a tautology. If a trend were not stable with respect to small changes, it would wander at random rather than following a definite direction; in other M words it would not be a long- term trend at 102. SECOND PRINCIPLE. If a change is made that is sufficiently large to alter permanently a long-term historical trend, then it will alter the society as a whole. In other words, a society is a system in which all parts are interrelated, and you can t permanently change any important part without changing all other parts as well. 103. THIRD PRINCIPLE. If a change is made that is large enough to alter permanently a long-term trM end, then the consequences for the society as a whole cannot be predicted in advance. (Unless various other societies have passed through the same change and have all experienced the same consequences, in which case one can predict on empirical grounds that another society that passes through the same change will be like to experience similar consequences.) 104. FOURTH PRINCIPLE. A new kind of society cannot be designed on paper. That is, you cannot plan out a new form of societM y in advance, then set it up and expect it to function as it was designed to do. 105. The third and fourth principles result from the complexity of human societies. A change in human behavior will affect the economy of a society and its physical environment; the economy will affect the environment and vice versa, and the changes in the economy and the environment will affect human behavior in complex, unpredictable ways; and so forth. The network of causes and effects is far tooM complex to be untangled and understood. 106. FIFTH PRINCIPLE. People do not consciously and rationally choose the form of their society. Societies develop through processes of social evolution that are not under rational human control. 107. The fifth principle is a consequence of the other four. 108. To illustrate: By the first principle, generally speaking an attempt at social reform either acts in the direction in which the society is developing anyway (so that it merely accelerates a M change that would have occurred in any case) or else it has only a transitory effect, so that the society soon slips back into its old groove. To make a lasting change in the direction of development of any important aspect of a society, reform is insufficient and revolution is required. (A revolution does not necessarily involve an armed uprising or the overthrow of a government.) By the second principle, a revolution never changes only one aspect of a society, it changes M the whole society; and by the third principle changes occur that were never expected or desired by the revolutionaries. By the fourth principle, when revolutionaries or utopians set up a new kind of society, it never works out as planned. 109. The American Revolution does not provide a counterexample. The American was not a revolution in our sense of the word, but a war of independence followed by a rather far-reaching political reform. The Founding Fathers did M not change the direction of development of American society, nor did they aspire to do so. They only freed the development of American society from the retarding effect of British rule. Their political reform did not change any basic trend, but only pushed American political culture along its natural direction of development. British society, of which American society was an offshoot, had been moving for a long time in the direction of representative democracy. And prior to the WM Independence the Americans were already practicing a significant degree of representative democracy in the colonial assemblies. The political system established by the Constitution was modeled on the British system and on the colonial assemblies. With major alteration, to be sure doubt that the Founding Fathers took a very important step. But it was a step along the road that English-speaking world was already traveling. The proof is that Britain and allM of its colonies that were populated predominantly by people of British descent ended up with systems of representative democracy essentially similar to that of the United States. If the Founding Fathers had lost their nerve and declined to sign the Declaration of Independence, our way of life today would not have been significantly different. Maybe we would have had somewhat closer ties to Britain, and would have had a Parliament and Prime Minister instead of d President. No big deal. Thus the American Revolution provides not a counterexample to our principles but a good illustration of 110. Still, one has to use common sense in applying the principles. They are expressed in imprecise language that allows latitude for interpretation, and exceptions to them can be found. So we present these principles not as inviolable laws but as rules of thumb, or guides to thinking, that may provide a partial antidote to naive ideas about theM future of society. The principles should be borne constantly in mind, and whenever one reaches a conclusion that conflicts with them one should carefully reexamine one thinking and retain the conclusion only if one has good, solid reasons for INDUSTRIAL-TECHNOLOGICAL SOCIETY CANNOT BE REFORMED 111. The foregoing principles help to show how hopelessly difficult it would be to reform the industrial system in such a way as to prevent it from progressively narrowM ing our sphere of freedom. There has been a consistent tendency, going back at least to the Industrial Revolution for technology to strengthen the system at a high cost in individual freedom and local autonomy. Hence any change designed to protect freedom from technology would be contrary to a fundamental trend in the development of our society. Consequently, such a change either would be a transitory one by the tide of history or, if large enough to be permanentM nature of our whole society. This by the first and second principles. Moreover, since society would be altered in a way that could not be predicted in advance (third principle) there would be great risk. Changes large enough to make a lasting difference in favor of freedom would not be initiated because it would be realized that they would gravely disrupt the system. So any attempts at reform would be too timid to be effective. Even if changes large enough M to make a lasting difference were initiated, they would be retracted when their disruptive effects became apparent. Thus, permanent changes in favor of freedom could be brought about only by persons prepared to accept radical, dangerous and unpredictable alteration of the entire system. In other words by revolutionaries, not reformers. 112. People anxious to rescue freedom without sacrificing the supposed benefits of technology will suggest naive schemes for some new form of society tM would reconcile freedom with technology. Apart from the fact that people who make such suggestions seldom propose any practical means by which the new form of society could be set up in the first place, it follows from the fourth principle that even if the new form of society could be once established, it either would collapse or would give results very different from those expected. 113. So even on very general grounds it seems highly improbable that any way of ng society could be found that would reconcile freedom with modern technology. In the next few sections we will give more specific reasons for concluding that freedom and technological progress are incompatible. RESTRICTION OF FREEDOM IS UNAVOIDABLE IN INDUSTRIAL SOCIETY 114. As explained in paragraphs 65-67, 70-73, modern man is strapped down by a network of rules and regulations, and his fate depends on the actions of persons remote from him whose decisions he cannot influence. This is nM accidental or a result of the arbitrariness of arrogant bureaucrats. It is necessary and inevitable in any technologically advanced society. The system HAS TO regulate human behavior closely in order to function. At work people have to do what they are told to do, otherwise production would be thrown into chaos. Bureaucracies HAVE TO be run according to rigid rules. To allow any substantial personal discretion to lower-level bureaucrats would disrupt the system and lead M to charges of unfairness due to differences in the way individual bureaucrats exercised their discretion. It is true that some restrictions on our freedom could be eliminated, but GENERALLY SPEAKING the regulation of our lives by large organizations is necessary for the functioning of industrial-technological society. The result is a sense of powerlessness on the part of the average person. It may be, however, that formal regulations will tend increasingly to be psychological tools that make us want to do what the system requires of us. (Propaganda [14], educational techniques, programs, etc.) 115. The system HAS TO force people to behave in ways that are increasingly remote from the natural pattern of human behavior. For example, the system needs scientists, mathematicians and engineers. It can them. So heavy pressure is put on children to excel in these fields. It t natural for an adolesM cent human being to spend the bulk of his time sitting at a desk absorbed in study. A normal adolescent wants to spend his time in active contact with the real world. Among primitive peoples the things that children are trained to do tend to be in reasonable harmony with natural human impulses. Among the American Indians, for example, boys were trained in active outdoor pursuits just the sort of thing that boys like. But in our society children are pushed into studying technicaM subjects, which most do grudgingly. 116. Because of the constant pressure that the system exerts to modify human behavior, there is a gradual increase in the number of people who cannot or will not adjust to society s requirements: welfare leeches, youth-gang members, cultists, anti-government rebels, radical environmentalist saboteurs, dropouts and resisters of various kinds. 117. In any technologically advanced society the individual on decisions thatM he personally cannot influence to any great extent. A technological society cannot be broken down into small, autonomous communities, because production depends on the cooperation of very large numbers of people and machines. Such a society MUST be highly organized and decisions HAVE TO be made that affect very large numbers of people. When a decision affects, say, a million people, then each of the affected individuals has, on the average, only a one-millionth share in making tM decision. What usually happens in practice is that decisions are made by public officials or corporation executives, or by technical specialists, but even when the public votes on a decision the number of voters ordinarily is too large for the vote of any one individual to be significant. [17] Thus most individuals are unable to influence measurably the major decisions that affect their lives. There is no conceivable way to remedy this in a technologically advanced socieM ty. The system tries to this problem by using propaganda to make people WANT the decisions that have been made for them, but even if this successful in making people feel better, it would be demeaning. 118. Conservatives and some others advocate more communities once did have autonomy, but such autonomy becomes less and less possible as local communities become more enmeshed with and dependent on systems like public utilities, computer networks, highway systems, the mass communications media, the modern health care system. Also operating against autonomy is the fact that technology applied in one location often affects people at other locations far way. Thus pesticide or chemical use near a creek may contaminate the water supply hundreds of miles downstream, and the greenhouse effect affects the whole world. 119. The system does not and cannot exist to satisfy human needs. InsM human behavior that has to be modified to fit the needs of the system. This has nothing to do with the political or social ideology that may pretend to guide the technological system. It is the fault of technology, because the system is guided not by ideology but by technical necessity. [18] Of course the system does satisfy many human needs, but generally speaking it does this only to the extend that it is to the advantage of the system to do it. It is the needM s of the system that are paramount, not those of the human being. For example, the system provides people with food because the system t function if everyone starved; it attends to people needs whenever it can CONVENIENTLY do so, because it couldn too many people became depressed or rebellious. But the system, for good, solid, practical reasons, must exert constant pressure on people to mold their behavior to the needs of the system. To M much waste accumulating? The government, the media, the educational system, environmentalists, everyone inundates us with a mass of propaganda about recycling. Need more technical personnel? A chorus of voices exhorts kids to study science. No one stops to ask whether it is inhumane to force adolescents to spend the bulk of their time studying subjects most of them hate. When skilled workers are put out of a job by technical advances and have to undergo one asks whether it is humiliating for them to be pushed around in this way. It is simply taken for granted that everyone must bow to technical necessity. and for good reason: If human needs were put before technical necessity there would be economic problems, unemployment, shortages or worse. The concept of in our society is defined largely by the extent to which an individual behaves in accord with the needs of the system and does so without showing signs oM 120. Efforts to make room for a sense of purpose and for autonomy within the system are no better than a joke. For example, one company, instead of having each of its employees assemble only one section of a catalogue, had each assemble a whole catalogue, and this was supposed to give them a sense of purpose and achievement. Some companies have tried to give their employees more autonomy in their work, but for practical reasons this usually can be done only to a very lM imited extent, and in any case employees are never given autonomy as to ultimate goals efforts can never be directed toward goals that they select personally, but only toward their employer s goals, such as the survival and growth of the company. Any company would soon go out of business if it permitted its employees to act otherwise. Similarly, in any enterprise within a socialist system, workers must direct their efforts toward the goals of the enterprise, otherwise the enterprise will not serve its purpose as part of the system. Once again, for purely technical reasons it is not possible for most individuals or small groups to have much autonomy in industrial society. Even the small-business owner commonly has only limited autonomy. Apart from the necessity of government regulation, he is restricted by the fact that he must fit into the economic system and conform to its requirements. For instance, when someone developM s a new technology, the small-business person often has to use that technology whether he wants to or not, in order to remain competitive. PARTS OF TECHNOLOGY CANNOT BE SEPARATED FROM THE 121. A further reason why industrial society cannot be reformed in favor of freedom is that modern technology is a unified system in which all parts are dependent on one another. You can technology and retain only the rts. Take modern medicine, for example. Progress in medical science depends on progress in chemistry, physics, biology, computer science and other fields. Advanced medical treatments require expensive, high-tech equipment that can be made available only by a technologically progressive, economically rich society. Clearly you can t have much progress in medicine without the whole technological system and everything that goes with it. 122. Even if medical progress could be maintM ained without the rest of the technological system, it would by itself bring certain evils. Suppose for example that a cure for diabetes is discovered. People with a genetic tendency to diabetes will then be able to survive and reproduce as well as anyone else. Natural selection against genes for diabetes will cease and such genes will spread throughout the population. (This may be occurring to some extent already, since diabetes, while not curable, can be controlled h use of insulin.) The same thing will happen with many other diseases susceptibility to which is affected by genetic degradation of the population. The only solution will be some sort of eugenics program or extensive genetic engineering of human beings, so that man in the future will no longer be a creation of nature, or of chance, or of God (depending on your religious or philosophical opinions), but a manufactured product. 123. If you think that big government interferes in your liM fe too much NOW, just wait till the government starts regulating the genetic constitution of your children. Such regulation will inevitably follow the introduction of genetic engineering of human beings, because the consequences of unregulated genetic engineering would be disastrous. [19] 124. The usual response to such concerns is to talk about a code of ethics would not serve to protect freedom in the face of medical progress; it would only make matteM rs worse. A code of ethics applicable to genetic engineering would be in effect a means of regulating the genetic constitution of human beings. Somebody (probably the upper-middle class, mostly) would decide that such and such applications of genetic engineering and others were not, so that in effect they would be imposing their own values on the genetic constitution of the population at large. Even if a code of ethics were chosen on a completely democratic basis, the majority would be imposing their own values on any minorities who might have a different idea of what constituted an genetic engineering. The only code of ethics that would truly protect freedom would be one that prohibited ANY genetic engineering of human beings, and you can be sure that no such code will ever be applied in a technological society. No code that reduced genetic engineering to a minor role could stand up for long, because the temM ptation presented by the immense power of biotechnology would be irresistible, especially since to the majority of people many of its applications will seem obviously and unequivocally good (eliminating physical and mental diseases, giving people the abilities they need to get along in today s world). Inevitably, genetic engineering will be used extensively, but only in ways consistent with the needs of the industrial- technological system. [20] TECHNOLOGY IS A MORE POWERFUL SM OCIAL FORCE THAN THE ASPIRATION FOR FREEDOM 125. It is not possible to make a LASTING compromise between technology and freedom, because technology is by far the more powerful social force and continually encroaches on freedom through REPEATED compromises. Imagine the case of two neighbors, each of whom at the outset owns the same amount of land, but one of whom is more powerful than the other. The powerful one demands a piece of the other s land. The weak one refuses. The powerful M s compromise. Give me half of what I asked. has little choice but to give in. Some time later the powerful neighbor demands another piece of land, again there is a compromise, and so forth. By forcing a long series of compromises on the weaker man, the powerful one eventually gets all of his land. So it goes in the conflict between technology and freedom. 126. Let us explain why technology is a more powerful social force than the 127. A technological advance that appears not to threaten freedom often turns out to threaten it very seriously later on. For example, consider motorized transport. A walking man formerly could go where he pleased, go at his own pace without observing any traffic regulations, and was independent of technological support-systems. When motor vehicles were introduced they appeared to increase man s freedom. They took no freedom away from the walking man, no onM e had to have an automobile if he didn anyone who did choose to buy an automobile could travel much faster and farther than a walking man. But the introduction of motorized transport soon changed society in such a way as to restrict greatly man locomotion. When automobiles became numerous, it became necessary to regulate their use extensively. In a car, especially in densely populated areas, one cannot just go where one likes at one is governed by the flow of traffic and by various traffic laws. One is tied down by various obligations: license requirements, driver test, renewing registration, insurance, maintenance required for safety, monthly payments on purchase price. Moreover, the use of motorized transport is no longer optional. Since the introduction of motorized transport the arrangement of our cities has changed in such a way that the majority of people no longer live within walM king distance of their place of employment, shopping areas and recreational opportunities, so that they HAVE TO depend on the automobile for transportation. Or else they must use public transportation, in which case they have even less control over their own movement than when driving a car. Even the walker s freedom is now greatly restricted. In the city he continually has to stop to wait for traffic lights that are designed mainly to serve auto traffic. In the country, motor M it dangerous and unpleasant to walk along the highway. (Note this important point that we have just illustrated with the case of motorized transport: When a new item of technology is introduced as an option that an individual can accept or not as he chooses, it does not necessarily REMAIN optional. In many cases the new technology changes society in such a way that people eventually find themselves FORCED to use it.) 128. While technological progress AS A WHOLE conM tinually narrows our sphere of freedom, each new technical advance CONSIDERED BY ITSELF appears to be desirable. Electricity, indoor plumbing, rapid long-distance communications ... how could one argue against any of these things, or against any other of the innumerable technical advances that have made modern society? It would have been absurd to resist the introduction of the telephone, for example. It offered many advantages and no disadvantages. Yet, as we paragraphs 59-76, all these technical advances taken together have created a world in which the average man s fate is no longer in his own hands or in the hands of his neighbors and friends, but in those of politicians, corporation executives and remote, anonymous technicians and bureaucrats whom he as an individual has no power to influence. [21] The same process will continue in the future. Take genetic engineering, for example. Few people will resist the introduction of a geM that eliminates a hereditary disease. It does no apparent harm and prevents much suffering. Yet a large number of genetic improvements taken together will make the human being into an engineered product rather than a free creation of chance (or of God, or whatever, depending on your religious 129. Another reason why technology is such a powerful social force is that, within the context of a given society, technological progress marches in direction; it can never be reversed. Once a technical innovation has been introduced, people usually become dependent on it, so that they can never again do without it, unless it is replaced by some still more advanced innovation. Not only do people become dependent as individuals on a new item of technology, but, even more, the system as a whole becomes dependent on it. (Imagine what would happen to the system today if computers, for example, were eliminated.) Thus the system cM one direction, toward greater technologization. Technology repeatedly forces freedom to take a step back, but technology can never take a step short of the overthrow of the whole technological system. 130. Technology advances with great rapidity and threatens freedom at many different points at the same time (crowding, rules and regulations, increasing dependence of individuals on large organizations, propaganda and other psychological techniques, genetic M engineering, invasion of privacy through surveillance devices and computers, etc.). To hold back any ONE of the threats to freedom would require a long and difficult social struggle. Those who want to protect freedom are overwhelmed by the sheer number of new attacks and the rapidity with which they develop, hence they become apathetic and no longer resist. To fight each of the threats separately would be futile. Success can be hoped for only by fighting the technological sM ystem as a whole; but that is revolution, not reform. 131. Technicians (we use this term in its broad sense to describe all those who perform a specialized task that requires training) tend to be so involved in their work (their surrogate activity) that when a conflict arises between their technical work and freedom, they almost always decide in favor of their technical work. This is obvious in the case of scientists, but it also appears elsewhere: Educators, humanitarian groups, consM organizations do not hesitate to use propaganda or other psychological techniques to help them achieve their laudable ends. Corporations and government agencies, when they find it useful, do not hesitate to collect information about individuals without regard to their privacy. Law enforcement agencies are frequently inconvenienced by the constitutional rights of suspects and often of completely innocent persons, and they do whatever they can do legally (or sometimeM s illegally) to restrict or circumvent those rights. Most of these educators, government officials and law officers believe in freedom, privacy and constitutional rights, but when these conflict with their work, they usually feel that their work is more important. 132. It is well known that people generally work better and more persistently when striving for a reward than when attempting to avoid a punishment or negative outcome. Scientists and other technicians are motivated maM the rewards they get through their work. But those who oppose technological invasions of freedom are working to avoid a negative outcome, consequently there are few who work persistently and well at this discouraging task. If reformers ever achieved a signal victory that seemed to set up a solid barrier against further erosion of freedom through technical progress, most would tend to relax and turn their attention to more agreeable pursuits. But the scientists wouldM remain busy in their laboratories, and technology as it progresses would find ways, in spite of any barriers, to exert more and more control over individuals and make them always more dependent on 133. No social arrangements, whether laws, institutions, customs or ethical codes, can provide permanent protection against technology. History shows that all social arrangements are transitory; they all change or break down eventually. But technological advances are permaM nent within the context of a given civilization. Suppose for example that it were possible to arrive at some social arrangements that would prevent genetic engineering from being applied to human beings, or prevent it from being applied in such a way as to threaten freedom and dignity. Still, the technology would remain waiting. Sooner or later the social arrangement would break down. Probably sooner, given the pace of change in our society. Then genetic engineering would bM egin to invade our sphere of freedom, and this invasion would be irreversible (short of a breakdown of technological civilization itself). Any illusions about achieving anything permanent through social arrangements should be dispelled by what is currently happening with environmental legislation. A few years ago its seemed that there were secure legal barriers preventing at least SOME of the worst forms of environmental degradation. A change in the political wind, and those barriers begin to crumble. 134. For all of the foregoing reasons, technology is a more powerful social force than the aspiration for freedom. But this statement requires an important qualification. It appears that during the next several decades the industrial-technological system will be undergoing severe stresses due to economic and environmental problems, and especially due to problems of human behavior (alienation, rebellion, hostility, a variety of social and l difficulties). We hope that the stresses through which the system is likely to pass will cause it to break down, or at least will weaken it sufficiently so that a revolution against it becomes possible. If such a revolution occurs and is successful, then at that particular moment the aspiration for freedom will have proved more powerful than technology. 135. In paragraph 125 we used an analogy of a weak neighbor who is left destitute by a strong neighbor who takes all his land by foM a series of compromises. But suppose now that the strong neighbor gets sick, so that he is unable to defend himself. The weak neighbor can force the strong one to give him his land back, or he can kill him. If he lets the strong man survive and only forces him to give the land back, he is a fool, because when the strong man gets well he will again take all the land for himself. The only sensible alternative for the weaker man is to kill the strong one while he M has the chance. In the same way, while the industrial system is sick we must destroy it. If we compromise with it and let it recover from its sickness, it will eventually wipe out all of our SIMPLER SOCIAL PROBLEMS HAVE PROVED INTRACTABLE 136. If anyone still imagines that it would be possible to reform the system in such a way as to protect freedom from technology, let him consider how clumsily and for the most part unsuccessfully our society has dealt with al problems that are far more simple and straightforward. Among other things, the system has failed to stop environmental degradation, political corruption, drug trafficking or domestic abuse. 137. Take our environmental problems, for example. Here the conflict of values is straightforward: economic expedience now versus saving some of our natural resources for our grandchildren. [22] But on this subject we get only a lot of blather and obfuscation from the people who have power, and M nothing like a clear, consistent line of action, and we keep on piling up environmental problems that our grandchildren will have to live with. Attempts to resolve the environmental issue consist of struggles and compromises between different factions, some of which are ascendant at one moment, others at another moment. The line of struggle changes with the shifting currents of public opinion. This is not a rational process, nor is it one that is likely to lead to a timely M and successful solution to the problem. Major social problems, if they get at all, are rarely or never solved through any rational, comprehensive plan. They just work themselves out through a process in which various competing groups pursuing their own (usually short- term) self-interest [23] arrive (mainly by luck) at some more or less stable modus vivendi. In fact, the principles we formulated in paragraphs 100-106 make it seem doubtful that rational, rm social planning can EVER be successful. 138. Thus it is clear that the human race has at best a very limited capacity for solving even relatively straightforward social problems. How then is it going to solve the far more difficult and subtle problem of reconciling freedom with technology? Technology presents clear-cut material advantages, whereas freedom is an abstraction that means different things to different people, and its loss is easily obscured by propaganda and fancy talk.M 139. And note this important difference: It is conceivable that our environmental problems (for example) may some day be settled through a rational, comprehensive plan, but if this happens it will be only because it is in the long-term interest of the system to solve these problems. But it is NOT in the interest of the system to preserve freedom or small-group autonomy. On the contrary, it is in the interest of the system to bring human behavior under control to the greatest poM ssible extent. [24] Thus, while practical considerations may eventually force the system to take a rational, prudent approach to environmental problems, equally practical considerations will force the system to regulate human behavior ever more closely (preferably by indirect means that will disguise the encroachment on freedom). This isn t just our opinion. Eminent social scientists (e.g. James Q. Wilson) have stressed the importance of REVOLUTION IS EASIER THAN REFORM 140. We hope we have convinced the reader that the system cannot be reformed in such a way as to reconcile freedom with technology. The only way out is to dispense with the industrial-technological system altogether. This implies revolution, not necessarily an armed uprising, but certainly a radical and fundamental change in the nature of society. 141. People tend to assume that because a revolution involves a much greater change than reformM does, it is more difficult to bring about than reform is. Actually, under certain circumstances revolution is much easier than reform. The reason is that a revolutionary movement can inspire an intensity of commitment that a reform movement cannot inspire. A reform movement merely offers to solve a particular social problem. A revolutionary movement offers to solve all problems at one stroke and create a whole new world; it provides the kind of ideal for which people take great risks and make great sacrifices. For this reasons it would be much easier to overthrow the whole technological system than to put effective, permanent restraints on the development or application of any one segment of technology, such as genetic engineering, for example. Not many people will devote themselves with single-minded passion to imposing and maintaining restraints on genetic engineering, but under suitable conditions large numbers of people may devote themselM ves passionately to a revolution against the industrial-technological system. As we noted in paragraph 132, reformers seeking to limit certain aspects of technology would be working to avoid a negative outcome. But revolutionaries work to gain a powerful reward fulfillment of their revolutionary vision therefore work harder and more persistently than reformers do. 142. Reform is always restrained by the fear of painful consequences if changes go too far. But once a revolM utionary fever has taken hold of a society, people are willing to undergo unlimited hardships for the sake of their revolution. This was clearly shown in the French and Russian Revolutions. It may be that in such cases only a minority of the population is really committed to the revolution, but this minority is sufficiently large and active so that it becomes the dominant force in society. We will have more to say about revolution in paragraphs 180-205. CONTROL OF HUMAN BEHAVIORM 143. Since the beginning of civilization, organized societies have had to put pressures on human beings of the sake of the functioning of the social organism. The kinds of pressures vary greatly from one society to another. Some of the pressures are physical (poor diet, excessive labor, environmental pollution), some are psychological (noise, crowding, forcing human behavior into the mold that society requires). In the past, human nature has been approximately constant, or at aM ny rate has varied only within certain bounds. Consequently, societies have been able to push people only up to certain limits. When the limit of human endurance has been passed, things start going wrong: rebellion, or crime, or corruption, or evasion of work, or depression and other mental problems, or an elevated death rate, or a declining birth rate or something else, so that either the society breaks down, or its functioning becomes too inefficient and it is gradually, through conquest, attrition or evolution) replaced by some more efficient form of society. [25] 144. Thus human nature has in the past put certain limits on the development of societies. People could be pushed only so far and no farther. But today this may be changing, because modern technology is developing ways of modifying human beings. 145. Imagine a society that subjects people to conditions that make them terribly unhappy, then gives them drugs to take away their unM Science fiction? It is already happening to some extent in our own society. It is well known that the rate of clinical depression has been greatly increasing in recent decades. We believe that this is due to disruption of the power process, as explained in paragraphs 59-76. But even if we are wrong, the increasing rate of depression is certainly the result of SOME conditions that exist in today s society. Instead of removing the conditions that make people deprM essed, modern society gives them antidepressant drugs. In effect, antidepressants are a means of modifying s internal state in such a way as to enable him to tolerate social conditions that he would otherwise find intolerable. (Yes, we know that depression is often of purely genetic origin. We are referring here to those cases in which environment plays the predominant role.) 146. Drugs that affect the mind are only one example of the new methods of uman behavior that modern society is developing. Let us look at some of the other methods. 147. To start with, there are the techniques of surveillance. Hidden video cameras are now used in most stores and in many other places, computers are used to collect and process vast amounts of information about individuals. Information so obtained greatly increases the effectiveness of physical coercion (i.e., law enforcement). [26] Then there are the methods of propaganda, for which theM mass communication media provide effective vehicles. Efficient techniques have been developed for winning elections, selling products, influencing public opinion. The entertainment industry serves as an important psychological tool of the system, possibly even when it is dishing out large amounts of sex and violence. Entertainment provides modern man with an essential means of escape. While absorbed in television, videos, etc., he can forget stress, anxiety, frustration, dissatiM Many primitive peoples, when they don t have work to do, are quite content to sit for hours at a time doing nothing at all, because they are at peace with themselves and their world. But most modern people must be constantly occupied or entertained, otherwise they get fidgety, uneasy, irritable. 148. Other techniques strike deeper than the foregoing. Education is no longer a simple affair of paddling a kid s behind when he doesn and patting him on the head when he does know them. It is becoming a scientific technique for controlling the child s development. Sylvan Learning Centers, for example, have had great success in motivating children to study, and psychological techniques are also used with more or less success in many conventional schools. techniques that are taught to parents are designed to make children accept fundamental values of the system and behave in wM ays that the system finds desirable. techniques, psychotherapy and so forth are ostensibly designed to benefit individuals, but in practice they usually serve as methods for inducing individuals to think and behave as the system requires. (There is no contradiction here; an individual whose attitudes or behavior bring him into conflict with the system is up against a force that is too powerful for him to conquer or escape from, henM suffer from stress, frustration, defeat. His path will be much easier if he thinks and behaves as the system requires. In that sense the system is acting for the benefit of the individual when it brainwashes him into conformity.) Child abuse in its gross and obvious forms is disapproved in most if not all cultures. Tormenting a child for a trivial reason or no reason at all is something that appalls almost everyone. But many psychologists interpret the coM ncept of abuse much more broadly. Is spanking, when used as part of a rational and consistent system of discipline, a form of abuse? The question will ultimately be decided by whether or not spanking tends to produce behavior that makes a person fit in well with the existing system of society. In practice, the word tends to be interpreted to include any method of child-rearing that produces behavior inconvenient for the system. Thus, when they go beyond ention of obvious, senseless cruelty, programs for preventing are directed toward the control of human behavior on behalf 149. Presumably, research will continue to increase the effectiveness of psychological techniques for controlling human behavior. But we think it is unlikely that psychological techniques alone will be sufficient to adjust human beings to the kind of society that technology is creating. Biological methods probably will have toM be used. We have already mentioned the use of drugs in this connection. Neurology may provide other avenues for modifying the human mind. Genetic engineering of human beings is already beginning to occur in the form of and there is no reason to assume that such methods will not eventually be used to modify those aspects of the body that affect mental functioning. 150. As we mentioned in paragraph 134, industrial society seems likely to be entering a period oM f severe stress, due in part to problems of human behavior and in part to economic and environmental problems. And a considerable proportion of the system s economic and environmental problems result from the way human beings behave. Alienation, low self-esteem, depression, hostility, rebellion; children who won youth gangs, illegal drug use, rape, child abuse, other crimes, unsafe sex, teen pregnancy, population growth, political corruption, race hatred, c rivalry, bitter ideological conflict (e.g., pro-choice vs. pro- life), political extremism, terrorism, sabotage, anti-government groups, hate groups. All these threaten the very survival of the system. The system will therefore be FORCED to use every practical means of controlling human 151. The social disruption that we see today is certainly not the result of mere chance. It can only be a result of the conditions of life that the system imposes on people. (We have M argued that the most important of these conditions is disruption of the power process.) If the systems succeeds in imposing sufficient control over human behavior to assure its own survival, a new watershed in human history will have been passed. Whereas formerly the limits of human endurance have imposed limits on the development of societies (as we explained in paragraphs 143, 144), industrial-technological society will be able to pass those limits by modifying human beinM gs, whether by psychological methods or biological methods or both. In the future, social systems will not be adjusted to suit the needs of human beings. Instead, human being will be adjusted to suit the needs of the system. [27] 152. Generally speaking, technological control over human behavior will probably not be introduced with a totalitarian intention or even through a conscious desire to restrict human freedom. [28] Each new step in the assertion of control over the human M mind will be taken as a rational response to a problem that faces society, such as curing alcoholism, reducing the crime rate or inducing young people to study science and engineering. In many cases there will be a humanitarian justification. For example, when a psychiatrist prescribes an anti-depressant for a depressed patient, he is clearly doing that individual a favor. It would be inhumane to withhold the drug from someone who needs it. When parents send their children to SylM Learning Centers to have them manipulated into becoming enthusiastic about their studies, they do so from concern for their children be that some of these parents wish that one didn t have to have specialized training to get a job and that their kid didn t have to be brainwashed into becoming a computer nerd. But what can they do? They can and their child may be unemployable if he doesn t have certain skills. So 153. Thus control over human behavior will be introduced not by a calculated decision of the authorities but through a process of social evolution (RAPID evolution, however). The process will be impossible to resist, because each advance, considered by itself, will appear to be beneficial, or at least the evil involved in making the advance will appear to be beneficial, or at least the evil involved in making the advance will seem to be less than that which would reM sult from not making it (see paragraph 127). Propaganda for example is used for many good purposes, such as discouraging child abuse or race hatred. [14] Sex education is obviously useful, yet the effect of sex education (to the extent that it is successful) is to take the shaping of sexual attitudes away from the family and put it into the hands of the state as represented by the public school 154. Suppose a biological trait is discovered that increases the likelihood tM a child will grow up to be a criminal, and suppose some sort of gene therapy can remove this trait. [29] Of course most parents whose children possess the trait will have them undergo the therapy. It would be inhumane to do otherwise, since the child would probably have a miserable life if he grew up to be a criminal. But many or most primitive societies have a low crime rate in comparison with that of our society, even though they have neither high- tech methods of chiM ld-rearing nor harsh systems of punishment. Since there is no reason to suppose that more modern men than primitive men have innate predatory tendencies, the high crime rate of our society must be due to the pressures that modern conditions put on people, to which many cannot or will not adjust. Thus a treatment designed to remove potential criminal tendencies is at least in part a way of re-engineering people so that they suit the requirements of the system. 155. Our society teM any mode of thought or behavior that is inconvenient for the system, and this is plausible because when an individual doesn t fit into the system it causes pain to the individual as well as problems for the system. Thus the manipulation of an individual to adjust him to the system is seen as a 156. In paragraph 127 we pointed out that if the use of a new item of technology is INITIALLY optM ional, it does not necessarily REMAIN optional, because the new technology tends to change society in such a way that it becomes difficult or impossible for an individual to function without using that technology. This applies also to the technology of human behavior. In a world in which most children are put through a program to make them enthusiastic about studying, a parent will almost be forced to put his kid through such a program, because if he does not, then the kid will gM to be, comparatively speaking, an ignoramus and therefore unemployable. Or suppose a biological treatment is discovered that, without undesirable side-effects, will greatly reduce the psychological stress from which so many people suffer in our society. If large numbers of people choose to undergo the treatment, then the general level of stress in society will be reduced, so that it will be possible for the system to increase the stress-producing pressures. In fact, M something like this seems to have happened already with one of our society s most important psychological tools for enabling people to reduce (or at least temporarily escape from) stress, namely, mass entertainment (see paragraph 147). Our use of mass entertainment is : No law requires us to watch television, listen to the radio, read magazines. Yet mass entertainment is a means of escape and stress-reduction on which most of us have become dependent. Everyone complains about the trashiness of television, but almost everyone watches it. A few have kicked the TV habit, but it would be a rare person who could get along today without using ANY form of mass entertainment. (Yet until quite recently in human history most people got along very nicely with no other entertainment than that which each local community created for itself.) Without the entertainment industry the system probably would not have been able to get away with putting aM s much stress-producing pressure on us as it does. 157. Assuming that industrial society survives, it is likely that technology will eventually acquire something approaching complete control over human behavior. It has been established beyond any rational doubt that human thought and behavior have a largely biological basis. As experimenters have demonstrated, feelings such as hunger, pleasure, anger and fear can be turned on and off by electrical stimulation of appropriate partM brain. Memories can be destroyed by damaging parts of the brain or they can be brought to the surface by electrical stimulation. Hallucinations can be induced or moods changed by drugs. There may or may not be an immaterial human soul, but if there is one it clearly is less powerful that the biological mechanisms of human behavior. For if that were not the case then researchers would not be able so easily to manipulate human feelings and behavior with drugs and eleM 158. It presumably would be impractical for all people to have electrodes inserted in their heads so that they could be controlled by the authorities. But the fact that human thoughts and feelings are so open to biological intervention shows that the problem of controlling human behavior is mainly a technical problem; a problem of neurons, hormones and complex molecules; the kind of problem that is accessible to scientific attack. Given the outstanding record oM f our society in solving technical problems, it is overwhelmingly probable that great advances will be made in the control of human behavior. 159. Will public resistance prevent the introduction of technological control of human behavior? It certainly would if an attempt were made to introduce such control all at once. But since technological control will be introduced through a long sequence of small advances, there will be no rational and effective public resistance. (See paraM graphs 127, 132, 153.) 160. To those who think that all this sounds like science fiction, we point out s science fiction is today s fact. The Industrial Revolution has radically altered man s environment and way of life, and it is only to be expected that as technology is increasingly applied to the human body and mind, man himself will be altered as radically as his environment and way of life have been. HUMAN RACE AT A CROSSROADS 161. But we have gotten ahead M of our story. It is one thing to develop in the laboratory a series of psychological or biological techniques for manipulating human behavior and quite another to integrate these techniques into a functioning social system. The latter problem is the more difficult of the two. For example, while the techniques of educational psychology doubtless work quite well in the where they are developed, it is not necessarily easy to apply them effectively throughout our educational system. We all know what many of our schools are like. The teachers are too busy taking knives and guns away from the kids to subject them to the latest techniques for making them into computer nerds. Thus, in spite of all its technical advances relating to human behavior, the system to date has not been impressively successful in controlling human beings. The people whose behavior is fairly well under the control of the system are those of the type that might be M growing numbers of people who in one way or another are rebels against the system: welfare leaches, youth gangs, cultists, satanists, nazis, radical environmentalists, militiamen, etc. 162. The system is currently engaged in a desperate struggle to overcome certain problems that threaten its survival, among which the problems of human behavior are the most important. If the system succeeds in acquiring sufficient control over human behavior M quickly enough, it will probably survive. Otherwise it will break down. We think the issue will most likely be resolved within the next several decades, say 40 to 100 years. 163. Suppose the system survives the crisis of the next several decades. By that time it will have to have solved, or at least brought under control, the principal problems that confront it, in particular that of human beings; that is, making people sufficiently docile so that heir no longer threatens the system. That being accomplished, it does not appear that there would be any further obstacle to the development of technology, and it would presumably advance toward its logical conclusion, which is complete control over everything on Earth, including human beings and all other important organisms. The system may become a unitary, monolithic organization, or it may be more or less fragmented and consist of a number of organizations coexisting in a relatioM nship that includes elements of both cooperation and competition, just as today the government, the corporations and other large organizations both cooperate and compete with one another. Human freedom mostly will have vanished, because individuals and small groups will be impotent vis-a-vis large organizations armed with supertechnology and an arsenal of advanced psychological and biological tools for manipulating human beings, besides instruments of surveillance and physiM cal coercion. Only a small number of people will have any real power, and even these probably will have only very limited freedom, because their behavior too will be regulated; just as today our politicians and corporation executives can retain their positions of power only as long as their behavior remains within certain fairly narrow limits. t imagine that the systems will stop developing further techniques for controlling human beings and nature once the crisis of the neM is over and increasing control is no longer necessary for the system survival. On the contrary, once the hard times are over the system will increase its control over people and nature more rapidly, because it will no longer be hampered by difficulties of the kind that it is currently experiencing. Survival is not the principal motive for extending control. As we explained in paragraphs 87-90, technicians and scientists carry on their work largely as a suM rrogate activity; that is, they satisfy their need for power by solving technical problems. They will continue to do this with unabated enthusiasm, and among the most interesting and challenging problems for them to solve will be those of understanding the human body and mind and intervening in their development. For the 165. But suppose on the other hand that the stresses of the coming decades prove to be too much for the system. If the systemM breaks down there may be a period of chaos, a such as those that history has recorded at various epochs in the past. It is impossible to predict what would emerge from such a time of troubles, but at any rate the human race would be given a new chance. The greatest danger is that industrial society may begin to reconstitute itself within the first few years after the breakdown. Certainly there will be many people (power-hungry types especially) who wM ill be anxious to get the factories running again. 166. Therefore two tasks confront those who hate the servitude to which the industrial system is reducing the human race. First, we must work to heighten the social stresses within the system so as to increase the likelihood that it will break down or be weakened sufficiently so that a revolution against it becomes possible. Second, it is necessary to develop and propagate an ideology that opposes technology and the ociety if and when the system becomes sufficiently weakened. And such an ideology will help to assure that, if and when industrial society breaks down, its remnants will be smashed beyond repair, so that the system cannot be reconstituted. The factories should be destroyed, technical books burned, etc. 167. The industrial system will not break down purely as a result of revolutionary action. It will not be vulnerable to revolutionary attack unless its own internM al problems of development lead it into very serious difficulties. So if the system breaks down it will do so either spontaneously, or through a process that is in part spontaneous but helped along by revolutionaries. If the breakdown is sudden, many people will die, since the world s population has become so overblown that it cannot even feed itself any longer without advanced technology. Even if the breakdown is gradual enough so that reduction of the population can occur morM through lowering of the birth rate than through elevation of the death rate, the process of de- industrialization probably will be very chaotic and involve much suffering. It is naive to think it likely that technology can be phased out in a smoothly managed, orderly way, especially since the technophiles will fight stubbornly at every step. Is it therefore cruel to work for the breakdown of the system? Maybe, but maybe not. In the first place, revolutionaries will not beM able to break the system down unless it is already in enough trouble so that there would be a good chance of its eventually breaking down by itself anyway; and the bigger the system grows, the more disastrous the consequences of its breakdown will be; so it may be that revolutionaries, by hastening the onset of the breakdown, will be reducing the extent of the disaster. 168. In the second place, one has to balance struggle and death against the loss of freedom and dignity. To mM any of us, freedom and dignity are more important than a long life or avoidance of physical pain. Besides, we all have to die some time, and it may be better to die fighting for survival, or for a cause, than to live a long but empty and purposeless life. 169. In the third place, it is not at all certain that survival of the system will lead to less suffering than breakdown of the system would. The system has already caused, and is continuing to cause, immense suffering all over the world. Ancient cultures, that for hundreds of years gave people a satisfactory relationship with each other and with their environment, have been shattered by contact with industrial society, and the result has been a whole catalogue of economic, environmental, social and psychological problems. One of the effects of the intrusion of industrial society has been that over much of the world traditional controls on population have been thrown out of balance. Hence the populatioM n explosion, with all that that implies. Then there is the psychological suffering that is widespread throughout the supposedly fortunate countries of the West (see paragraphs 44, 45). No one knows what will happen as a result of ozone depletion, the greenhouse effect and other environmental problems that cannot yet be foreseen. And, as nuclear proliferation has shown, new technology cannot be kept out of the hands of dictators and irresponsible Third World nations. ou like to speculate about what Iraq or North Korea will do with genetic engineering? say the technophiles, Science is going to fix all that! We will conquer famine, eliminate psychological suffering, make everybody healthy s what they said 200 years ago. The Industrial Revolution was supposed to eliminate poverty, make everybody happy, etc. The actual result has been quite different. The technophiles are hopelessly lf-deceiving) in their understanding of social problems. They are unaware of (or choose to ignore) the fact that when large changes, even seemingly beneficial ones, are introduced into a society, they lead to a long sequence of other changes, most of which are impossible to predict (paragraph 103). The result is disruption of the society. So it is very probable that in their attempts to end poverty and disease, engineer docile, happy personalities and so forth, the technophiles wM social systems that are terribly troubled, even more so than the present once. For example, the scientists boast that they will end famine by creating new, genetically engineered food plants. But this will allow the human population to keep expanding indefinitely, and it is well known that crowding leads to increased stress and aggression. This is merely one example of the PREDICTABLE problems that will arise. We emphasize that, as past experience has shown, techM nical progress will lead to other new problems that CANNOT be predicted in advance (paragraph 103). In fact, ever since the Industrial Revolution, technology has been creating new problems for society far more rapidly than it has been solving old ones. Thus it will take a long and difficult period of trial and error for the technophiles to work the bugs out of their Brave New World (if they every do). In the meantime there will be great suffering. So it is not at all that the survival of industrial society would involve less suffering than the breakdown of that society would. Technology has gotten the human race into a fix from which there is not likely to be any easy escape. 171. But suppose now that industrial society does survive the next several decades and that the bugs do eventually get worked out of the system, so that it functions smoothly. What kind of system will it be? We will consider several possibilities. us postulate that the computer scientists succeed in developing intelligent machines that can do all things better than human beings can do them. In that case presumably all work will be done by vast, highly organized systems of machines and no human effort will be necessary. Either of two cases might occur. The machines might be permitted to make all of their own decisions without human oversight, or else human control over the machines might be retained. 173. If the machines aM re permitted to make all their own decisions, we can make any conjectures as to the results, because it is impossible to guess how such machines might behave. We only point out that the fate of the human race would be at the mercy of the machines. It might be argued that the human race would never be foolish enough to hand over all power to the machines. But we are suggesting neither that the human race would voluntarily turn power over to the machines nor that the machines wM willfully seize power. What we do suggest is that the human race might easily permit itself to drift into a position of such dependence on the machines that it would have no practical choice but to accept all of the decisions. As society and the problems that face it become more and more complex and as machines become more and more intelligent, people will let machines make more and more of their decisions for them, simply because machine-made decisions wilM l bring better results than man-made ones. Eventually a stage may be reached at which the decisions necessary to keep the system running will be so complex that human beings will be incapable of making them intelligently. At that stage the machines will be in effective control. People won t be able to just turn the machines off, because they will be so dependent on them that turning them off would amount to suicide. 174. On the other hand it is possible that human control overM be retained. In that case the average man may have control over certain private machines of his own, such as his car or his personal computer, but control over large systems of machines will be in the hands of a tiny just as it is today, but with two differences. Due to improved techniques the elite will have greater control over the masses; and because human work will no longer be necessary the masses will be superfluous, a useless burden on the sM ystem. If the elite is ruthless they may simply decide to exterminate the mass of humanity. If they are humane they may use propaganda or other psychological or biological techniques to reduce the birth rate until the mass of humanity becomes extinct, leaving the world to the elite. Or, if the elite consists of soft- hearted liberals, they may decide to play the role of good shepherds to the rest of the human race. They will see to it that everyone s physical needs are satisfieM children are raised under psychologically hygienic conditions, that everyone has a wholesome hobby to keep him busy, and that anyone who may become dissatisfied undergoes life will be so purposeless that people will have to be biologically or psychologically engineered either to remove their need for the power process or to make them their drive for power into some harmless hobby. These engineM ered human beings may be happy in such a society, but they most certainly will not be free. They will have been reduced to the status of domestic animals. 175. But suppose now that the computer scientists do not succeed in developing artificial intelligence, so that human work remains necessary. Even so, machines will take care of more and more of the simpler tasks so that there will be an increasing surplus of human workers at the lower levels of ability. (We see this happeningM already. There are many people who find it difficult or impossible to get work, because for intellectual or psychological reasons they cannot acquire the level of training necessary to make themselves useful in the present system.) On those who are employed, ever-increasing demands will be placed: They will need more and more training, more and more ability, and will have to be ever more reliable, conforming and docile, because they will be more and more like ant organism. Their tasks will be increasingly specialized, so that their work will be, in a sense, out of touch with the real world, being concentrated on one tiny slice of reality. The system will have to use any means that it can, whether psychological or biological, to engineer people to be docile, to have the abilities that the system requires and to their drive for power into some specialized task. But the statement that the people of such a society will havM require qualification. The society may find competitiveness useful, provided that ways are found of directing competitiveness into channels that serve the needs of the system. We can imagine a future society in which there is endless competition for positions of prestige and power. But no more than a very few people will ever reach the top, where the only real power is (see end of paragraph 163). Very repellent is a society in which a person can satisfy hM is need for power only by pushing large numbers of other people out of the way and depriving them of THEIR opportunity for 176. One can envision scenarios that incorporate aspects of more than one of the possibilities that we have just discussed. For instance, it may be that machines will take over most of the work that is of real, practical importance, but that human beings will be kept busy by being given relatively unimportant work. It has been suggested, for example, M a great development of the service industries might provide work for human beings. Thus people would spent their time shining each other driving each other around in taxicabs, making handicrafts for one another, waiting on each other s tables, etc. This seems to us a thoroughly contemptible way for the human race to end up, and we doubt that many people would find fulfilling lives in such pointless busy-work. They would seek other, dangerous outlets (drugsM hate groups) unless they were biologically or psychologically engineered to adapt them to such 177. Needless to say, the scenarios outlined above do not exhaust all the possibilities. They only indicate the kinds of outcomes that seem to us most likely. But we can envision no plausible scenarios that are any more palatable than the ones we ve just described. It is overwhelmingly probable that if the industrial- technological system survivesM the next 40 to 100 years, it will by that time have developed certain general characteristics: Individuals (at least those of the type, who are integrated into the system and make it run, and who therefore have all the power) will be more dependent than ever on large organizations; they will be more than ever and their physical and mental qualities to a significant extent (possibly to a very great extent) will be those that are engineered iM nto them rather than being the results of chance (or of s will, or whatever); and whatever may be left of wild nature will be reduced to remnants preserved for scientific study and kept under the supervision and management of scientists (hence it will no longer be truly wild). In the long run (say a few centuries from now) it is likely that neither the human race nor any other important organisms will exist as we know them today, because once you start modifying organisms tM engineering there is no reason to stop at any particular point, so that the modifications will probably continue until man and other organisms have been utterly transformed. 178. Whatever else may be the case, it is certain that technology is creating for human beings a new physical and social environment radically different from the spectrum of environments to which natural selection has adapted the human race physically and psychologically. If man is not adjusteM this new environment by being artificially re-engineered, then he will be adapted to it through a long and painful process of natural selection. The former is far more likely than the latter. 179. It would be better to dump the whole stinking system and take the 180. The technophiles are taking us all on an utterly reckless ride into the unknown. Many people understand something of what technological progress is doing to us yet take a passive attituM de toward it because they think it is inevitable. But we (FC) don t think it is inevitable. We think it can be stopped, and we will give here some indications of how to go about stopping 181. As we stated in paragraph 166, the two main tasks for the present are to promote social stress and instability in industrial society and to develop and propagate an ideology that opposes technology and the industrial system. When the system becomes sufficiently stressed and unstable, M a revolution against technology may be possible. The pattern would be similar to that of the French and Russian Revolutions. French society and Russian society, for several decades prior to their respective revolutions, showed increasing signs of stress and weakness. Meanwhile, ideologies were being developed that offered a new world view that was quite different from the old one. In the Russian case, revolutionaries were actively working to undermine the old order. Then, wM hen the old system was put under sufficient additional stress (by financial crisis in France, by military defeat in Russia) it was swept away by revolution. What we propose is something along the same lines. 182. It will be objected that the French and Russian Revolutions were failures. But most revolutions have two goals. One is to destroy an old form of society and the other is to set up the new form of society envisioned by the revolutionaries. The French and Russian revolutiM (fortunately!) to create the new kind of society of which they dreamed, but they were quite successful in destroying the old society. We have no illusions about the feasibility of creating a new, ideal form of society. Our goal is only to destroy the existing form of society. 183. But an ideology, in order to gain enthusiastic support, must have a positive ideal as well as a negative one; it must be FOR something as well as AGAINST something. The positive ideal thM at we propose is Nature. That is, WILD nature: those aspects of the functioning of the Earth and its living things that are independent of human management and free of human interference and control. And with wild nature we include human nature, by which we mean those aspects of the functioning of the human individual that are not subject to regulation by organized society but are products of chance, or free will, or God (depending on your religious or philosophical 184. Nature makes a perfect counter-ideal to technology for several reasons. Nature (that which is outside the power of the system) is the opposite of technology (which seeks to expand indefinitely the power of the system). Most people will agree that nature is beautiful; certainly it has tremendous popular appeal. The radical environmentalists ALREADY hold an ideology that exalts nature and opposes technology. [30] It is not necessary for the sake of nature to set up some cM himerical utopia or any new kind of social order. Nature takes care of itself: It was a spontaneous creation that existed long before any human society, and for countless centuries many different kinds of human societies coexisted with nature without doing it an excessive amount of damage. Only with the Industrial Revolution did the effect of human society on nature become really devastating. To relieve the pressure on nature it is not necessary to create a special kind of M social system, it is only necessary to get rid of industrial society. Granted, this will not solve all problems. Industrial society has already done tremendous damage to nature and it will take a very long time for the scars to heal. Besides, even pre-industrial societies can do significant damage to nature. Nevertheless, getting rid of industrial society will accomplish a great deal. It will relieve the worst of the pressure on nature so that the scars can begin to heal. It willM remove the capacity of organized society to keep increasing its control over nature (including human nature). Whatever kind of society may exist after the demise of the industrial system, it is certain that most people will live close to nature, because in the absence of advanced technology there is no other way that people CAN live. To feed themselves they must be peasants or herdsmen or fishermen or hunters, etc. And, generally speaking, local autonomy should tend to incM rease, because lack of advanced technology and rapid communications will limit the capacity of governments or other large organizations to control local communities. 185. As for the negative consequences of eliminating industrial society t eat your cake and have it too. To gain one thing you have to sacrifice another. 186. Most people hate psychological conflict. For this reason they avoid doing any serious thinking about difficult social issues, and they like to haM such issues presented to them in simple, black-and-white terms: THIS is all good and THAT is all bad. The revolutionary ideology should therefore be developed on two levels. 187. On the more sophisticated level the ideology should address itself to people who are intelligent, thoughtful and rational. The object should be to create a core of people who will be opposed to the industrial system on a rational, thought-out basis, with full appreciation of the problems and biguities involved, and of the price that has to be paid for getting rid of the system. It is particularly important to attract people of this type, as they are capable people and will be instrumental in influencing others. These people should be addressed on as rational a level as possible. Facts should never intentionally be distorted and intemperate language should be avoided. This does not mean that no appeal can be made to the emotions, but in making such appeal care should M be taken to avoid misrepresenting the truth or doing anything else that would destroy the intellectual respectability of the ideology. 188. On a second level, the ideology should be propagated in a simplified form that will enable the unthinking majority to see the conflict of technology vs. nature in unambiguous terms. But even on this second level the ideology should not be expressed in language that is so cheap, intemperate or irrational that it alienates people of the thoughM tful and rational type. Cheap, intemperate propaganda sometimes achieves impressive short-term gains, but it will be more advantageous in the long run to keep the loyalty of a small number of intelligently committed people than to arouse the passions of an unthinking, fickle mob who will change their attitude as soon as someone comes along with a better propaganda gimmick. However, propaganda of the rabble-rousing type may be necessary when the system is nearing the point oM f collapse and there is a final struggle between rival ideologies to determine which will become dominant when the old world-view 189. Prior to that final struggle, the revolutionaries should not expect to have a majority of people on their side. History is made by active, determined minorities, not by the majority, which seldom has a clear and consistent idea of what it really wants. Until the time comes for the final push toward revolution [31], the task of revolutM ionaries will be less to win the shallow support of the majority than to build a small core of deeply committed people. As for the majority, it will be enough to make them aware of the existence of the new ideology and remind them of it frequently; though of course it will be desirable to get majority support to the extent that this can be done without weakening the core of seriously committed 190. Any kind of social conflict helps to destabilize the system, but one shouM be careful about what kind of conflict one encourages. The line of conflict should be drawn between the mass of the people and the power-holding elite of industrial society (politicians, scientists, upper-level business executives, government officials, etc.). It should NOT be drawn between the revolutionaries and the mass of the people. For example, it would be bad strategy for the revolutionaries to condemn Americans for their habits of consumption. Instead, the averagM e American should be portrayed as a victim of the advertising and marketing industry, which has suckered him into buying a lot of junk that he doesn t need and that is very poor compensation for his lost freedom. Either approach is consistent with the facts. It is merely a matter of attitude whether you blame the advertising industry for manipulating the public or blame the public for allowing itself to be manipulated. As a matter of strategy one should generally 191. One should think twice before encouraging any other social conflict than that between the power- holding elite (which wields technology) and the general public (over which technology exerts its power). For one thing, other conflicts tend to distract attention from the important conflicts (between power-elite and ordinary people, between technology and nature); for another thing, other conflicts may actually tend to encourage technologization, because each M side in such a conflict wants to use technological power to gain advantages over its adversary. This is clearly seen in rivalries between nations. It also appears in ethnic conflicts within nations. For example, in America many black leaders are anxious to gain power for African Americans by placing back individuals in the technological power-elite. They want there to be many black government officials, scientists, corporation executives and so forth. In this way elping to absorb the African American subculture into the technological system. Generally speaking, one should encourage only those social conflicts that can be fitted into the framework of the conflicts of power-elite vs. ordinary people, technology vs nature. 192. But the way to discourage ethnic conflict is NOT through militant advocacy of minority rights (see paragraphs 21, 29). Instead, the revolutionaries should emphasize that although minorities do suffer more or less advantage, this disadvantage is of peripheral significance. Our real enemy is the industrial- technological system, and in the struggle against the system, ethnic distinctions are of no importance. 193. The kind of revolution we have in mind will not necessarily involve an armed uprising against any government. It may or may not involve physical violence, but it will not be a POLITICAL revolution. Its focus will be on technology and economics, not politics. [32] 194. Probably the revM olutionaries should even AVOID assuming political power, whether by legal or illegal means, until the industrial system is stressed to the danger point and has proved itself to be a failure in the eyes of most people. Suppose for example that some party should win control of the United States Congress in an election. In order to avoid betraying or watering down their own ideology they would have to take vigorous measures to turn economic growth into economic shrinkageM man the results would appear disastrous: There would be massive unemployment, shortages of commodities, etc. Even if the grosser ill effects could be avoided through superhumanly skillful management, still people would have to begin giving up the luxuries to which they have become addicted. Dissatisfaction would grow, the party would be voted out of office and the revolutionaries would have suffered a severe setback. For this reason the revolutiM onaries should not try to acquire political power until the system has gotten itself into such a mess that any hardships will be seen as resulting from the failures of the industrial system itself and not from the policies of the revolutionaries. The revolution against technology will probably have to be a revolution by outsiders, a revolution from below and not from above. 195. The revolution must be international and worldwide. It cannot be carried out on a nation-by-nation baM sis. Whenever it is suggested that the United States, for example, should cut back on technological progress or economic growth, people get hysterical and start screaming that if we fall behind in technology the Japanese will get ahead of us. Holy robots! The world will fly off its orbit if the Japanese ever sell more cars than we do! (Nationalism is a great promoter of technology.) More reasonably, it is argued that if the relatively democratic nations of the world fall behind in technology while nasty, dictatorial nations like China, Vietnam and North Korea continue to progress, eventually the dictators may come to dominate the world. That is why the industrial system should be attacked in all nations simultaneously, to the extent that this may be possible. True, there is no assurance that the industrial system can be destroyed at approximately the same time all over the world, and it is even conceivable that the attempt to overthrow the system cM ould lead instead to the domination of the system by dictators. That is a risk that has to be taken. And it is worth taking, since the difference between a industrial system and one controlled by dictators is small compared with the difference between an industrial system and a non-industrial one. [33] It might even be argued that an industrial system controlled by dictators would be preferable, because dictator-controlled systems usually have cient, hence they are presumably more likely to break down. 196. Revolutionaries might consider favoring measures that tend to bind the world economy into a unified whole. Free trade agreements like NAFTA and GATT are probably harmful to the environment in the short run, but in the long run they may perhaps be advantageous because they foster economic interdependence between nations. It will be easier to destroy the industrial system on a worldwide basis if the worM ld economy is so unified that its breakdown in any one major nation will lead to its breakdown in all industrialized nations. 197. Some people take the line that modern man has too much power, too much control over nature; they argue for a more passive attitude on the part of the human race. At best these people are expressing themselves unclearly, because they fail to distinguish between power for LARGE ORGANIZATIONS and power for INDIVIDUALS and SMALL GROUPS. It is a mistake tM powerlessness and passivity, because people NEED power. Modern man as a collective entity that is, the industrial system has immense power over nature, and we (FC) regard this as evil. But modern INDIVIDUALS and SMALL GROUPS OF INDIVIDUALS have far less power than primitive man ever did. Generally speaking, the vast power of over nature is exercised not by individuals or small groups but by large organizations. To the extent that the averageM modern INDIVIDUAL can wield the power of technology, he is permitted to do so only within narrow limits and only under the supervision and control of the system. (You need a license for everything and with the license come rules and regulations.) The individual has only those technological powers with which the system chooses to provide him. His PERSONAL power over nature is slight. 198. Primitive INDIVIDUALS and SMALL GROUPS actually had considerable power over nature; or maybM e it would be better to say power WITHIN nature. When primitive man needed food he knew how to find and prepare edible roots, how to track game and take it with homemade weapons. He knew how to protect himself from heat, cold, rain, dangerous animals, etc. But primitive man did relatively little damage to nature because the COLLECTIVE power of primitive society was negligible compared to the COLLECTIVE power of industrial society. 199. Instead of arguing for powerlessness and paM ssivity, one should argue that the power of the INDUSTRIAL SYSTEM should be broken, and that this will greatly INCREASE the power and freedom of INDIVIDUALS and SMALL GROUPS. 200. Until the industrial system has been thoroughly wrecked, the destruction of that system must be the revolutionaries ONLY goal. Other goals would distract attention and energy from the main goal. More importantly, if the revolutionaries permit themselves to have any other goal than the destruction ofM technology, they will be tempted to use technology as a tool for reaching that other goal. If they give in to that temptation, they will fall right back into the technological trap, because modern technology is a unified, tightly organized system, so that, in order to retain SOME technology, one finds oneself obliged to retain MOST technology, hence one ends up sacrificing only token amounts of technology. 201. Suppose for example that the revolutionaries took a goal. Human nature being what it is, social justice would not come about spontaneously; it would have to be enforced. In order to enforce it the revolutionaries would have to retain central organization and control. For that they would need rapid long-distance transportation and communication, and therefore all the technology needed to support the transportation and communication systems. To feed and clothe poor people they would have to use agricultural and manufacturinM g technology. And so forth. So that the attempt to insure social justice would force them to retain most parts of the technological system. Not that we have anything against social justice, but it must not be allowed to interfere with the effort to get rid of the technological system. 202. It would be hopeless for revolutionaries to try to attack the system without using SOME modern technology. If nothing else they must use the communications media to spread their message. But tM hey should use modern technology for only ONE purpose: to attack the technological system. 203. Imagine an alcoholic sitting with a barrel of wine in front of him. Suppose he starts saying to himself, t bad for you if used in moderation. Why, they say small amounts of wine are even good for you! It won any harm if I take just one little drink.... Well you know what is going to happen. Never forget that the human race with technology is just like an coholic with a barrel of wine. 204. Revolutionaries should have as many children as they can. There is strong scientific evidence that social attitudes are to a significant extent inherited. No one suggests that a social attitude is a direct outcome of s genetic constitution, but it appears that personality traits are partly inherited and that certain personality traits tend, within the context of our society, to make a person more likely to hold this or that ttitude. Objections to these findings have been raised, but the objections are feeble and seem to be ideologically motivated. In any event, no one denies that children tend on the average to hold social attitudes similar to those of their parents. From our point of view it doesn all that much whether the attitudes are passed on genetically or through childhood training. In either case they ARE passed on. 205. The trouble is that many of the people who are inclined to rebel M the industrial system are also concerned about the population problems, hence they are apt to have few or no children. In this way they may be handing the world over to the sort of people who support or at least accept the industrial system. To insure the strength of the next generation of revolutionaries the present generation should reproduce itself abundantly. In doing so they will be worsening the population problem only slightly. And the important problem is toM get rid of the industrial system, because once the industrial system is gone the world s population necessarily will decrease (see paragraph 167); whereas, if the industrial system survives, it will continue developing new techniques of food production that may enable the world s population to keep increasing almost indefinitely. 206. With regard to revolutionary strategy, the only points on which we absolutely insist are that the single overriding goal must be the ion of modern technology, and that no other goal can be allowed to compete with this one. For the rest, revolutionaries should take an empirical approach. If experience indicates that some of the recommendations made in the foregoing paragraphs are not going to give good results, then those recommendations should be discarded. TWO KINDS OF TECHNOLOGY 207. An argument likely to be raised against our proposed revolution is that it is bound to fail, because (it is claimed) throughout hiM story technology has always progressed, never regressed, hence technological regression is impossible. But this claim is false. 208. We distinguish between two kinds of technology, which we will call small-scale technology and organization-dependent technology. Small-scale technology is technology that can be used by small-scale communities without outside assistance. Organization-dependent technology is technology that depends on large-scale social organization. We are aware ofM significant cases of regression in small-scale technology. But organization-dependent technology DOES regress when the social organization on which it depends breaks down. Example: When the Roman Empire fell apart small-scale technology survived because any clever village craftsman could build, for instance, a water wheel, any skilled smith could make steel by Roman methods, and so forth. But the Romans organization-dependent technology DID regress. TheM ir aqueducts fell into disrepair and were never rebuilt. Their techniques of road construction were lost. The Roman system of urban sanitation was forgotten, so that not until rather recent times did the sanitation of European cities equal that of Ancient Rome. 209. The reason why technology has seemed always to progress is that, until perhaps a century or two before the Industrial Revolution, most technology was small-scale technology. But most of the technology developed sinceM Industrial Revolution is organization-dependent technology. Take the refrigerator for example. Without factory-made parts or the facilities of a post-industrial machine shop it would be virtually impossible for a handful of local craftsmen to build a refrigerator. If by some miracle they did succeed in building one it would be useless to them without a reliable source of electric power. So they would have to dam a stream and build a generator. Generators require large M amounts of copper wire. Imagine trying to make that wire without modern machinery. And where would they get a gas suitable for refrigeration? It would be much easier to build an icehouse or preserve food by drying or picking, as was done before the invention of the refrigerator. 210. So it is clear that if the industrial system were once thoroughly broken down, refrigeration technology would quickly be lost. The same is true of other organization-dependent technology. And once tM his technology had been lost for a generation or so it would take centuries to rebuild it, just as it took centuries to build it the first time around. Surviving technical books would be few and scattered. An industrial society, if built from scratch without outside help, can only be built in a series of stages: You need tools to make tools to make tools to make tools ... . A long process of economic development and progress in social organization is required. the absence of an ideology opposed to technology, there is no reason to believe that anyone would be interested in rebuilding industrial society. The enthusiasm for is a phenomenon peculiar to the modern form of society, and it seems not to have existed prior to the 17th century or thereabouts. 211. In the late Middle Ages there were four main civilizations that were about : Europe, the Islamic world, India, and the Far East (China, Japan, KoM rea). Three of those civilizations remained more or less stable, and only Europe became dynamic. No one knows why Europe became dynamic at that time; historians have their theories but these are only speculation. At any rate, it is clear that rapid development toward a technological form of society occurs only under special conditions. So there is no reason to assume that a long-lasting technological regression cannot be brought about. 212. Would society EVENTUALLY develop againM toward an industrial-technological form? Maybe, but there is no use in worrying about it, since we can predict or control events 500 or 1,000 years in the future. Those problems must be dealt with by the people who will live at that time. THE DANGER OF LEFTISM 213. Because of their need for rebellion and for membership in a movement, leftists or persons of similar psychological type often are unattracted to a rebellious or activist movement whose goals and membership are not initially leftist. The resulting influx of leftish types can easily turn a non-leftist movement into a leftist one, so that leftist goals replace or distort the original goals of the movement. 214. To avoid this, a movement that exalts nature and opposes technology must take a resolutely anti-leftist stance and must avoid all collaboration with leftists. Leftism is in the long run inconsistent with wild nature, with human freedom and with the elimination of modern technology. LeftiM collectivist; it seeks to bind together the entire world (both nature and the human race) into a unified whole. But this implies management of nature and of human life by organized society, and it requires advanced technology. You can t have a united world without rapid transportation and communication, you can t make all people love one another without sophisticated psychological techniques, you can without the necessary technologM ical base. Above all, leftism is driven by the need for power, and the leftist seeks power on a collective basis, through identification with a mass movement or an organization. Leftism is unlikely ever to give up technology, because technology is too valuable a source of collective power. 215. The anarchist [34] too seeks power, but he seeks it on an individual or small-group basis; he wants individuals and small groups to be able to control the circumstances of their own livesM . He opposes technology because it makes small groups dependent on large organizations. 216. Some leftists may seem to oppose technology, but they will oppose it only so long as they are outsiders and the technological system is controlled by non-leftists. If leftism ever becomes dominant in society, so that the technological system becomes a tool in the hands of leftists, they will enthusiastically use it and promote its growth. In doing this they will be repeating a pattern thM at leftism has shown again and again in the past. When the Bolsheviks in Russia were outsiders, they vigorously opposed censorship and the secret police, they advocated self-determination for ethnic minorities, and so forth; but as soon as they came into power themselves, they imposed a tighter censorship and created a more ruthless secret police than any that had existed under the tsars, and they oppressed ethnic minorities at least as much as the tsars had done. In the United States, a couple of decades ago when leftists were a minority in our universities, leftist professors were vigorous proponents of academic freedom, but today, in those of our universities where leftists have become dominant, they have shown themselves ready to take away from everyone s academic freedom. (This is political correctness. happen with leftists and technology: They will use it to oppress everyone else if they ever get it under their ownM 217. In earlier revolutions, leftists of the most power-hungry type, repeatedly, have first cooperated with non-leftist revolutionaries, as well as with leftists of a more libertarian inclination, and later have double- crossed them to seize power for themselves. Robespierre did this in the French Revolution, the Bolsheviks did it in the Russian Revolution, the communists did it in Spain in 1938 and Castro and his followers did it in Cuba. Given the past history of lefM tism, it would be utterly foolish for non-leftist revolutionaries today to collaborate with leftists. 218. Various thinkers have pointed out that leftism is a kind of religion. Leftism is not a religion in the strict sense because leftist doctrine does not postulate the existence of any supernatural being. But, for the leftist, leftism plays a psychological role much like that which religion plays for some people. The leftist NEEDS to believe in leftism; it plays a vital role inM his psychological economy. His beliefs are not easily modified by logic or facts. He has a deep conviction that leftism is morally Right with a capital R, and that he has not only a right but a duty to impose leftist morality on everyone. (However, many of the people we are referring to as do not think of themselves as leftists and would not describe their system of beliefs as leftism. We use the term t know of any better words to deM signate the spectrum of related creeds that includes the feminist, gay rights, political correctness, etc., movements, and because these movements have a strong affinity with the old left. See paragraphs 227-230.) 219. Leftism is a totalitarian force. Wherever leftism is in a position of power it tends to invade every private corner and force every thought into a leftist mold. In part this is because of the quasi-religious character of leftism; everything contrary to leftist belM iefs represents Sin. More importantly, leftism is a totalitarian force because of the leftists for power. The leftist seeks to satisfy his need for power through identification with a social movement and he tries to go through the power process by helping to pursue and attain the goals of the movement (see paragraph 83). But no matter how far the movement has gone in attaining its goals the leftist is never satisfied, because his activism is a surrogate ee paragraph 41). That is, the leftist s real motive is not to attain the ostensible goals of leftism; in reality he is motivated by the sense of power he gets from struggling for and then reaching a social goal. [35] Consequently the leftist is never satisfied with the goals he has already attained; his need for the power process leads him always to pursue some new goal. The leftist wants equal opportunities for minorities. When that is attained he insists on statistical equalM ity of achievement by minorities. And as long as anyone harbors in some corner of his mind a negative attitude toward some minority, the leftist has to re-educated him. And ethnic minorities are not enough; no one can be allowed to have a negative attitude toward homosexuals, disabled people, fat people, old people, ugly people, and on and on and on. It s not enough that the public should be informed about the hazards of smoking; a warning has to be stamped on every packaM ge of cigarettes. Then cigarette advertising has to be restricted if not banned. The activists will never be satisfied until tobacco is outlawed, and after that it will be alcohol, then junk food, etc. Activists have fought gross child abuse, which is reasonable. But now they want to stop all spanking. When they have done that they will want to ban something else they consider unwholesome, then another thing and then another. They will never be satisfied until they have complete M all child rearing practices. And then they will move on to another cause. 220. Suppose you asked leftists to make a list of ALL the things that were wrong with society, and then suppose you instituted EVERY social change that they demanded. It is safe to say that within a couple of years the majority of leftists would find something new to complain about, some new social to correct because, once again, the leftist is motivated less by distress s ills than by the need to satisfy his drive for power by imposing his solutions on society. 221. Because of the restrictions placed on their thoughts and behavior by their high level of socialization, many leftists of the over-socialized type cannot pursue power in the ways that other people do. For them the drive for power has only one morally acceptable outlet, and that is in the struggle to impose their morality on everyone. 222. Leftists, especially those of the oversocializedM type, are True Believers in the sense of Eric Hoffer Believers are of the same psychological type as leftists. Presumably a true-believing nazi, for instance, is very different psychologically from a true-believing leftist. Because of their capacity for single-minded devotion to a cause, True Believers are a useful, perhaps a necessary, ingredient of any revolutionary movement. This presents a problem with which we must aM t know how to deal. We aren harness the energies of the True Believer to a revolution against technology. At present all we can say is that no True Believer will make a safe recruit to the revolution unless his commitment is exclusively to the destruction of technology. If he is committed also to another ideal, he may want to use technology as a tool for pursuing that other ideal (see paragraphs 220, 221). 223. Some readers may say, out leftism is a lot of crap. I know John and Jane who are leftish types and they don totalitarian tendencies. s quite true that many leftists, possibly even a numerical majority, are decent people who sincerely believe in tolerating values (up to a point) and wouldn t want to use high-handed methods to reach their social goals. Our remarks about leftism are not meant to apply to every individual leftist but to describe the general characterM leftism as a movement. And the general character of a movement is not necessarily determined by the numerical proportions of the various kinds of people involved in the movement. 224. The people who rise to positions of power in leftist movements tend to be leftists of the most power- hungry type, because power-hungry people are those who strive hardest to get into positions of power. Once the power-hungry types have captured control of the movement, there are many tists of a gentler breed who inwardly disapprove of many of the actions of the leaders, but cannot bring themselves to oppose them. They NEED their faith in the movement, and because they cannot give up this faith they go along with the leaders. True, SOME leftists do have the guts to oppose the totalitarian tendencies that emerge, but they generally lose, because the power-hungry types are better organized, are more ruthless and Machiavellian and have taken care to build themselM ves a strong power base. 225. These phenomena appeared clearly in Russia and other countries that were taken over by leftists. Similarly, before the breakdown of communism in the USSR, leftish types in the West would seldom criticize that country. If prodded they would admit that the USSR did many wrong things, but then they would try to find excuses for the communists and begin talking about the faults of the West. They always opposed Western military resistance to gression. Leftish types all over the world vigorously protested the U.S. military action in Vietnam, but when the USSR invaded Afghanistan they did nothing. Not that they approved of the Soviet actions; but because of their leftist faith, they just couldn t bear to put themselves in opposition to communism. Today, in those of our universities where political correctness has become dominant, there are probably many leftish types who privately disapprove of the suppression M freedom, but they go along with it anyway. 226. Thus the fact that many individual leftists are personally mild and fairly tolerant people by no means prevents leftism as a whole form having a totalitarian tendency. 227. Our discussion of leftism has a serious weakness. It is still far from clear what we mean by the word t seem to be much we can do about this. Today leftism is fragmented into a whole spectrum of activist movements. Yet nM ot all activist movements are leftist, and some activist movements (e.g., radical environmentalism) seem to include both personalities of the leftist type and personalities of thoroughly un-leftist types who ought to know better than to collaborate with leftists. Varieties of leftists fade out gradually into varieties of non-leftists and we ourselves would often be hard-pressed to decide whether a given individual is or is not a leftist. To the extent that it is defined all, our conception of leftism is defined by the discussion of it that we have given in this article, and we can only advise the reader to use his own judgment in deciding who is a leftist. 228. But it will be helpful to list some criteria for diagnosing leftism. These criteria cannot be applied in a cut and dried manner. Some individuals may meet some of the criteria without being leftists, some leftists may not meet any of the criteria. Again, you just have to use your judgment. 9. The leftist is oriented toward large-scale collectivism. He emphasizes the duty of the individual to serve society and the duty of society to take care of the individual. He has a negative attitude toward individualism. He often takes a moralistic tone. He tends to be for gun control, for sex education and other psychologically educational methods, for social planning, for affirmative action, for multiculturalism. He tends to identify with victims. He tends tM o be against competition and against violence, but he often finds excuses for those leftists who do commit violence. He is fond of using the common catch- phrases of the left, like responsibility. Maybe the best diagnostic trait of the leftist is his tendency to sympathize with the following movements: femM ethnic rights, disability rights, animal rights, political correctness. Anyone who strongly sympathizes with ALL of these movements is almost certainly a leftist. [36] 230. The more dangerous leftists, that is, those who are most power-hungry, are often characterized by arrogance or by a dogmatic approach to ideology. However, the most dangerous leftists of all may be certain oversocialized types who avoid irritating displays of aggressiveness and refrain fromM advertising their leftism, but work quietly and unobtrusively to promote collectivist values, psychological techniques for socializing children, dependence of the individual on the system, and so forth. These crypto- leftists (as we may call them) approximate certain bourgeois types as far as practical action is concerned, but differ from them in psychology, ideology and motivation. The ordinary bourgeois tries to bring people under control of the system M in order to protect his way of life, or he does so simply because his attitudes are conventional. The crypto-leftist tries to bring people under control of the system because he is a True Believer in a collectivistic ideology. The crypto-leftist is differentiated from the average leftist of the oversocialized type by the fact that his rebellious impulse is weaker and he is more securely socialized. He is differentiated from the ordinary well-socialized bourgeois by the factM that there is some deep lack within him that makes it necessary for him to devote himself to a cause and immerse himself in a collectivity. And maybe his (well-sublimated) drive for power is stronger than that of the average bourgeois. 231. Throughout this article we ve made imprecise statements and statements that ought to have had all sorts of qualifications and reservations attached to them; and some of our statements may be flatly false. Lack of sufficient ormation and the need for brevity made it impossible for us to formulate our assertions more precisely or add all the necessary qualifications. And of course in a discussion of this kind one must rely heavily on intuitive judgment, and that can sometimes be wrong. So we don article expresses more than a crude approximation to the truth. 232. All the same, we are reasonably confident that the general outlines of the picture we have painted here are roughly correct. M Just one possible weak point needs to be mentioned. We have portrayed leftism in its modern form as a phenomenon peculiar to our time and as a symptom of the disruption of the power process. But we might possibly be wrong about this. Oversocialized types who try to satisfy their drive for power by imposing their morality on everyone have certainly been around for a long time. But we THINK that the decisive role played by feelings of inferiority, low self-esteem, powerlessneM ss, identification with victims by people who are not themselves victims, is a peculiarity of modern leftism. Identification with victims by people not themselves victims can be seen to some extent in 19th century leftism and early Christianity but as far as we can make out, symptoms of low self-esteem, etc., were not nearly so evident in these movements, or in any other movements, as they are in modern leftism. But we are not in a position to assert confidently that no such moveM existed prior to modern leftism. This is a significant question to which historians ought to give their attention. 1. (Paragraph 19) We are asserting that ALL, or even most, bullies and ruthless competitors suffer from feelings of inferiority. 2. (Paragraph 25) During the Victorian period many oversocialized people suffered from serious psychological problems as a result of repressing or trying to repress their sexual feelings. Freud apparently based his theories eople of this type. Today the focus of socialization has shifted from sex 3. (Paragraph 27) Not necessarily including specialists in engineering or the 4. (Paragraph 28) There are many individuals of the middle and upper classes who resist some of these values, but usually their resistance is more or less covert. Such resistance appears in the mass media only to a very limited extent. The main thrust of propaganda in our society is in favor of the The main reason why these values have become, so to speak, the official values of our society is that they are useful to the industrial system. Violence is discouraged because it disrupts the functioning of the system. Racism is discouraged because ethnic conflicts also disrupt the system, and discrimination wastes the talents of minority-group members who could be useful to the system. Poverty must be because the underclass causes problems for the system and contacM t with the underclass lowers the morale of the other classes. Women are encouraged to have careers because their talents are useful to the system and, more importantly, because by having regular jobs women become better integrated into the system and tied directly to it rather than to their families. This helps to weaken family solidarity. (The leaders of the system say they want to strengthen the family, but they really mean is that they want the family to serve as an effective tool for cializing children in accord with the needs of the system. We argue in paragraphs 51, 52 that the system cannot afford to let the family or other small-scale social groups be strong or autonomous.) 5. (Paragraph 42) It may be argued that the majority of people don make their own decisions but want leaders to do their thinking for them. There is an element of truth in this. People like to make their own decisions in small matters, but making decisions on difficult, fundamental questiM requires facing up to psychological conflict, and most people hate psychological conflict. Hence they tend to lean on others in making difficult decisions. But it does not follow that they like to have decisions imposed upon them without having any opportunity to influence those decisions. The majority of people are natural followers, not leaders, but they like to have direct personal access to their leaders, they want to be able to influence the leaders and participate to some extenM t in making even the difficult decisions. At least to that degree they need autonomy. 6. (Paragraph 44) Some of the symptoms listed are similar to those shown by To explain how these symptoms arise from deprivation with respect to the Common-sense understanding of human nature tells one that lack of goals whose attainment requires effort leads to boredom and that boredom, long continued, often leads eventually to depression. Failure to attain goals leaM frustration and lowering of self-esteem. Frustration leads to anger, anger to aggression, often in the form of spouse or child abuse. It has been shown that long-continued frustration commonly leads to depression and that depression tends to cause guilt, sleep disorders, eating disorders and bad feelings about oneself. Those who are tending toward depression seek pleasure as an antidote; hence insatiable hedonism and excessive sex, with perversions as a means of getting new kicks. M Boredom too tends to cause excessive pleasure-seeking since, lacking other goals, people often use pleasure as a goal. See accompanying The foregoing is a simplification. Reality is more complex, and of course, deprivation with respect to the power process is not the ONLY cause of the symptoms described. By the way, when we mention depression we do not necessarily mean depression that is severe enough to be treated by a psychiatrist. Often only mild forms of are involved. And when we speak of goals we do not necessarily mean long-term, thought-out goals. For many or most people through much of human history, the goals of a hand-to-mouth existence (merely providing oneself and s family with food from day to day) have been quite sufficient. 7. (Paragraph 52) A partial exception may be made for a few passive, inward-looking groups, such as the Amish, which have little effect on the wider society. Apart from these, some genuine small-scale commuM exist in America today. For instance, youth gangs and regards them as dangerous, and so they are, because the members of these groups are loyal primarily to one another rather than to the system, hence the system cannot control them. Or take the gypsies. The gypsies commonly get away with theft and fraud because their loyalties are such that they can always get other gypsies to give their innocence. Obviously the system wouldM serious trouble if too many people belonged to such groups. Some of the early-20th century Chinese thinkers who were concerned with modernizing China recognized the necessity breaking down small-scale social groups such as the family: (According to Sun Yat-sen) the Chinese people needed a new surge of patriotism, which would lead to a transfer of loyalty from the family to the state.... (According to Li Huang) traditional attachments, particularly to the family had to be abaM ndoned if nationalism were to develop in Chinese Political Thought in the Twentieth Century, page 125, page 297.) 8. (Paragraph 56) Yes, we know that 19th century America had its problems, and serious ones, but for the sake of brevity we have to express ourselves in simplified terms. 9. (Paragraph 61) We leave aside the We are speaking of the 10. (Paragraph 62) Some social scientists, educators, als and the like are doing their best to push the social drives into group 1 by trying to see to it that everyone has a satisfactory social 11. (Paragraphs 63, 82) Is the drive for endless material acquisition really an artificial creation of the advertising and marketing industry? Certainly there is no innate human drive for material acquisition. There have been many cultures in which people have desired little material wealth beyond what was necessary to satisfy their basic phM ysical needs (Australian aborigines, traditional Mexican peasant culture, some African cultures). On the other hand there have also been many pre-industrial cultures in which material acquisition has played an important role. So we can s acquisition-oriented culture is exclusively a creation of the advertising and marketing industry. But it is clear that the advertising and marketing industry has had an important part in creating that culture. The ations that spend millions on advertising wouldn that kind of money without solid proof that they were getting it back in increased sales. One member of FC met a sales manager a couple of years ago who was frank enough to tell him, Our job is to make people buy things they He then described how an untrained novice could present people with the facts about a product, and make no sales at all, while a trained and experienced professionM al salesman would make lots of sales to the same people. This shows that people are manipulated into buying 12. (Paragraph 64) The problem of purposelessness seems to have become less serious during the last 15 years or so, because people now feel less secure physically and economically than they did earlier, and the need for security provides them with a goal. But purposelessness has been replaced by frustration over the difficulty of attaining securiM ty. We emphasize the problem of purposelessness because the liberals and leftists would wish to solve our social problems by having society guarantee everyone but if that could be done it would only bring back the problem of purposelessness. The real issue is not whether society provides well or poorly for people s security; the trouble is that people are dependent on the system for their security rather than having it in their own hands. This, by the way, is part M of the reason why some people get worked up about the right to bear arms; possession of a gun puts that aspect of their security in their own hands. 13. (Paragraph 66) Conservatives efforts to decrease the amount of government regulation are of little benefit to the average man. For one thing, only a fraction of the regulations can be eliminated because most regulations are necessary. For another thing, most of the deregulation affects business rather than the average individual, sM o that its main effect is to take power from the government and give it to private corporations. What this means for the average man is that government interference in his life is replaced by interference from big corporations, which may be permitted, for example, to dump more chemicals that get into his water supply and give him cancer. The conservatives are just taking the average man for a sucker, exploiting his resentment of Big Government to promote the power of Big Business. (Paragraph 73) When someone approves of the purpose for which propaganda is being used in a given case, he generally calls it it some similar euphemism. But propaganda is propaganda regardless of the purpose for which it is used. 15. (Paragraph 83) We are not expressing approval or disapproval of the Panama invasion. We only use it to illustrate a point. 16. (Paragraph 95) When the American colonies were under British rule there were fewer and less effectiveM legal guarantees of freedom than there were after the American Constitution went into effect, yet there was more personal freedom in pre-industrial America, both before and after the War of Independence, than there was after the Industrial Revolution took hold in this country. We quote from Violence in America: Historical and Comparative edited by Hugh Davis Graham and Ted Robert Gurr, Chapter 12 by Roger Lane, pages 476-478: The progressive heighteningM of standards of propriety, and with it the increasing reliance on official law enforcement (in 19th century America) ... were common to the whole society.... [T]he change in social behavior is so long term and so widespread as to suggest a connection with the most fundamental of contemporary social processes; that of industrial urbanization Massachusetts in 1835 had a population of some 660,940, 81 percent rural, overwhelmingly preindustrial and native born. It considerable personal freedom. Whether teamsters, farmers or artisans, they were all accustomed to setting their own schedules, and the nature of their work made them physically independent of each other.... Individual problems, sins or even crimes, were not generally cause for wider social concern.... the twin movements to the city and to the factory, both just gathering force in 1835, had a progressive effect on personal behavior throughout the 19tM and into the 20th. The factory demanded regularity of behavior, a life governed by obedience to the rhythms of clock and calendar, the demands of foreman and supervisor. In the city or town, the needs of living in closely packed neighborhoods inhibited many actions previously unobjectionable. Both blue- and white-collar employees in larger establishments were mutually dependent on their fellows; as one man s work fit into anther The results of the new organization of life and work were apparent by 1900, when some 76 percent of the 2,805,346 inhabitants of Massachusetts were classified as urbanites. Much violent or irregular behavior which had been tolerable in a casual, independent society was no longer acceptable in the more formalized, cooperative atmosphere of the later period.... The move to the cities had, in short, produced a more tractable, more socialized, more eneration than its predecessors. 17. (Paragraph 117) Apologists for the system are fond of citing cases in which elections have been decided by one or two votes, but such cases are rare. 18. (Paragraph 119) Today, in technologically advanced lands, men live very similar lives in spite of geographical, religious, and political differences. The daily lives of a Christian bank clerk in Chicago, a Buddhist bank clerk in Tokyo, and a Communist bank clerk in Moscow are far more alike thanM the life of any one of them is like that of any single man who lived a thousand years ago. These similarities are the result of a common technology.... L. Sprague de Camp, The Ancient Engineers, Ballantine edition, page 17. The lives of the three bank clerks are not IDENTICAL. Ideology does have SOME effect. But all technological societies, in order to survive, must evolve along APPROXIMATELY the same trajectory. 19. (Paragraph 123) Just think an irresponsible genetic enM gineer might create a lot of terrorists. 20. (Paragraph 124) For a further example of undesirable consequences of medical progress, suppose a reliable cure for cancer is discovered. Even if the treatment is too expensive to be available to any but the elite, it will greatly reduce their incentive to stop the escape of carcinogens into the 21. (Paragraph 128) Since many people may find paradoxical the notion that a large number of good things can add up to a bad thing, weM an analogy. Suppose Mr. A is playing chess with Mr. B. Mr. C, a Grand Master, is looking over Mr. A s shoulder. Mr. A of course wants to win his game, so if Mr. C points out a good move for him to make, he is doing Mr. A a favor. But suppose now that Mr. C tells Mr. A how to make ALL of his moves. In each particular instance he does Mr. A a favor by showing him his best move, but by making ALL of his moves for him he spoils his game, since there is not point in M s playing the game at all if someone else makes The situation of modern man is analogous to that of Mr. A. The system makes an s life easier for him in innumerable ways, but in doing so it deprives him of control over his own fate. 22. (Paragraph 137) Here we are considering only the conflict of values within the mainstream. For the sake of simplicity we leave out of the picture values like the idea that wild nature is more importM human economic welfare. 23. (Paragraph 137) Self-interest is not necessarily MATERIAL self-interest. It can consist in fulfillment of some psychological need, for example, by s own ideology or religion. 24. (Paragraph 139) A qualification: It is in the interest of the system to permit a certain prescribed degree of freedom in some areas. For example, economic freedom (with suitable limitations and restraints) has proved effective in promoting economic growth.M But only planned, circumscribed, limited freedom is in the interest of the system. The individual must always be kept on a leash, even if the leash is sometimes long (see paragraphs 94, 25. (Paragraph 143) We don t mean to suggest that the efficiency or the potential for survival of a society has always been inversely proportional to the amount of pressure or discomfort to which the society subjects people. That certainly is not the case. There is good reason to believe that many primitive societies subjected people to less pressure than European society did, but European society proved far more efficient than any primitive society and always won out in conflicts with such societies because of the advantages conferred by technology. 26. (Paragraph 147) If you think that more effective law enforcement is unequivocally good because it suppresses crime, then remember that crime as defined by the system is not necessarily what YOU would call crime. Today, smoking marijuana is a and, in some places in the U.S., so is possession of an unregistered handgun. Tomorrow, possession of ANY firearm, registered or not, may be made a crime, and the same thing may happen with disapproved methods of child-rearing, such as spanking. In some countries, expression of dissident political opinions is a crime, and there is no certainty that this will never happen in the U.S., since no constitution or political system lasts forever. If a society needs a large, powerful law enforcement establishment, then there is something gravely wrong with that society; it must be subjecting people to severe pressures if so many refuse to follow the rules, or follow them only because forced. Many societies in the past have gotten by with little or no formal law- enforcement. 27. (Paragraph 151) To be sure, past societies have had means of influencing human behavior, but these have been primitive and of low effectiveness d with the technological means that are now being developed. 28. (Paragraph 152) However, some psychologists have publicly expressed opinions indicating their contempt for human freedom. And the mathematician Claude Shannon was quoted in Omni (August 1987) as saying, I visualize a time when we will be to robots what dogs are to humans, and I 29. (Paragraph 154) This is no science fiction! After writing paragraph 154 we came across an article in ScientifM ic American according to which scientists are actively developing techniques for identifying possible future criminals and for treating them by a combination of biological and psychological means. Some scientists advocate compulsory application of the treatment, which may be available in the near future. (See Seeking the Criminal by W. Wayt Gibbs, Scientific American, March 1995.) Maybe you think this is OK because the treatment would be applied to those who might ecome violent criminals. But of course it won t stop there. Next, a treatment will be applied to those who might become drunk drivers (they endanger human life too), then perhaps to peel who spank their children, then to environmentalists who sabotage logging equipment, eventually to anyone whose behavior is inconvenient for the system. 30. (Paragraph 184) A further advantage of nature as a counter-ideal to technology is that, in many people, nature inspires the kind of reverence at is associated with religion, so that nature could perhaps be idealized on a religious basis. It is true that in many societies religion has served as a support and justification for the established order, but it is also true that religion has often provided a basis for rebellion. Thus it may be useful to introduce a religious element into the rebellion against technology, the more so because Western society today has no strong religious foundation. Religion, nowadays either is used M transparent support for narrow, short-sighted selfishness (some conservatives use it this way), or even is cynically exploited to make easy money (by many evangelists), or has degenerated into crude irrationalism (fundamentalist protestant sects, ), or is simply stagnant (Catholicism, main-line Protestantism). The nearest thing to a strong, widespread, dynamic religion that the West has seen in recent times has been the quasi-religion of leftism, but leftisM m today is fragmented and has no clear, unified, inspiring goal. Thus there is a religious vacuum in our society that could perhaps be filled by a religion focused on nature in opposition to technology. But it would be a mistake to try to concoct artificially a religion to fill this role. Such an invented religion would probably be a failure. Take the example. Do its adherents REALLY believe in it or are they just play-acting? If they are just play-actiM ng their religion will be a flop in the end. It is probably best not to try to introduce religion into the conflict of nature vs. technology unless you REALLY believe in that religion yourself and find that it arouses a deep, strong, genuine response in many other people. 31. (Paragraph 189) Assuming that such a final push occurs. Conceivably the industrial system might be eliminated in a somewhat gradual or piecemeal fashion (see paragraphs 4, 167 and Note 4). 32. (Paragraph 193) It M is even conceivable (remotely) that the revolution might consist only of a massive change of attitudes toward technology resulting in a relatively gradual and painless disintegration of the industrial system. But if this happens we ll be very lucky. It s far more probably that the transition to a nontechnological society will be very difficult and full of conflicts and disasters. 33. (Paragraph 195) The economic and technological structure of a society are far more important than M its political structure in determining the way the average man lives (see paragraphs 95, 119 and Notes 16, 18). 34. (Paragraph 215) This statement refers to our particular brand of anarchism. A wide variety of social attitudes have been called be that many who consider themselves anarchists would not accept our statement of paragraph 215. It should be noted, by the way, that there is a nonviolent anarchist movement whose members probably would not accept FC as anarchist and certainly would not approve of FC 35. (Paragraph 219) Many leftists are motivated also by hostility, but the hostility probably results in part from a frustrated need for power. 36. (Paragraph 229) It is important to understand that we mean someone who sympathizes with these MOVEMENTS as they exist today in our society. One who believes that women, homosexuals, etc., should have equal rights is not necessary a leftist. The feminist, gay rights, etc., moveM ments that exist in our society have the particular ideological tone that characterizes leftism, and if one believes, for example, that women should have equal rights it does not necessarily follow that one must sympathize with the feminist movement as it exists today. If copyright problems make it impossible for this long quotation to be printed, then please change Note 16 to read as follows: 16. (Paragraph 95) When the American colonies were under British rule there were effective legal guarantees of freedom than there were after the American Constitution went into effect, yet there was more personal freedom in pre-industrial America, both before and after the War of Independence, than there was after the Industrial Revolution took hold in this country. In Violence in America: Historical and Comparative edited by Hugh Davis Graham and Ted Robert Gurr, Chapter 12 by Roger Lane, it is explained how in pre-industrial America the averaL person had greater independence and autonomy than he does today, and how the process of industrialization necessarily led to the restriction of personal SjLP1ero q puso nonfinancial data en bloque es 1ero q dijo c/Foundry USA Pool #dropgold/ IjGREFUND:A1160A936F21C75928B8CB609A64DB506EEDAC042200A18094A68E7E385D9A2B LjJProcertif:578a55eff2b5a508af6533a09449ee3b0fd200cec5f2688dc8f004490ce3bc87 &"""""""""""""""""""""""""""""""""""""""r text/plain;charset=utf-8 text/html;charset=utf-8 "title": "Ordinal Loops", "description": "Do Not Fiat", "storyline": "First chapter is observing the hostile FIAT environment in which Bitcoin has been born from the ashes of GFC. Such a contemporary landscape remains a living organism - loop. Furiously, Bitcoin ASCII fights the army of five main government currencies - ad infinitum. Conceptualization starts with 'Object 0' being a circular donut of currency interaction. Over time, these objects are deciphered into abstraction [Object 1 tM o 5] and the 'Object 6' has the Bitcoin logo highlighted and colour coded demonstrating adversity against the government issued currency.", "website": "https://www.ordinalloops.xyz/", "twitter": "https://twitter.com/Ordinal_Loops" { "Object": "0" "inscription": "c2fe83b53f4eb0b8ba2b4748884727887f840332ef02f3f79b455fcf3a3d11ebi0", } { "Object": "1" "inscription": "2e785256005b2aee00fd22038d99891e0026d2620e3c4180c9c2d9706fe4b364i0", } { "Object": "2" "inscription": "dd1f515b828eedabd6b0M be147cf611ca08c20f39058feee9b96efaa2eba43d9di0", } { "Object": "3" "inscription": "821eec8cf7672e232aa34998d0a92e638decd5aab9f36c30bccdaedec662a829i0", } { "Object": "4" "inscription": "fe00c5cb1d042621025b1a9bbb36d682607f0efe1182ec5109840d3fccd7ce4di0", } { "Object": "5" "inscription": "9c5c6e9b6769fd5eda0aef192d9e39a6133b28ffb3ea2ada5e40b04266485e5bi0", } { "Object": "6" "inscription": "51db0e3702a0679e8adedeab75d58bc1cabfc5f3d53ff6f59e07de62365dc124i0", } 6j4ion:1.QmP72xL8L516W516ihb8dJxkusim5SqHsqovAXfg5mhMgL Aj?=:ETH.ETH:0xdCb7C510423E8c197FD8503d161a9cc4F462086c:38300:te:0 )j'11hLbkAPQzqhohneXthhouZy16KQXmUMGGqGrRh FjDOUT:D139DEC2EABAA85DD8DC25C7F1BFA35B7657CE445794F6885F8FE83965EAC0A8 FjDOUT:D7C3ED8D9992C353263C9ECD38B3C73B2BC8D8212A456E68A6427C47B293EBC0 CjA=:AVAX.AVAX:0xe66fbc8be19bd6b60a7fdc743a1019435ee0a04f:34722901:t !22222222222222222222222222222222222222222222222222 1\ Powered by Luxor Tech \ IjGREFUND:16FF38E52C23A71AD390541B9339499F5449CFE3FACB034ABAA8AE02329B73D7 Ahttp://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.5.0"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" photoshop:ColorMode="3" photoshop:ICCProfile="M sRGB IEC61966-2.1" xmp:ModifyDate="2023-02-02T18:08:59-05:00" xmp:MetadataDate="2023-02-02T18:08:59-05:00"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="produced" stEvt:softwareAgent="Affinity Photo 1.10.6" stEvt:when="2023-02-02T18:08:59-05:00"/> </rdf:Seq> </xmpMM:History> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> .)10.)-,3:J>36F7,-@WAFLNRSR2>ZaZP`JQRO &O5-5OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC cropWhenPrintingbool http://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c148 79.164036, 2019/08/13-01:06:57 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about=M "" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmp:CreatorTool="Adobe Photoshop 21.0 (Windows)" xmp:CreateDate="2023-01-31T19:01:26+03:00" xmp:MetadataDate="2023-02-01T14:55:30+03:00" xmp:ModifyDate="2023-02-01T14:55:30+03:00" dc:format="image/jpeg" xmpMM:M InstanceID="xmp.iid:29c0327d-f970-8344-90bf-be98edc57fc5" xmpMM:DocumentID="adobe:docid:photoshop:d288bca7-b69c-fa45-9c20-27c80addcac8" xmpMM:OriginalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288" photoshop:ColorMode="3" photoshop:ICCProfile="sRGB IEC61966-2.1"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="created" stEvt:instanceID="xmp.iid:8653de18-59b9-7643-ac05-7a8bf5110288" stEvt:when="2023-01-31T19:01:26+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)"/> <rdf:li stEvt:action="saved" stM Evt:instanceID="xmp.iid:3be4a496-9a2e-5b43-b9e4-aa2e09192e28" stEvt:when="2023-01-31T21:56:39+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:f1d9e2a9-67df-2e4f-a24f-e2f0dee4dcdd" stEvt:when="2023-02-01T14:55:30+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="converted" stEvt:parameters="from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="derived" stEvtM :parameters="converted from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:29c0327d-f970-8344-90bf-be98edc57fc5" stEvt:when="2023-02-01T14:55:30+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> </rdf:Seq> </xmpMM:History> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:f1d9e2a9-67df-2e4f-a24f-e2f0dee4dcdd" stRef:documentID="adobe:docid:photoshop:7827a9af-d813-f04d-b2d1-dd034885cb76" stRef:originalDocumentID="xmp.did:8653de18-59b9-7M 643-ac05-7a8bf5110288"/> <photoshop:DocumentAncestors> <rdf:Bag> <rdf:li>72CD41DEE1DA26104D97BB46D1E733F2</rdf:li> </rdf:Bag> </photoshop:DocumentAncestors> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> Copyright (c) 1998 Hewlett-Packard Company IEC http://www.iec.ch IEC http://www.iec.ch .IEC 61966-2.1 Default RGB colour space - sRGB .IEC 61966-2.1 Default RGB colour space - sRGB ,Reference Viewing Condition in IEC61966-2.1 ,Reference Viewing Condition in IEC61966-2.1 EzTXtRaw profile type exif iTXtXML:com.adobe.xmp " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2"M <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:aux="http://ns.adobe.com/exif/1.0/aux/" aux:SerialNumber="20f3f16cb9daf2995495936439d4c8e848849c0c4f91f20e9a0939334ccb1225"/> M M M M '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"> <title>Bitcoin Face</title> <g style="isolation:isolate"><rect id="Background-2" width="1025" height="1025" style="fill:#3d4b8a" /><g id="Body-4"><path d="M504,494.22h0A412.66,412.66,0,0,1,916.61,906.88V1003a0,0,0,0,1,0,0H91.3a0,0,0,0,1,0,0V906.88A412.66,412.66,0,0,1,504,494.22Z" style="fill:#6c1908" /></g><g id="Head-11"><path d="M164.72,490.82C164.72,683.66,317,840,504.85,840S845,683.66,845,490.82" transform="translate(0.28)" style="fill:#f5b659M " /><path d="M186,490.82c0,180.79,142.76,327.34,318.87,327.34S823.71,671.61,823.71,490.82" transform="translate(0.28)" style="fill:#ecdea0" /><path d="M228.5,490.82c0,156.68,123.72,283.7,276.35,283.7s276.34-127,276.34-283.7" transform="translate(0.28)" style="fill:#e18d27" /><polygon points="506.79 774.52 503.45 774.52 503.45 510.93 781.47 510.93 781.47 514.36 506.79 514.36 506.79 774.52" style="fill:#ecdea0" /><polygon points="695.33 688.51 497.93 508.38 760.74 598.31 759.68 601.56 512.31 516.91 697.55 685.94 695.M 33 688.51" style="fill:#ecdea0" /><polygon points="609.89 753.4 503.6 513.35 506.64 511.94 612.93 751.98 609.89 753.4" style="fill:#ecdea0" /><polygon points="506.79 774.52 503.45 774.52 503.45 514.36 228.77 514.36 228.77 510.93 506.79 510.93 506.79 774.52" style="fill:#ecdea0" /><polygon points="314.91 688.51 312.69 685.94 497.93 516.91 250.56 601.56 249.5 598.31 512.31 508.38 314.91 688.51" style="fill:#ecdea0" /><polygon points="400.35 753.4 397.31 751.98 503.6 511.94 506.64 513.35 400.35 753.4" style="fill:#ecdM ea0" /><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M664.28,601.65A12.63,12.63,0,1,1,676.58,589,12.47,12.47,0,0,1,664.28,601.65Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.09,9.09,0,0,0,664.28,579.83Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M643,579.83a12.63,12.63,0,1,1,12.3-12.63A12.49,12.49,0,0,1,643,579.83ZM643,558a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,643,558Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-bM lend-mode:overlay"><path d="M738.68,579.83c-12.64,0-22.93-10.56-22.93-23.54s10.29-23.54,22.93-23.54,22.92,10.56,22.92,23.54S751.32,579.83,738.68,579.83Zm0-43.65c-10.8,0-19.59,9-19.59,20.11s8.79,20.11,19.59,20.11,19.58-9,19.58-20.11S749.48,536.18,738.68,536.18Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M685.53,536.18a12.63,12.63,0,1,1,12.3-12.63A12.47,12.47,0,0,1,685.53,536.18Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.08,9.08,0,0,0,685.53,514.36Z" transformM ="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M483.59,732.59c-12.64,0-22.93-10.56-22.93-23.54s10.29-23.54,22.93-23.54,22.93,10.56,22.93,23.54S496.23,732.59,483.59,732.59Zm0-43.65c-10.8,0-19.59,9-19.59,20.11s8.79,20.11,19.59,20.11,19.58-9,19.58-20.11S494.39,688.94,483.59,688.94Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M451.7,688.94A12.63,12.63,0,1,1,464,676.32,12.48,12.48,0,0,1,451.7,688.94M Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,451.7,667.12Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M387.93,601.65A12.63,12.63,0,1,1,400.23,589,12.48,12.48,0,0,1,387.93,601.65Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.09,9.09,0,0,0,387.93,579.83Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M324.16,558a12.63,12.63,0,1,1,12.3-12.62A12.47,12.47,0,0,1,324.16,558Zm0-21.82a9.2,9.2,0,1,0,9,9.2AM 9.09,9.09,0,0,0,324.16,536.18Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M260.38,558a12.63,12.63,0,1,1,12.3-12.62A12.47,12.47,0,0,1,260.38,558Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,260.38,536.18Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M292.27,645.3c-12.64,0-22.93-10.56-22.93-23.54s10.29-23.54,22.93-23.54,22.93,10.56,22.93,23.54S304.91,645.3,292.27,645.3Zm0-43.65c-10.8M ,0-19.59,9-19.59,20.11s8.79,20.11,19.59,20.11,19.59-9,19.59-20.11S303.07,601.65,292.27,601.65Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M324.16,667.12a12.63,12.63,0,1,1,12.3-12.63A12.48,12.48,0,0,1,324.16,667.12Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.09,9.09,0,0,0,324.16,645.3Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M600.5,667.12a12.63,12.63,0,1,1,12.3-12.63A12.48,12.48,0,0,1,600.M 5,667.12Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.08,9.08,0,0,0,600.5,645.3Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M664.28,710.76a12.63,12.63,0,1,1,12.3-12.62A12.47,12.47,0,0,1,664.28,710.76Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,664.28,688.94Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M611.13,732.59c-12.64,0-22.92-10.56-22.92-23.54s10.28-23.54,22.92-23.54,22.93,10.56,22.93,23M .54S623.77,732.59,611.13,732.59Zm0-43.65c-10.8,0-19.58,9-19.58,20.11s8.78,20.11,19.58,20.11,19.59-9,19.59-20.11S621.94,688.94,611.13,688.94Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M706.79,645.3a12.63,12.63,0,1,1,12.3-12.63A12.49,12.49,0,0,1,706.79,645.3Zm0-21.83a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,706.79,623.47Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M473,579.83a12.63,12.M 63,0,1,1,12.3-12.63A12.49,12.49,0,0,1,473,579.83ZM473,558a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,473,558Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M430.45,536.18a12.63,12.63,0,1,1,12.29-12.63A12.48,12.48,0,0,1,430.45,536.18Zm0-21.82a9.2,9.2,0,1,0,8.95,9.19A9.09,9.09,0,0,0,430.45,514.36Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M579.25,558a12.63,12.63,0,1,1,12.3-12.62A12.48,12.48M ,0,0,1,579.25,558Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,579.25,536.18Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M558,601.65A12.63,12.63,0,1,1,570.29,589,12.48,12.48,0,0,1,558,601.65Zm0-21.82a9.2,9.2,0,1,0,9,9.19A9.09,9.09,0,0,0,558,579.83Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M494.22,649.82a17.15,17.15,0,1,1,16.7-17.15A17,17,0,0,1,494.22,649.82Zm0-30.87a13.72,13.72M ,0,1,0,13.36,13.72A13.56,13.56,0,0,0,494.22,619Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M536.73,710.76A12.63,12.63,0,1,1,549,698.14,12.48,12.48,0,0,1,536.73,710.76Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,536.73,688.94Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M558,758.93a17.15,17.15,0,1,1,16.7-17.15A16.95,16.95,0,0,1,558,758.93Zm0-30.86a13.72,13.72,0,1,0,13.36,13.71A13M .55,13.55,0,0,0,558,728.07Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M366.67,710.76A12.63,12.63,0,1,1,379,698.14,12.48,12.48,0,0,1,366.67,710.76Zm0-21.82a9.2,9.2,0,1,0,9,9.2A9.09,9.09,0,0,0,366.67,688.94Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><rect x="164.72" y="490.82" width="680.24" height="21.82" transform="translate(1009.97 1003.46) rotate(-180)" style="fill:#aea4b3;opacity:0.52;mix-blend-mode:overlay" /><path d="M845.81,50M 7.17C845.81,314.33,693.53,158,505.69,158S165.57,314.33,165.57,507.17" transform="translate(0.28)" style="fill:#f5b659" /><path d="M824.55,507.17c0-180.79-142.76-327.35-318.86-327.35S186.82,326.38,186.82,507.17" transform="translate(0.28)" style="fill:#ecdea0" /><path d="M782,507.17c0-156.69-123.73-283.7-276.35-283.7s-276.35,127-276.35,283.7" transform="translate(0.28)" style="fill:#e18d27" /><polygon points="504.29 223.47 507.63 223.47 507.63 487.06 229.62 487.06 229.62 483.63 504.29 483.63 504.29 223.47" style="fiM ll:#ecdea0" /><polygon points="315.76 309.48 513.15 489.61 250.35 399.68 251.4 396.43 498.77 481.07 313.54 312.04 315.76 309.48" style="fill:#ecdea0" /><polygon points="401.19 244.58 507.49 484.63 504.44 486.05 398.15 246 401.19 244.58" style="fill:#ecdea0" /><polygon points="504.29 223.47 507.63 223.47 507.63 483.63 782.31 483.63 782.31 487.06 504.29 487.06 504.29 223.47" style="fill:#ecdea0" /><polygon points="696.17 309.48 698.39 312.04 513.15 481.07 760.53 396.43 761.58 399.68 498.77 489.61 696.17 309.48" styleM ="fill:#ecdea0" /><polygon points="610.73 244.58 613.77 246 507.49 486.05 504.44 484.63 610.73 244.58" style="fill:#ecdea0" /><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M346.26,396.34A12.63,12.63,0,1,1,334,409,12.48,12.48,0,0,1,346.26,396.34Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,346.26,418.16Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M367.51,418.16a12.63,12.63,0,1,1-12.29,12.63A12.48,12.48,0,0,1,367.51,418.16Zm0,21.82a9.2,9.2M ,0,1,0-8.95-9.19A9.08,9.08,0,0,0,367.51,440Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M271.85,418.16c12.65,0,22.93,10.56,22.93,23.54s-10.28,23.54-22.93,23.54-22.92-10.56-22.92-23.54S259.21,418.16,271.85,418.16Zm0,43.65c10.81,0,19.59-9,19.59-20.11s-8.78-20.11-19.59-20.11-19.58,9-19.58,20.11S261.05,461.81,271.85,461.81Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M325,461.81a12.63M ,12.63,0,1,1-12.3,12.62A12.48,12.48,0,0,1,325,461.81Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,325,483.63Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M527,265.4c12.64,0,22.92,10.56,22.92,23.54S539.59,312.48,527,312.48,504,301.92,504,288.94,514.3,265.4,527,265.4Zm0,43.65c10.8,0,19.58-9,19.58-20.11s-8.78-20.11-19.58-20.11-19.59,9-19.59,20.11S516.15,309.05,527,309.05Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mM ix-blend-mode:overlay"><path d="M558.83,309.05a12.63,12.63,0,1,1-12.3,12.62A12.47,12.47,0,0,1,558.83,309.05Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,558.83,330.87Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M622.61,396.34A12.63,12.63,0,1,1,610.31,409,12.47,12.47,0,0,1,622.61,396.34Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,622.61,418.16Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><M path d="M686.38,440a12.63,12.63,0,1,1-12.3,12.63A12.49,12.49,0,0,1,686.38,440Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,686.38,461.81Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M750.15,440a12.63,12.63,0,1,1-12.3,12.63A12.49,12.49,0,0,1,750.15,440Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,750.15,461.81Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M718.26,352.69c12.65,0,22.93M ,10.56,22.93,23.54s-10.28,23.54-22.93,23.54-22.92-10.56-22.92-23.54S705.62,352.69,718.26,352.69Zm0,43.65c10.81,0,19.59-9,19.59-20.11s-8.78-20.11-19.59-20.11-19.58,9-19.58,20.11S707.46,396.34,718.26,396.34Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M686.38,330.87a12.63,12.63,0,1,1-12.3,12.62A12.48,12.48,0,0,1,686.38,330.87Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,686.38,352.69Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="M opacity:0.52;mix-blend-mode:overlay"><path d="M410,330.87a12.63,12.63,0,1,1-12.3,12.62A12.48,12.48,0,0,1,410,330.87Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,410,352.69Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M346.26,287.22A12.63,12.63,0,1,1,334,299.85,12.49,12.49,0,0,1,346.26,287.22Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,346.26,309.05Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlM ay"><path d="M399.4,265.4c12.64,0,22.93,10.56,22.93,23.54S412,312.48,399.4,312.48s-22.93-10.56-22.93-23.54S386.76,265.4,399.4,265.4Zm0,43.65c10.8,0,19.59-9,19.59-20.11s-8.79-20.11-19.59-20.11-19.59,9-19.59,20.11S388.6,309.05,399.4,309.05Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M303.74,352.69a12.63,12.63,0,1,1-12.3,12.63A12.48,12.48,0,0,1,303.74,352.69Zm0,21.82a9.2,9.2,0,1,0-9-9.19A9.09,9.09,0,0,0,303.74,374.51Z" transform="translate(0.28)" styM le="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M537.58,418.16a12.63,12.63,0,1,1-12.3,12.63A12.47,12.47,0,0,1,537.58,418.16Zm0,21.82a9.2,9.2,0,1,0-9-9.19A9.08,9.08,0,0,0,537.58,440Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M580.09,461.81a12.63,12.63,0,1,1-12.3,12.62A12.48,12.48,0,0,1,580.09,461.81Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,580.09,483.63Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><gM style="opacity:0.52;mix-blend-mode:overlay"><path d="M431.29,440A12.63,12.63,0,1,1,419,452.61,12.49,12.49,0,0,1,431.29,440Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,431.29,461.81Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M452.54,396.34A12.63,12.63,0,1,1,440.25,409,12.47,12.47,0,0,1,452.54,396.34Zm0,21.82a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,452.54,418.16Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blendM -mode:overlay"><path d="M516.32,348.17a17.15,17.15,0,1,1-16.7,17.15A16.95,16.95,0,0,1,516.32,348.17Zm0,30.86A13.72,13.72,0,1,0,503,365.32,13.55,13.55,0,0,0,516.32,379Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M473.8,287.22a12.63,12.63,0,1,1-12.3,12.63A12.49,12.49,0,0,1,473.8,287.22Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,473.8,309.05Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><patM h d="M452.54,239.06a17.15,17.15,0,1,1-16.7,17.14A17,17,0,0,1,452.54,239.06Zm0,30.86a13.72,13.72,0,1,0-13.36-13.72A13.56,13.56,0,0,0,452.54,269.92Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><g style="opacity:0.52;mix-blend-mode:overlay"><path d="M643.86,287.22a12.63,12.63,0,1,1-12.3,12.63A12.49,12.49,0,0,1,643.86,287.22Zm0,21.83a9.2,9.2,0,1,0-9-9.2A9.09,9.09,0,0,0,643.86,309.05Z" transform="translate(0.28)" style="fill:#aea4b3" /></g><rect x="165.84" y="485.34" width="680.24" height="21.82" style="fillM :#aea4b3;opacity:0.52;mix-blend-mode:overlay" /></g><g id="Face-Accessory-1"><circle cx="516.52" cy="942.46" r="62.02" style="fill:#f96020" /><circle cx="516.52" cy="942.46" r="54.28" style="fill:#d74816" /><rect x="487.05" y="879.79" width="35.52" height="96.79" transform="translate(214.46 1954.46) rotate(-135)" style="fill:#ffa520" /><circle cx="510.95" cy="942.07" r="66.36" style="fill:#f96020" /><circle cx="510.94" cy="942.07" r="58.08" style="fill:#d74816" /><g id="_04CbBX.tif" data-name="04CbBX.tif"><path d="M M499.36,875.6h5.23a5.28,5.28,0,0,0,1.13.08c1.51.13,3,.28,4.52.52a52.15,52.15,0,0,1,10.43,2.72,53.73,53.73,0,0,1,32.17,33.55,52.41,52.41,0,0,1,2.21,10c.15,1.22.26,2.45.31,3.67a1.63,1.63,0,0,0,.08.82v4.31a1.52,1.52,0,0,0-.08.82,46.85,46.85,0,0,1-.52,5.14,52,52,0,0,1-5.39,16.51,53.57,53.57,0,0,1-43.89,28.75,50.62,50.62,0,0,1-8-.07,52.07,52.07,0,0,1-10.21-1.83,53.13,53.13,0,0,1-29.73-21.46,53,53,0,0,1-9.05-25.57,17,17,0,0,0-.16-1.88c0-.61,0-1.22,0-1.83,0-1.1,0-2.2,0-3.3a.77.77,0,0,0,.07-.4,53.09,53.09,0,0,1,5.25-20.37,M 53.67,53.67,0,0,1,35.15-28.6,54.71,54.71,0,0,1,9.34-1.48A8.57,8.57,0,0,0,499.36,875.6Zm-42,53.5a44.61,44.61,0,1,0,44.59-44.62A44.59,44.59,0,0,0,457.33,929.1Z" transform="translate(9.03 13)" style="fill:#ff9317" /><path d="M515.36,927.51a21.52,21.52,0,0,1,5.48,1.69,11.83,11.83,0,0,1,5.27,4.7,12,12,0,0,1,1.52,4.64,17.76,17.76,0,0,1-.27,6.21,10.87,10.87,0,0,1-6.8,8,25.21,25.21,0,0,1-5.77,1.53c-1.34.21-2.68.35-4,.44-.47,0-.47,0-.47.51v7.94c0,.5,0,.51-.51.51H504c-.56,0-.53,0-.53-.53v-7.68c0-.64.05-.57-.58-.57h-5.8c-.46,M 0-.46,0-.47.48v7.73c0,.59,0,.57-.54.57h-5.74c-.55,0-.55,0-.55-.57,0-2.51,0-5,0-7.53,0-.14,0-.27,0-.41s-.06-.29-.26-.27H476.43c-.24,0-.34-.06-.34-.31,0-1.9,0-3.79,0-5.69,0-.27.11-.35.36-.35,1.18,0,2.36,0,3.54,0a10.18,10.18,0,0,0,2.13-.27,1.92,1.92,0,0,0,1.55-1.57,11.89,11.89,0,0,0,.29-2.85V921.52c0-2.47,0-4.95,0-7.43a13.56,13.56,0,0,0-.27-2.69,2,2,0,0,0-1.71-1.68,13.39,13.39,0,0,0-2.75-.24h-2.66c-.47,0-.47,0-.47-.49,0-1.76,0-3.51,0-5.27,0-.6-.06-.56.54-.57h12.55c.57,0,.57,0,.58-.54v-7.89c0-.2.09-.28.28-.26h6c.61,0,.M 56-.06.56.54,0,2.51,0,5,0,7.53,0,.7-.05.62.64.62H503c.46,0,.46,0,.47-.49v-7.94c0-.2.07-.28.27-.26h6c.59,0,.57-.09.57.58v7.84c0,.39,0,.39.39.42a32.44,32.44,0,0,1,6.11,1,17.64,17.64,0,0,1,3.2,1.2,9.17,9.17,0,0,1,5,6.06,14.22,14.22,0,0,1,.12,7.24,10.31,10.31,0,0,1-4.17,6,18.31,18.31,0,0,1-5.34,2.53C515.55,927.36,515.43,927.34,515.36,927.51Zm-18.76,12.4c0,2.77,0,5.53,0,8.3,0,.44,0,.45.44.45h3a44,44,0,0,0,6.39-.31,15,15,0,0,0,3.66-.93,6.31,6.31,0,0,0,3.8-4.16,12,12,0,0,0,.26-6,6.32,6.32,0,0,0-2.8-4.24A9.11,9.11,0,0,0,50M 8.7,932a23.93,23.93,0,0,0-4.61-.65c-2.39-.14-4.78,0-7.17-.08-.26,0-.34.1-.32.33s0,.24,0,.36Zm0-22.76v4.25c0,1.05,0,2.09,0,3.13,0,.25,0,.37.34.37a68.58,68.58,0,0,0,7.72-.23,14.47,14.47,0,0,0,3.67-.89,5.57,5.57,0,0,0,3.45-4A11.75,11.75,0,0,0,512,915a5.49,5.49,0,0,0-3.83-4.58,18.25,18.25,0,0,0-5-.88c-2-.1-4,0-6,0h-.21c-.17,0-.25.08-.24.25s0,.27,0,.41Z" transform="translate(9.03 13)" style="fill:#ff9317" /></g><circle cx="459.8" cy="882.7" r="13.97" style="fill:#ff9317" /><circle cx="438.84" cy="861.74" r="13.97" styleM ="fill:#ff9317" /><circle cx="417.89" cy="840.79" r="13.97" style="fill:#ff9317" /><circle cx="396.93" cy="819.83" r="13.97" style="fill:#ff9317" /><circle cx="564.57" cy="882.7" r="13.97" style="fill:#f96020" /><circle cx="585.53" cy="861.74" r="13.97" style="fill:#f96020" /><circle cx="606.48" cy="840.79" r="13.97" style="fill:#f96020" /><circle cx="627.44" cy="819.83" r="13.97" style="fill:#f96020" /></g><g id="Earrings-1"><circle cx="141.68" cy="475.44" r="61.24" style="fill:#f26227" /><circle cx="141.68" cy="4M 75.44" r="53.6" style="fill:#d74b27" /><g><path d="M134.36,420.84h4.82a6.06,6.06,0,0,0,1,.07c1.4.12,2.79.26,4.18.48A48.47,48.47,0,0,1,154,423.9a49.56,49.56,0,0,1,29.69,31,47.44,47.44,0,0,1,2,9.2c.13,1.13.24,2.25.28,3.39a1.48,1.48,0,0,0,.07.75v4a1.37,1.37,0,0,0-.07.75,42.22,42.22,0,0,1-.48,4.75,47.88,47.88,0,0,1-5,15.23,49.3,49.3,0,0,1-47.87,26.48,47.84,47.84,0,0,1-9.43-1.7,49.07,49.07,0,0,1-27.44-19.8,49.09,49.09,0,0,1-8.35-23.6,12.49,12.49,0,0,0-.15-1.73c0-.57,0-1.13,0-1.69,0-1,0-2,0-3.05a.57.57,0,0,0,.07-.36,47.0M 7,47.07,0,0,1,.76-6.38,48.23,48.23,0,0,1,4.09-12.43,49.32,49.32,0,0,1,41.06-27.75A9.08,9.08,0,0,0,134.36,420.84ZM95.57,470.21A41.17,41.17,0,1,0,136.72,429,41.12,41.12,0,0,0,95.57,470.21Z" transform="translate(4.96 5.26)" style="fill:#f79421" /><path d="M149.13,468.75a19.6,19.6,0,0,1,5.05,1.56,10.88,10.88,0,0,1,4.87,4.33,11.08,11.08,0,0,1,1.4,4.28,16.24,16.24,0,0,1-.25,5.73,10.06,10.06,0,0,1-6.28,7.4,23.28,23.28,0,0,1-5.33,1.41c-1.23.19-2.46.33-3.71.4-.43,0-.44,0-.44.48v7.33c0,.46,0,.46-.47.46h-5.35c-.52,0-.49,0-.49M -.48v-7.09c0-.59.05-.53-.54-.53h-5.34c-.43,0-.43,0-.43.44,0,2.38,0,4.76,0,7.14,0,.55.05.52-.5.52H126c-.5,0-.51,0-.51-.52v-7.33c0-.17-.06-.27-.24-.25s-.26,0-.38,0H113.19c-.21,0-.31,0-.31-.29q0-2.62,0-5.25c0-.25.1-.32.34-.31h3.26a9.33,9.33,0,0,0,2-.26,1.76,1.76,0,0,0,1.43-1.45,11,11,0,0,0,.26-2.63q0-10.31,0-20.61c0-2.29,0-4.57,0-6.86a11.52,11.52,0,0,0-.24-2.48,1.88,1.88,0,0,0-1.58-1.55,12.77,12.77,0,0,0-2.54-.23c-.82,0-1.64,0-2.46,0-.43,0-.43,0-.43-.45v-4.87c0-.56,0-.52.5-.52H125c.52,0,.52,0,.52-.51v-6.9c0-.12,0-.25,M 0-.38s.09-.25.26-.24h5.54c.56,0,.51-.06.51.5,0,2.32,0,4.63,0,6.95,0,.65,0,.58.58.58h5.3c.43,0,.43,0,.43-.46v-7c0-.11,0-.22,0-.33s.07-.26.25-.25h5.54c.55,0,.52-.08.52.54V446c0,.36,0,.35.37.38a30.6,30.6,0,0,1,5.63.91,16.49,16.49,0,0,1,3,1.11,8.5,8.5,0,0,1,4.65,5.6,13.15,13.15,0,0,1,.11,6.68,9.54,9.54,0,0,1-3.85,5.55,17,17,0,0,1-4.93,2.34C149.3,468.61,149.19,468.58,149.13,468.75Zm-17.31,11.44c0,2.55,0,5.1,0,7.66,0,.41,0,.41.41.41H135a40.14,40.14,0,0,0,5.9-.28,14.22,14.22,0,0,0,3.38-.86,5.83,5.83,0,0,0,3.5-3.84,11,11,0M ,0,0,.24-5.51,5.78,5.78,0,0,0-2.58-3.91,8.31,8.31,0,0,0-2.49-1,21.66,21.66,0,0,0-4.25-.6c-2.2-.13-4.41,0-6.62-.07-.23,0-.31.09-.29.3s0,.22,0,.33Q131.81,476.5,131.82,480.19Zm0-21v3.92q0,1.44,0,2.88c0,.23.05.35.31.34a62.82,62.82,0,0,0,7.13-.2,13.67,13.67,0,0,0,3.39-.83,5.13,5.13,0,0,0,3.18-3.67,10.6,10.6,0,0,0,.15-4.45,5,5,0,0,0-3.52-4.22,16.7,16.7,0,0,0-4.64-.82c-1.86-.09-3.72,0-5.58,0H132a.2.2,0,0,0-.22.23v.38Q131.81,456,131.81,459.19Z" transform="translate(4.96 5.26)" style="fill:#f79421" /></g><circle cx="897.68"M cy="478.42" r="61.24" style="fill:#f26227" /><circle cx="897.68" cy="478.42" r="53.6" style="fill:#d74b27" /><g><path d="M890.36,423.81h4.82a5.2,5.2,0,0,0,1,.07c1.4.12,2.79.27,4.18.48a48.5,48.5,0,0,1,9.62,2.52,49.52,49.52,0,0,1,29.69,31,47.6,47.6,0,0,1,2,9.2c.13,1.13.24,2.26.28,3.39a1.52,1.52,0,0,0,.07.76v4a1.4,1.4,0,0,0-.07.76,41.81,41.81,0,0,1-.48,4.74,47.88,47.88,0,0,1-5,15.23,49.44,49.44,0,0,1-40.51,26.54,47.59,47.59,0,0,1-7.36-.06,48.57,48.57,0,0,1-9.43-1.69,49.12,49.12,0,0,1-27.44-19.8,49.17,49.17,0,0,1-8.35M -23.6,12.63,12.63,0,0,0-.15-1.74c0-.56,0-1.12,0-1.69,0-1,0-2,0-3a.62.62,0,0,0,.07-.37,47.11,47.11,0,0,1,.76-6.37,48.1,48.1,0,0,1,4.09-12.43,49.49,49.49,0,0,1,32.44-26.4,50.07,50.07,0,0,1,8.62-1.36A7.27,7.27,0,0,0,890.36,423.81Zm-38.79,49.37A41.17,41.17,0,1,0,892.72,432,41.14,41.14,0,0,0,851.57,473.18Z" transform="translate(4.96 5.26)" style="fill:#f79421" /><path d="M905.13,471.72a19.6,19.6,0,0,1,5,1.56,10.85,10.85,0,0,1,4.87,4.34,11,11,0,0,1,1.4,4.28,16.25,16.25,0,0,1-.25,5.73,10,10,0,0,1-6.28,7.39,23.28,23.28,0,0M ,1-5.33,1.41c-1.23.19-2.46.33-3.71.41-.43,0-.44,0-.44.47v7.33c0,.46,0,.47-.47.47h-5.35c-.52,0-.49,0-.49-.49v-7.09c0-.59,0-.53-.54-.53h-5.34c-.43,0-.43,0-.43.45,0,2.38,0,4.76,0,7.14,0,.54.05.52-.5.52H882c-.5,0-.51,0-.51-.53v-7.33c0-.17-.06-.26-.24-.25H869.19c-.21,0-.31-.05-.31-.28q0-2.62,0-5.25c0-.26.1-.32.34-.32h3.26a9.33,9.33,0,0,0,2-.26,1.76,1.76,0,0,0,1.43-1.45,11,11,0,0,0,.26-2.62q0-10.32,0-20.62c0-2.28,0-4.57,0-6.85a11.55,11.55,0,0,0-.24-2.49,1.88,1.88,0,0,0-1.58-1.55,12.77,12.77,0,0,0-2.54-.23h-2.46c-.43,0-.4M 3,0-.43-.45v-4.87c0-.55,0-.52.5-.52H881c.52,0,.52,0,.52-.5v-6.91c0-.12,0-.25,0-.37s.09-.26.26-.25h5.54c.56,0,.51-.05.51.5,0,2.32,0,4.64,0,6.95,0,.65-.05.58.58.58h5.3c.43,0,.43,0,.43-.45v-7c0-.11,0-.22,0-.33s.07-.26.25-.25h5.54c.55,0,.52-.07.52.55V449c0,.36,0,.36.37.38a30.62,30.62,0,0,1,5.63.92,16.45,16.45,0,0,1,3,1.1,8.5,8.5,0,0,1,4.65,5.6,13.19,13.19,0,0,1,.11,6.69,9.49,9.49,0,0,1-3.85,5.54,16.79,16.79,0,0,1-4.93,2.34C905.3,471.58,905.19,471.56,905.13,471.72Zm-17.31,11.44c0,2.55,0,5.11,0,7.66,0,.41,0,.41.41.41H891M a40.14,40.14,0,0,0,5.9-.29,14.2,14.2,0,0,0,3.38-.85,5.85,5.85,0,0,0,3.5-3.85,11,11,0,0,0,.24-5.51,5.78,5.78,0,0,0-2.58-3.91,8.31,8.31,0,0,0-2.49-1,23.27,23.27,0,0,0-4.25-.6c-2.2-.13-4.41,0-6.62-.07-.23,0-.31.09-.29.31s0,.22,0,.33C887.81,478.25,887.81,480.7,887.82,483.16Zm0-21v3.92c0,1,0,1.92,0,2.89,0,.22.05.34.31.33a62.82,62.82,0,0,0,7.13-.2,13.67,13.67,0,0,0,3.39-.83,5.11,5.11,0,0,0,3.18-3.66,10.65,10.65,0,0,0,.15-4.46,5,5,0,0,0-3.52-4.22,16.68,16.68,0,0,0-4.64-.81c-1.86-.1-3.72,0-5.58-.05H888c-.15,0-.23.07-.22.23M v.38Q887.8,458.92,887.81,462.16Z" transform="translate(4.96 5.26)" style="fill:#f79421" /></g></g><g id="Ears-2"><circle cx="133.72" cy="393.44" r="61.91" style="fill:#883a62" /><circle cx="154.36" cy="469.11" r="41.27" style="fill:#883a62" /><circle cx="181.87" cy="427.84" r="41.27" style="fill:#542f5f" /><g style="opacity:0.5700000000000001;mix-blend-mode:overlay"><line x1="126.08" y1="359.82" x2="194.87" y2="428.6" style="fill:none;stroke:#070707;stroke-miterlimit:10;stroke-width:3px" /><line x1="181.87" y1="436M .67" x2="146.52" y2="472.03" style="fill:none;stroke:#070707;stroke-miterlimit:10;stroke-width:3px" /><line x1="155.36" y1="392.49" x2="128.85" y2="419" style="fill:none;stroke:#070707;stroke-miterlimit:10;stroke-width:3px" /><line x1="164.2" y1="401.32" x2="128.85" y2="436.67" style="fill:none;stroke:#070707;stroke-miterlimit:10;stroke-width:3px" /></g><circle cx="119.85" cy="534.05" r="53.19" style="fill:#f96020" /><circle cx="119.84" cy="534.05" r="46.55" style="fill:#d74816" /><circle cx="890.1" cy="392.85" r="M 61.91" style="fill:#883a62" /><circle cx="869.47" cy="468.51" r="41.27" style="fill:#883a62" /><circle cx="841.95" cy="427.24" r="41.27" style="fill:#542f5f" /><g style="opacity:0.5700000000000001;mix-blend-mode:overlay"><line x1="897.75" y1="359.22" x2="828.96" y2="428.01" style="fill:none;stroke:#070707;stroke-miterlimit:10;stroke-width:3px" /><line x1="841.95" y1="436.08" x2="877.31" y2="471.43" style="fill:none;stroke:#070707;stroke-miterlimit:10;stroke-width:3px" /><line x1="868.47" y1="391.89" x2="894.98" y2=M "418.41" style="fill:none;stroke:#070707;stroke-miterlimit:10;stroke-width:3px" /><line x1="859.63" y1="400.73" x2="894.98" y2="436.08" style="fill:none;stroke:#070707;stroke-miterlimit:10;stroke-width:3px" /></g><circle cx="903.98" cy="533.46" r="53.19" style="fill:#f96020" /><circle cx="903.99" cy="533.46" r="46.55" style="fill:#d74816" /></g><g id="Mouth-5"><polyline points="528.78 551.11 214.79 551.11 336.9 673.22 528.78 673.22 528.78 551.11" style="fill:#4a2955" /><rect x="441.56" y="562.74" width="23.26" heigM ht="29.07" style="fill:#fdf6de" /><rect x="470.63" y="562.74" width="23.26" height="29.07" style="fill:#fdf6de" /><polygon points="406.67 603.44 429.93 562.74 429.93 562.74 406.67 562.74 406.67 603.44" style="fill:#fdf6de" /><polygon points="331.08 664.5 389.23 562.74 389.23 562.74 331.08 562.74 331.08 664.5" style="fill:#fdf6de" /><polygon points="302.84 591.81 319.45 562.74 319.45 562.74 302.84 562.74 302.84 591.81" style="fill:#fdf6de" /><polyline points="480.08 550.62 794.06 550.62 671.96 672.72 480.08 672.72 4M 80.08 550.62" style="fill:#161f26" /><rect x="545.7" y="562.24" width="23.26" height="29.07" transform="translate(1112.99 1153.56) rotate(-180)" style="fill:#fdf6de" /><rect x="516.62" y="562.24" width="23.26" height="29.07" transform="translate(1054.85 1153.56) rotate(-180)" style="fill:#fdf6de" /><rect x="487.55" y="562.24" width="23.26" height="29.07" transform="translate(996.7 1153.56) rotate(-180)" style="fill:#fdf6de" /><polygon points="602.18 602.95 578.92 562.25 578.92 562.25 602.18 562.25 602.18 602.95" stM yle="fill:#fdf6de" /><polygon points="677.77 664 619.63 562.25 619.63 562.25 677.77 562.25 677.77 664" style="fill:#fdf6de" /><polygon points="706.01 591.32 689.4 562.25 689.4 562.25 706.01 562.25 706.01 591.32" style="fill:#fdf6de" /><path d="M443.22,676.13a61.06,61.06,0,1,1,122.11,0" transform="translate(-1.66 0)" style="fill:#e63580" /></g><g id="Eyebrows-1">undefined</g><g id="Glasses-4">undefined</g><g id="Eyes-9"><polyline points="406.74 531.45 329.95 454.66 406.06 454.66" style="fill:#e1d4d1" /><polyline poiM nts="609.46 531.45 686.25 454.66 609.06 454.66" style="fill:#aea4b2" /><path d="M773.72,413a85.24,85.24,0,1,0-170.48,0" transform="translate(0.34)" style="fill:#959acc" /><circle cx="668.36" cy="413.04" r="64.79" style="fill:#727fbd" /><circle cx="667.27" cy="413.04" r="25.85" style="fill:#213f7d" /><polyline points="648.81 394.58 611.88 424.12 648.81 424.12" style="fill:#213f7d" /><circle cx="667.27" cy="413.04" r="18.47" style="fill:#131640" /><path d="M581.29,429.05V368.69A103.31,103.31,0,0,1,684.6,265.38h19.45"M transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;opacity:0.49;mix-blend-mode:overlay" /><path d="M497.89,213.26" transform="translate(0.34)" style="fill:#f1f5fa" /><path d="M241.8,413a85.25,85.25,0,0,1,170.49,0" transform="translate(0.34)" style="fill:#d0bac3" /><circle cx="347.84" cy="413.04" r="64.79" style="fill:#aaaecc" /><polyline points="365.88 394.58 402.81 424.12 365.88 424.12" style="fill:#727fbd" /><circle cx="347.41" cy="413.04" r="25.85" style="fill:M #727fbd" /><circle cx="347.41" cy="413.04" r="18.47" style="fill:#213f7d" /><circle cx="339.93" cy="409.35" r="4.39" style="fill:#eff3f9" /><path d="M431.26,427.83V367.47A103.3,103.3,0,0,0,328,264.16H308.51" transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;mix-blend-mode:overlay" /><path d="M253.74,412.68a78.86,78.86,0,1,1,157.71,0" transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;mix-blend-mode:overlay" /><patM h d="M268.9,413.43a70.69,70.69,0,1,1,141.37,0" transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;mix-blend-mode:overlay" /><path d="M296.55,413.15a57.12,57.12,0,0,1,114.24,0" transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;opacity:0.58;mix-blend-mode:overlay" /><path d="M760.73,413.67a78.86,78.86,0,0,0-157.72,0" transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:1M 0;opacity:0.49;mix-blend-mode:overlay" /><path d="M745.56,414.42a70.69,70.69,0,0,0-141.37,0" transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;opacity:0.49;mix-blend-mode:overlay" /><path d="M717.91,414.15a57.12,57.12,0,1,0-114.24,0" transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;opacity:0.49;mix-blend-mode:overlay" /><path d="M572.2,428V363.34A110.66,110.66,0,0,1,682.87,252.68h20.84" transform="translate(0.34M )" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;opacity:0.49;mix-blend-mode:overlay" /><path d="M552.15,428.56V354.18A127.29,127.29,0,0,1,679.43,226.9h24" transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;opacity:0.49;mix-blend-mode:overlay" /><path d="M440.4,427.44V362.77A110.65,110.65,0,0,0,329.74,252.11H308.9" transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;mix-blend-mode:overlayM " /><path d="M460.46,428V353.61A127.29,127.29,0,0,0,333.18,226.33h-24" transform="translate(0.34)" style="fill:none;stroke:#e9dedb;stroke-linecap:round;stroke-miterlimit:10;mix-blend-mode:overlay" /><circle cx="659.4" cy="409.28" r="4.39" style="fill:#eff3f9" /></g><g id="Nose-1"><path d="M515.59,360.78h0A111.68,111.68,0,0,1,627.27,472.46V598.54a0,0,0,0,1,0,0H403.92a0,0,0,0,1,0,0V472.46A111.68,111.68,0,0,1,515.59,360.78Z" style="fill:#883a62;opacity:0.7000000000000001" /><path d="M458.09,527.5h59.68V427.43H476.51a2M 4.44,24.44,0,0,0-23,16.17l-18.42,51.17A24.45,24.45,0,0,0,458.09,527.5Z" transform="translate(9.03 13)" style="fill:#fa8cb2" /><circle cx="479.97" cy="487.27" r="18.01" style="fill:#542f5f" /><path d="M551.83,527.9H506.16V427.83h27.25a24.45,24.45,0,0,1,23,16.17l18.42,51.17A24.45,24.45,0,0,1,551.83,527.9Z" transform="translate(9.03 13)" style="fill:#f46461" /><circle cx="548.01" cy="486.55" r="18.01" style="fill:#172027" /></g><g id="Hat-3"><rect x="362" y="88" width="144" height="135" style="fill:#542f5f" /><rect x=L "506" y="88" width="144" height="135" style="fill:#172027" /><rect x="254" y="214" width="513" height="18" style="fill:#172027" /></g></g></svg>h! ************************************************** %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz &'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC !22222222222222222222222222222222222222222222222222 filter: 0; jpegRotation: 90; fileterIntensity: 0.000000; filterMask: 0; module:1facing:0; touch: (-1.0, -1.0); AI_Scene: (-1, -1); filter: 0; jpegRotation: 90; fileterInteM nsity: 0.000000; filterMask: 0; module:1facing:0; touch: (-1.0, -1.0); AI_Scene: (-1, -1); !22222222222222222222222222222222222222222222222222 %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz &'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:GIMP="http://www.gimp.org/xmp/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" xmlns:xmp="http://ns.adobe.com/xap/1M .0/" xmpMM:DocumentID="gimp:docid:gimp:8316bccc-2935-4a47-97f1-f21f2f7511e8" xmpMM:InstanceID="xmp.iid:28ad7955-1d0f-4fbc-bcb9-31bd60dd7812" xmpMM:OriginalDocumentID="xmp.did:55268173-c20b-48e2-b1f8-9017bbc1123a" dc:Format="image/webp" GIMP:API="2.0" GIMP:Platform="Windows" GIMP:TimeStamp="1675232775611897" GIMP:Version="2.10.30" tiff:Orientation="1" xmp:CreatorTool="GIMP 2.10"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="saved" stEvt:changed="/" stEvt:instanceID="xmp.iid:82f338de-3b92-418b-9fe3-827fdfb9b654" sM tEvt:softwareAgent="Gimp 2.10 (Windows)" stEvt:when="2023-02-01T07:19:57"/> <rdf:li stEvt:action="saved" stEvt:changed="/" stEvt:instanceID="xmp.iid:ffbb490b-2e1f-4668-8b2d-a092335f6f8d" stEvt:softwareAgent="Gimp 2.10 (Windows)" stEvt:when="2023-02-01T07:26:15"/> </rdf:Seq> </xmpMM:History> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M MB <?xpacket end="w"?>h! '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC 4j2DC-L5:wyJzxVNyQCr40trKQw5W+FNsq4WPDqI0o0uMj0OLIoY= 9j7+:BTC/BTC::bc1q3f787hr38pmal87yxtpq8tng09q60ljjqqd759:0 IjG=:BNB.TWT-8C2:bnb12w6plevt6kmjdhawzea0u8gjll2nuser5p5e0x:242784547:te:0 c/Foundry USA Pool #dropgold/ FjDOUT:3FD037F22CE424042BB411720B98286FAC6E020A83E9D6013978D2F2C8B72620 IjGREFUND:024EE60586980A9C6377B66335EDB5E4D2E7FD700B153B658005BF01FDFFC9BA CjA=:ETH.ETH:0x09eeC9795f09342b8E364A5ed90B7B58057576b2:2591762:te:0 FjDOUT:9933C1878C9F90EA05ED40599FEF03C94BDD2FBE57720665F193456264205014 FjDOUT:0D13B6AAB253FB9D8D26EC64A3B53817EC43602D04D3A3BB1762FAA251597257 FjDOUT:0E16416FA49D4A73F48A3F1F01E703F8B9940194A6E2250BE15726F4EDD55F3B FjDOUT:3BE8B6600F3DE4BBC31860CB7F50D1EC38B01E0C2DFF2B576D56411345C83D8C FjDOUT:B1ED19234C9A42E00462A84F8BA59E748DC0312EDB91142B3F018BE40D7F988C Adobe Photoshop 21.0 (Windows) cropWhenPrintingbool http://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-M c148 79.164036, 2019/08/13-01:06:57 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmp:CreatorTool="Adobe Photoshop 21.0 (Windows)" xmp:CreateDate="2023-M 01-31T19:01:26+03:00" xmp:MetadataDate="2023-02-03T00:07:24+03:00" xmp:ModifyDate="2023-02-03T00:07:24+03:00" dc:format="image/jpeg" xmpMM:InstanceID="xmp.iid:10302117-c060-314b-a620-8d00379ec6e5" xmpMM:DocumentID="adobe:docid:photoshop:d9ba050b-ede3-cb49-9a2a-3b3b4140153f" xmpMM:OriginalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288" photoshop:ColorMode="3" photoshop:ICCProfile="sRGB IEC61966-2.1"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="created" stEvt:instanceID="xmp.iid:8653de18-59b9-7643-ac05-M 7a8bf5110288" stEvt:when="2023-01-31T19:01:26+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:3be4a496-9a2e-5b43-b9e4-aa2e09192e28" stEvt:when="2023-01-31T21:56:39+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:9d78ffab-bc21-5740-a28e-1a9bdf78970f" stEvt:when="2023-02-03T00:07:24+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rM df:li stEvt:action="converted" stEvt:parameters="from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="derived" stEvt:parameters="converted from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:10302117-c060-314b-a620-8d00379ec6e5" stEvt:when="2023-02-03T00:07:24+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> </rdf:Seq> </xmpMM:History> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:9d78ffab-bc21-5740-a28e-1aM 9bdf78970f" stRef:documentID="adobe:docid:photoshop:6564fe94-253e-b74d-87e1-5aecb72f57e0" stRef:originalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288"/> <photoshop:DocumentAncestors> <rdf:Bag> <rdf:li>72CD41DEE1DA26104D97BB46D1E733F2</rdf:li> </rdf:Bag> </photoshop:DocumentAncestors> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> Copyright (c) 1998 Hewlett-Packard Company IEC http://www.iec.ch IEC http://www.iec.ch .IEC 61966-2.1 Default RGB colour space - sRGB .IEC 61966-2.1 Default RGB colour space - sRGB ,Reference Viewing Condition in IEC61966-2.1 ,Reference Viewing Condition in IEC61966-2.1 Adobe Photoshop 21.0 (Windows) cropWhenPrintingbool http://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c148 79.164036, 2019/08/13-01:06:57 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:DescriptioM n rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmp:CreatorTool="Adobe Photoshop 21.0 (Windows)" xmp:CreateDate="2023-01-31T19:01:26+03:00" xmp:MetadataDate="2023-02-03T00:05:24+03:00" xmp:ModifyDate="2023-02-03T00:05:24+03:00" dc:format="image/M jpeg" xmpMM:InstanceID="xmp.iid:dcbbd076-90e3-714a-ad17-5e3aa8c29009" xmpMM:DocumentID="adobe:docid:photoshop:4b629e50-f7c3-b24e-9a19-ee62c555bbac" xmpMM:OriginalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288" photoshop:ColorMode="3" photoshop:ICCProfile="sRGB IEC61966-2.1"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="created" stEvt:instanceID="xmp.iid:8653de18-59b9-7643-ac05-7a8bf5110288" stEvt:when="2023-01-31T19:01:26+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)"/> <rdf:li stEvt:actioM n="saved" stEvt:instanceID="xmp.iid:3be4a496-9a2e-5b43-b9e4-aa2e09192e28" stEvt:when="2023-01-31T21:56:39+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:0104d6c5-35f3-0e45-a87d-5ffe488fae0f" stEvt:when="2023-02-03T00:05:24+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="converted" stEvt:parameters="from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="deM rived" stEvt:parameters="converted from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:dcbbd076-90e3-714a-ad17-5e3aa8c29009" stEvt:when="2023-02-03T00:05:24+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> </rdf:Seq> </xmpMM:History> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:0104d6c5-35f3-0e45-a87d-5ffe488fae0f" stRef:documentID="adobe:docid:photoshop:6564fe94-253e-b74d-87e1-5aecb72f57e0" stRef:originalDocumentID="xmp.did:865M 3de18-59b9-7643-ac05-7a8bf5110288"/> <photoshop:DocumentAncestors> <rdf:Bag> <rdf:li>72CD41DEE1DA26104D97BB46D1E733F2</rdf:li> </rdf:Bag> </photoshop:DocumentAncestors> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> Copyright (c) 1998 Hewlett-Packard Company IEC http://www.iec.ch IEC http://www.iec.ch .IEC 61966-2.1 Default RGB colour space - sRGB .IEC 61966-2.1 Default RGB colour space - sRGB ,Reference Viewing Condition in IEC61966-2.1 ,Reference Viewing Condition in IEC61966-2.1 Adobe Photoshop 21.0 (Windows) cropWhenPrintingbool http://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c148 79.164036, 2019/08/13-01:06:57 "> <rdf:RDF xmlns:rdf="http:/M /www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmp:CreatorTool="Adobe Photoshop 21.0 (Windows)" xmp:CreateDate="2023-01-31T19:01:26+03:00" xmp:MetadataDate="2023-02-03T00:06:11+03:00" xmp:MM odifyDate="2023-02-03T00:06:11+03:00" dc:format="image/jpeg" xmpMM:InstanceID="xmp.iid:87f68be3-dd4b-2349-adc6-93cb6ff19d58" xmpMM:DocumentID="adobe:docid:photoshop:ab05481d-a4ab-ec48-9379-8b5f15ced756" xmpMM:OriginalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288" photoshop:ColorMode="3" photoshop:ICCProfile="sRGB IEC61966-2.1"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="created" stEvt:instanceID="xmp.iid:8653de18-59b9-7643-ac05-7a8bf5110288" stEvt:when="2023-01-31T19:01:26+03:00" stEvt:softwareAgentM ="Adobe Photoshop 21.0 (Windows)"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:3be4a496-9a2e-5b43-b9e4-aa2e09192e28" stEvt:when="2023-01-31T21:56:39+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:8a22ee1f-0bb9-a544-a28a-666c77beb644" stEvt:when="2023-02-03T00:06:11+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="converted" stEvt:parameters="from application/vnd.adM obe.photoshop to image/jpeg"/> <rdf:li stEvt:action="derived" stEvt:parameters="converted from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:87f68be3-dd4b-2349-adc6-93cb6ff19d58" stEvt:when="2023-02-03T00:06:11+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> </rdf:Seq> </xmpMM:History> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:8a22ee1f-0bb9-a544-a28a-666c77beb644" stRef:documentID="adobe:docid:photoshop:6564fe94-253e-b74d-8M 7e1-5aecb72f57e0" stRef:originalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288"/> <photoshop:DocumentAncestors> <rdf:Bag> <rdf:li>72CD41DEE1DA26104D97BB46D1E733F2</rdf:li> </rdf:Bag> </photoshop:DocumentAncestors> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> Copyright (c) 1998 Hewlett-Packard Company IEC http://www.iec.ch IEC http://www.iec.ch .IEC 61966-2.1 Default RGB colour space - sRGB .IEC 61966-2.1 Default RGB colour space - sRGB ,Reference Viewing Condition in IEC61966-2.1 ,Reference Viewing Condition in IEC61966-2.1 Adobe Photoshop 21.0 (Windows) cropWhenPrintingbool http://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c148 79.164036, 2019/08/13-01:06:57 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.coM m/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmp:CreatorTool="Adobe Photoshop 21.0 (Windows)" xmp:CreateDate="2023-01-31T19:01:26+03:00" xmp:MetadataDate="2023-02-03T00:03:36+03:00" xmp:ModifyDate="2023-02-03T00:03:36+03:00" dc:format="image/jpeg" xmpMM:InstanceID="xmp.iid:ed709d07-309M 4-8241-b761-b5291cf565ed" xmpMM:DocumentID="adobe:docid:photoshop:efe1b116-b36e-144a-9454-8539f97dba60" xmpMM:OriginalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288" photoshop:ColorMode="3" photoshop:ICCProfile="sRGB IEC61966-2.1"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="created" stEvt:instanceID="xmp.iid:8653de18-59b9-7643-ac05-7a8bf5110288" stEvt:when="2023-01-31T19:01:26+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:3be4a496M -9a2e-5b43-b9e4-aa2e09192e28" stEvt:when="2023-01-31T21:56:39+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:e96dedf3-4cfd-9448-8f08-a627edb36972" stEvt:when="2023-02-03T00:03:36+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="converted" stEvt:parameters="from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="derived" stEvt:parameters="converted from applM ication/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:ed709d07-3094-8241-b761-b5291cf565ed" stEvt:when="2023-02-03T00:03:36+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> </rdf:Seq> </xmpMM:History> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:e96dedf3-4cfd-9448-8f08-a627edb36972" stRef:documentID="adobe:docid:photoshop:6564fe94-253e-b74d-87e1-5aecb72f57e0" stRef:originalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288"/> <photosM hop:DocumentAncestors> <rdf:Bag> <rdf:li>72CD41DEE1DA26104D97BB46D1E733F2</rdf:li> </rdf:Bag> </photoshop:DocumentAncestors> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> yright (c) 1998 Hewlett-Packard Company IEC http://www.iec.ch IEC http://www.iec.ch .IEC 61966-2.1 Default RGB colour space - sRGB .IEC 61966-2.1 Default RGB colour space - sRGB iewing Condition in IEC61966-2.1 ,Reference Viewing Condition in IEC61966-2.1 FjDOUT:13151B37BB35BB9F7A38D91D4216928C060DF7EBD638F840A04405BE5D6D0907 FjDOUT:4080AFC2008608D6852DB07BC067C4FCD027C8FBE4DEDBBA764333FD5DB2976B c/Foundry USA Pool #dropgold/ Adobe Photoshop 21.0 (Windows) cropWhenPrintingbool http://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c148 79.164036, 2019/08/13-01:06:57 "> <M rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmp:CreatorTool="Adobe Photoshop 21.0 (Windows)" xmp:CreateDate="2023-01-31T19:01:26+03:00" xmp:MetadataDate="2023-02M -03T00:03:04+03:00" xmp:ModifyDate="2023-02-03T00:03:04+03:00" dc:format="image/jpeg" xmpMM:InstanceID="xmp.iid:18905f51-745a-4c47-8460-219f1d59abe4" xmpMM:DocumentID="adobe:docid:photoshop:596e5cf2-93e5-3440-b90a-44c991b060c2" xmpMM:OriginalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288" photoshop:ColorMode="3" photoshop:ICCProfile="sRGB IEC61966-2.1"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="created" stEvt:instanceID="xmp.iid:8653de18-59b9-7643-ac05-7a8bf5110288" stEvt:when="2023-01-31T19:01:26+0M 3:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:3be4a496-9a2e-5b43-b9e4-aa2e09192e28" stEvt:when="2023-01-31T21:56:39+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:9cf81fec-b00c-b941-8732-c91540fb6ec2" stEvt:when="2023-02-03T00:03:04+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="converted" stEvt:parametersM ="from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="derived" stEvt:parameters="converted from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:18905f51-745a-4c47-8460-219f1d59abe4" stEvt:when="2023-02-03T00:03:04+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> </rdf:Seq> </xmpMM:History> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:9cf81fec-b00c-b941-8732-c91540fb6ec2" stRef:documentID="adobe:docid:photoM shop:6564fe94-253e-b74d-87e1-5aecb72f57e0" stRef:originalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288"/> <photoshop:DocumentAncestors> <rdf:Bag> <rdf:li>72CD41DEE1DA26104D97BB46D1E733F2</rdf:li> </rdf:Bag> </photoshop:DocumentAncestors> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> Copyright (c) 1998 Hewlett-Packard Company IEC http://www.iec.ch IEC http://www.iec.ch .IEC 61966-2.1 Default RM GB colour space - sRGB .IEC 61966-2.1 Default RGB colour space - sRGB ,Reference Viewing Condition in IEC61966-2.1 ,Reference Viewing Condition in IEC61966-2.1 DjB=:BNB.BNB:bnb1xehn2r9dhlac5x2q74ur046nknyr2cv9hnckdn:17881693:te:0 Ahttp://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.5.0"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" photoshop:ColorMode="3" photoshop:M ICCProfile="sRGB IEC61966-2.1" xmp:ModifyDate="2023-02-02T18:27:45-05:00" xmp:MetadataDate="2023-02-02T18:27:45-05:00"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="produced" stEvt:softwareAgent="Affinity Photo 1.10.6" stEvt:when="2023-02-02T18:27:45-05:00"/> </rdf:Seq> </xmpMM:History> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> .)10.)-,3:J>36F7,-@WAFLNRSR2M &O5-5OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO Ahttp://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.5.0"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" photoshop:ColorMode="3" photoshop:M ICCProfile="sRGB IEC61966-2.1" xmp:ModifyDate="2023-02-02T18:24:24-05:00" xmp:MetadataDate="2023-02-02T18:24:24-05:00"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="produced" stEvt:softwareAgent="Affinity Photo 1.10.6" stEvt:when="2023-02-02T18:24:24-05:00"/> </rdf:Seq> </xmpMM:History> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> .)10.)-,3:J>36F7,-@WAFLNRSR2M &O5-5OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO zTXtRaw profile type exif "title": "The Basics", "description": "Basic HTML and JS Game Collection", "title": "Snake", "inscription": "6edf80efbbae537b554340c31496439b57bef65357a57f21cbb547bc6287d7bfi0" "title": "Tetris", "inscription": "d7f1917503abc4ee718e871acdd3a701cd383c9b8694692d587eebba434fb1c7i0" "title": "Frogger", "inscription": "d3c366b3605b1a212b7e648446eba21215dbdb826fd20a5dc51b7421989c35e0i0" "inscription": "c2b3eec36b0949dee2729265b8aa045d0dd8b24913847fb69e04946b28e68bc2i0" "title": "Sokoban", "inscription": "0eb7edf9d10e27f443d605c3fc986564df3c4914bb33cbaeabb114f5adbd47a9i0" "title": "Puzzle Bobble", "inscription": "64aba1641a97ec70ccd21bb9e99647cae7619900ad173b2d52214754bd9c0a9ei0" "title": "Bomberman", "inscription": "c10adaa4ee52306c8db381da3e136189192fab9c160de3e9f6c56b918afec141i0" "inscription": "6cc328043c8e8837a421e2b869026363010fb10a5e50f89005187cadc429a821i0" "title": "Missile Command", "inscription": "6a3e2377103969dedcdd58fa3d55a31901e6359e8446d97700a6e0a81b71bd24i0" "title": "Doodle Jump", "inscription": "f07e36f8d23758372d076ce17019a784b72046612fd19842d45a1182d382c3d8i0" text/plain;charset=utf-8 /ViaBTC/Mined by nextone2/, KjISWAPTX:0xda0464b36aacb6955fa7c1f9732b93e81d5de4d6548c22f3eabbc3a24d019dab IjGREFUND:F7E0A1C6FD328DC8ACD55F4AA0F6D1036FF7DB0F5E71D6A352FE24B697F43189 EjC=:BNB.ETH-1C9:bnb1krqpk0d4t33uqkrajjfvgx8785tsz76axvgc8l:21290:te:0 Mined by AntPool958[ '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC '#))'#&%,1?5,.;/%&6J7;ACFGF*4MRLDR?EFC C-&-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC ",+7!!,ZZ`^^b/.<XX] -;=O''8(%%TTYbbe**<<<I/0B328DEU23D423efl@ANXVOTRKEI]?AT1($|. 6&OMFNThRTbWaz*,Bpry c/Foundry USA Pool #dropgold/ Bj@1fb878ce0ca08b1a4b8ba2e88b459710bf5afa2cfae0a88010466a6185159572 EjCs:LTC.LTC:ltc1qq6qse2etz6nk6ld3gmmdggwyydv37504fzt7rg:97473793:ss:0 Adobe Photoshop 21.0 (Windows) cropWhenPrintingbool http://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmM eta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c148 79.164036, 2019/08/13-01:06:57 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmp:CreatorToM ol="Adobe Photoshop 21.0 (Windows)" xmp:CreateDate="2023-01-31T19:01:26+03:00" xmp:MetadataDate="2023-02-03T00:05:13+03:00" xmp:ModifyDate="2023-02-03T00:05:13+03:00" dc:format="image/jpeg" xmpMM:InstanceID="xmp.iid:1c80af2b-cce7-8d42-a1d6-e9008a86e4a4" xmpMM:DocumentID="adobe:docid:photoshop:39b59a96-c41e-f441-94f6-0f310e0bab75" xmpMM:OriginalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288" photoshop:ColorMode="3" photoshop:ICCProfile="sRGB IEC61966-2.1"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="crM eated" stEvt:instanceID="xmp.iid:8653de18-59b9-7643-ac05-7a8bf5110288" stEvt:when="2023-01-31T19:01:26+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:3be4a496-9a2e-5b43-b9e4-aa2e09192e28" stEvt:when="2023-01-31T21:56:39+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:7c566773-5e1c-374f-a2fe-76c79f68ef57" stEvt:when="2023-02-03T00:05:13+03:00" stEvt:softwareAgenM t="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="converted" stEvt:parameters="from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="derived" stEvt:parameters="converted from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:1c80af2b-cce7-8d42-a1d6-e9008a86e4a4" stEvt:when="2023-02-03T00:05:13+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> </rdf:Seq> </xmpMM:History> <xmpMM:DerivedM From stRef:instanceID="xmp.iid:7c566773-5e1c-374f-a2fe-76c79f68ef57" stRef:documentID="adobe:docid:photoshop:6564fe94-253e-b74d-87e1-5aecb72f57e0" stRef:originalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288"/> <photoshop:DocumentAncestors> <rdf:Bag> <rdf:li>72CD41DEE1DA26104D97BB46D1E733F2</rdf:li> </rdf:Bag> </photoshop:DocumentAncestors> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> Copyright (c) 1998 Hewlett-Packard Company IEC http://www.iec.ch IEC http://www.iec.ch .IEC 61966-2.1 Default RGB colour space - sRGB .IEC 61966-2.1 Default RGB colour space - sRGB ,Reference Viewing Condition in IEC61966-2.1 ,Reference Viewing Condition in IEC61966-2.1 CjA=:BNB.BNB:bnb1xehn2r9dhlac5x2q74ur046nknyr2cv9hnckdn:3072145:te:0 Adobe Photoshop 21.0 (Windows) cropWhenPrintingbool http://ns.adobe.com/xap/1.0/ " id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c148 79.164036, 2019/08/13-01:06:57 "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:M stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmp:CreatorTool="Adobe Photoshop 21.0 (Windows)" xmp:CreateDate="2023-01-31T19:01:26+03:00" xmp:MetadataDate="2023-02-03T00:03:19+03:00" xmp:ModifyDate="2023-02-03T00:03:19+03:00" dc:format="image/jpeg" xmpMM:InstanceID="xmp.iid:cd82dfed-a484-bc4f-a105-fab2d276973b" xmpMM:DocumentID="adobe:docid:photoshop:851dbb5d-b5ce-924e-9f02-86c32bad17ef" xmpMM M:OriginalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288" photoshop:ColorMode="3" photoshop:ICCProfile="sRGB IEC61966-2.1"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="created" stEvt:instanceID="xmp.iid:8653de18-59b9-7643-ac05-7a8bf5110288" stEvt:when="2023-01-31T19:01:26+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:3be4a496-9a2e-5b43-b9e4-aa2e09192e28" stEvt:when="2023-01-31T21:56:39+03:00" stEvt:softwareAgent="Adobe Photoshop 21M .0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:ef2a2553-7376-db40-881f-d277a7ae9815" stEvt:when="2023-02-03T00:03:19+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> <rdf:li stEvt:action="converted" stEvt:parameters="from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="derived" stEvt:parameters="converted from application/vnd.adobe.photoshop to image/jpeg"/> <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:cd82dfedM -a484-bc4f-a105-fab2d276973b" stEvt:when="2023-02-03T00:03:19+03:00" stEvt:softwareAgent="Adobe Photoshop 21.0 (Windows)" stEvt:changed="/"/> </rdf:Seq> </xmpMM:History> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:ef2a2553-7376-db40-881f-d277a7ae9815" stRef:documentID="adobe:docid:photoshop:6564fe94-253e-b74d-87e1-5aecb72f57e0" stRef:originalDocumentID="xmp.did:8653de18-59b9-7643-ac05-7a8bf5110288"/> <photoshop:DocumentAncestors> <rdf:Bag> <rdf:li>72CD41DEE1DA26104D97BB46D1E733F2</rdf:li> </rdf:Bag> </photoshop:DoM cumentAncestors> </rdf:Description> </rdf:RDF> </x:xmpmeta> M M M M <?xpacket end="w"?> Copyright (c) 1998 Hewlett-Packard Company IEC http://www.iec.ch IEC http://www.iec.ch .IEC 61966-2.1 Default RGB colour space - sRGB .IEC 61966-2.1 Default RGB colour space - sRGB ,Reference Viewing Condition in IEC61966-2.1 ,Reference Viewing Condition in IEC61966-2.1 KjISWAPTX:0xc2db3fe8bfc51988dc335a013c970910d91e65f74f68b7287aa6354afc971918Q FjDOUT:8DB2E32CCAFE708B8A7EC5CF2B11D2A6B851B50CA18864A39B8081EFF69F69B9 FjDOUT:6DA7FBB369E6058B34DA69053488363904660F2FFDDA269D0E1CA0AC62E352E2 FjDOUT:FA5D8703AA6632BD34974F158F4640DE09E3D05332E3ED5DD5A58727EA17A953 LjJ=:ETH.USDC-B48:0x86a680ee2a0C4eed78664931AC9aeFD40E7575a8:195169989017:xdf LjJ=:ETH.USDC-B48:0x86a680ee2a0C4eed78664931AC9aeFD40E7575a8:112509347596:xdf(} text/plain;charset=utf-8 THE TRAGEDY OF ROMEO AND JULIET by William Shakespeare Scene I. A public place. Scene III. Room in Capulet Scene V. A Hall in Capulet Scene I. An open place adjoining Capulet Scene III. Friar Lawrence Scene VI. Friar Lawrence Scene I. A public Place. ene II. A Room in Capulet Scene III. Friar Lawrence Scene IV. A Room in Capulet Scene V. An open Gallery to Juliet s Chamber, overlooking the Garden. Scene I. Friar Lawrence Scene II. Hall in Capulet Scene IV. Hall in Capulet s Chamber; Juliet on the bed. Scene I. Mantua. A Street. Scene II. Friar Lawrence Scene III. A churchyard; in it a Monument belonging tM ESCALUS, Prince of Verona. MERCUTIO, kinsman to the Prince, and friend to Romeo. PARIS, a young Nobleman, kinsman to the Prince. MONTAGUE, head of a Veronese family at feud with the Capulets. LADY MONTAGUE, wife to Montague. ROMEO, son to Montague. BENVOLIO, nephew to Montague, and friend to Romeo. ABRAM, servant to Montague. BALTHASAR, servant to Romeo. CAPULET, head of a Veronese family at feud with the Montagues. LADY CAPULET, wife to CM JULIET, daughter to Capulet. TYBALT, nephew to Lady Capulet. S COUSIN, an old man. PETER, servant to Juliet SAMPSON, servant to Capulet. GREGORY, servant to Capulet. FRIAR LAWRENCE, a Franciscan. FRIAR JOHN, of the same Order. Citizens of Verona; several Men and Women, relations to both houses; Maskers, Guards, Watchmen and Attendants. SCENE. During the greater part of the Play in Verona;M Fifth Act, at Mantua. Two households, both alike in dignity, In fair Verona, where we lay our scene, From ancient grudge break to new mutiny, Where civil blood makes civil hands unclean. From forth the fatal loins of these two foes A pair of star-cross d lovers take their life; d piteous overthrows Doth with their death bury their parents The fearful passage of their death-mark And the continuance of tM Which, but their children s end, nought could remove, Is now the two hours traffic of our stage; The which, if you with patient ears attend, What here shall miss, our toil shall strive to mend. SCENE I. A public place. Enter Sampson and Gregory armed with swords and bucklers. Gregory, on my word, we No, for then we should be colliers. I mean, if we be in choler, we , while you live, draw your neck out o I strike quickly, being moved. But thou art not quickly moved to strike. A dog of the house of Montague moves me. To move is to stir; and to be valiant is to stand: therefore, if thou art moved, thou runn A dog of that house shall move me to stand. I will take the wall of any man or maid of Montague That shows thee a weak slave, for the weakest goes to the wall. True, and therefore women, being the weaker vessels, are ever thrust to the wall: therefore I will push Montague s men from the wall, and thrust his maids to the wall. The quarrel is between our masters and us their men. Tis all one, I will show myself a tyrant: when I have fought with the men I will be civil with the maids, I will cut off their heads. The heads of the maids? Ay, the heads of the maids, or their maidenheads; take it in what sense They must take it in sense that feel it. Me they shall feel while I am able to stand: and pretty piece of flesh. Tis well thou art not fish; if thou hadst, thou hadst been poor John. Draw thy tool; here comes of the house of Montagues. Enter Abram and Balthasar. My naked weapon is out: quarrel, I will back thee. How? Turn thy back and run? No, marry; I fear thee! Let us take the law of our sides; let them begin. I will frown as I pass by, and let them take it as they list. Nay, as they dare. I will bite my thumb at them, which is disgrace to them if they bear it. Do you bite your thumb at us, sir? I do bite my thumb, sir. Do you bite your thumb at us, sir? Is the law of our side if I say ay? No sir, I do not bite my thumb at you, sir; but I bite my thumb, sir. Do you quarrel, sir? Quarrel, sir? No, sir. But if you do, sir, I am for you. I serve as good a man as you. Say better; here comes one of my master Draw, if you be men. Gregory, remember thy washing blow. Part, fools! put up your swords, you know not what you do. [_Beats down their swords._] What, art thou drawn among these heartless hinds? Turn thee Benvolio, look upon thy death. I do but keep the peace, put up thy sword, Or manage it to part these men with me. What, drawn, and talk of peace? I hate the word As I hate hell, all Montagues, and thee: Have at thee, coward. Enter three or four Citizens with clubs. Clubs, bills and partisans! Strike! Beat them down! Down with the Capulets! Down with the MonM Enter Capulet in his gown, and Lady Capulet. What noise is this? Give me my long sword, ho! A crutch, a crutch! Why call you for a sword? My sword, I say! Old Montague is come, And flourishes his blade in spite of me. Enter Montague and his Lady Montague. Thou villain Capulet! Hold me not, let me go. Thou shalt not stir one foot to seek a foe. Enter Prince Escalus, with Attendants. Rebellious subjects, enemM Profaners of this neighbour-stained steel, Will they not hear? What, ho! You men, you beasts, That quench the fire of your pernicious rage With purple fountains issuing from your veins, On pain of torture, from those bloody hands Throw your mistemper d weapons to the ground And hear the sentence of your moved prince. Three civil brawls, bred of an airy word, By thee, old Capulet, and Montague, d the quiet of our streets, by their grave beseeming ornaments, To wield old partisans, in hands as old, d with peace, to part your canker If ever you disturb our streets again, Your lives shall pay the forfeit of the peace. For this time all the rest depart away: You, Capulet, shall go along with me, And Montague, come you this afternoon, To know our farther pleasure in this case, To old Free-town, our common judgement-place. Once more, on pain of death, all men depart. [_Exeunt Prince and Attendants; CapuletM , Lady Capulet, Tybalt, Citizens and Servants._] Who set this ancient quarrel new abroach? Speak, nephew, were you by when it began? Here were the servants of your adversary And yours, close fighting ere I did approach. I drew to part them, in the instant came The fiery Tybalt, with his sword prepar d defiance to my ears, He swung about his head, and cut the winds, Who nothing hurt withal, hiss While we were interchanging thrusts andM Came more and more, and fought on part and part, Till the Prince came, who parted either part. O where is Romeo, saw you him today? Right glad I am he was not at this fray. Madam, an hour before the worshipp d forth the golden window of the east, A troubled mind drave me to walk abroad, Where underneath the grove of sycamore That westward rooteth from this city side, So early walking did I see your son. Towards him I made, but he was ware of me, tole into the covert of the wood. I, measuring his affections by my own, Which then most sought where most might not be found, Being one too many by my weary self, d my humour, not pursuing his, d who gladly fled from me. Many a morning hath he there been seen, With tears augmenting the fresh morning Adding to clouds more clouds with his deep sighs; But all so soon as the all-cheering sun Should in the farthest east begin to draw The shady curtains from AuM Away from light steals home my heavy son, And private in his chamber pens himself, Shuts up his windows, locks fair daylight out And makes himself an artificial night. Black and portentous must this humour prove, Unless good counsel may the cause remove. My noble uncle, do you know the cause? I neither know it nor can learn of him. Both by myself and many other friends; But he, his own affections I will not say how true But to himself so secret and so close, So far from sounding and discovery, As is the bud bit with an envious worm Ere he can spread his sweet leaves to the air, Or dedicate his beauty to the sun. Could we but learn from whence his sorrows grow, We would as willingly give cure as know. See, where he comes. So please you step aside; ll know his grievance or be much denied. I would thou wert so happy by thyM To hear true shrift. Come, madam, let [_Exeunt Montague and Lady Montague._] Good morrow, cousin. Is the day so young? But new struck nine. Ay me, sad hours seem long. Was that my father that went hence so fast? It was. What sadness lengthens Romeo Not having that which, having, makes them short. Out of her favour where I am in love. Alas that love so gentle in his view, Should be so tyrannous and rough in proof. Alas that love, whose view is muffled still, Should, without eyes, see pathways to his will! Where shall we dine? O me! What fray was here? Yet tell me not, for I have heard it all. s much to do with hate, but more with love: Why, then, O brawling love! O loving hate! O anything, of nothing first create! O heavy lightness! serious vanity! Misshapen chaos of well-seeming forms! Feather of lead, briM ght smoke, cold fire, sick health! Still-waking sleep, that is not what it is! This love feel I, that feel no love in this. Dost thou not laugh? No coz, I rather weep. Good heart, at what? Griefs of mine own lie heavy in my breast, Which thou wilt propagate to have it prest With more of thine. This love that thou hast shown Doth add more grief to too much of mine own. Love is a smoke made M with the fume of sighs; d, a fire sparkling in lovers What is it else? A madness most discreet, A choking gall, and a preserving sweet. Soft! I will go along: And if you leave me so, you do me wrong. Tut! I have lost myself; I am not here. This is not Romeo, he Tell me in sadness who is that you love? What, shall I groan and tellM Groan! Why, no; but sadly tell me who. Bid a sick man in sadness make his will, d to one that is so ill. In sadness, cousin, I do love a woman. d so near when I suppos A right good markman, and she A right fair mark, fair coz, is soonest hit. Well, in that hit you miss: she s arrow, she hath Dian And in strong proof of chastity well arM s weak childish bow she lives uncharm She will not stay the siege of loving terms encounter of assailing eyes, Nor ope her lap to saint-seducing gold: s rich in beauty, only poor That when she dies, with beauty dies her store. Then she hath sworn that she will still live chaste? She hath, and in that sparing makes huge waste; d with her severity, Cuts beauty off from all posterity. She is too fair, too wise; wisely toM To merit bliss by making me despair. She hath forsworn to love, and in that vow Do I live dead, that live to tell it now. d by me, forget to think of her. O teach me how I should forget to think. By giving liberty unto thine eyes; Examine other beauties. To call hers, exquisite, in question more. These happy masks that kiss fair ladies Being black, puts us in mind they hide the fair; He that is strucken blind cannot M The precious treasure of his eyesight lost. Show me a mistress that is passing fair, What doth her beauty serve but as a note Where I may read who pass d that passing fair? Farewell, thou canst not teach me to forget. ll pay that doctrine, or else die in debt. Enter Capulet, Paris and Servant. But Montague is bound as well as I, In penalty alike; and tis not hard, I think, For men so old as we to keep the peace. Of honourable reckoning are you both, But now my lord, what say you to my suit? er what I have said before. My child is yet a stranger in the world, She hath not seen the change of fourteen years; Let two more summers wither in their pride Ere we may think her ripe to be a bride. Younger than she are happy mothers made. d are those so early made. The earth hath swallowed all my hopes but she, She is the hopeful lady of my earth: But woo her, gentle Paris, get her heart, My will to her consent is but a part; And she agree, within her scope of choice Lies my consent and fair according voice. This night I hold an old accustom Whereto I have invited many a guest, Such as I love, and you among the store, One more, most welcome, makes my number more. At my poor house look to behold this night Earth-treading stars that make dark heaven light: Such comfort as do lusty young men feel Of limping winter treads, even such delight Among fresh female buds shall you this night Inherit at my house. Hear all, all see, And like her most whose merit most shall be: Which, on more view of many, mine, being one, May stand in number, though in reckoning none. Come, go with me. Go, sirrah, trudge about Through fair Verona; find those persons out Whose names are written there, [_gives a paper_] and to them say, My house and welcome on their pleasure stay. eunt Capulet and Paris._] Find them out whose names are written here! It is written that the shoemaker should meddle with his yard and the tailor with his last, the fisher with his pencil, and the painter with his nets; but I am sent to find those persons whose names are here writ, and can never find what names the writing person hath here writ. I must to the learned. In good Enter Benvolio and Romeo. Tut, man, one fire burns out another Turn giddy, and be holp by backward turning; One desperate grief cures with another Take thou some new infection to thy eye, And the rank poison of the old will die. Your plantain leaf is excellent for that. For what, I pray thee? For your broken shin. Why, Romeo, art thou mad? Not mad, but bound more than a madman is: Shut up in prison, kept without my food, God-den, good fellow. go-den. I pray, sir, can you read? Ay, mine own fortune in my misery. Perhaps you have learned it without book. But I pray, can you read anything you see? Ay, If I know the letters and the language. Ye say honestly, rest you merry! Stay, fellow; I can read. [_He reads the letter._] _Signior Martino and his wife and daughters; County Anselmo and his beauteous sisters; The lady widow of Utruvio; Signior Placentio and his loveM Mercutio and his brother Valentine; Mine uncle Capulet, his wife, and daughters; My fair niece Rosaline and Livia; Signior Valentio and his cousin Tybalt; Lucio and the lively Helena. _ A fair assembly. [_Gives back the paper_] Whither should they come? Indeed I should have ask ll tell you without asking. My master is M the great rich Capulet, and if you be not of the house of Montagues, I pray come and crush a cup of wine. Rest you merry. At this same ancient feast of Capulet Sups the fair Rosaline whom thou so lov With all the admired beauties of Verona. Go thither and with unattainted eye, Compare her face with some that I shall show, And I will make thee think thy swan a crow. When the devout religion of mine eye Maintains such falsehood, then turn tears to fire; ese who, often drown Transparent heretics, be burnt for liars. One fairer than my love? The all-seeing sun er saw her match since first the world begun. Tut, you saw her fair, none else being by, d with herself in either eye: But in that crystal scales let there be weigh s love against some other maid That I will show you shining at this feast, And she shall scant show well that now shows best. ll go along, no such sight M But to rejoice in splendour of my own. SCENE III. Room in Capulet Enter Lady Capulet and Nurse. s my daughter? Call her forth to me. Now, by my maidenhead, at twelve year old, I bade her come. What, lamb! What ladybird! s this girl? What, Juliet! Madam, I am here. What is your will? . Nurse, give leave awhile, We must talk in secret. Nurse, come back again, Thou knowest my daughter Faith, I can tell her age unto an hour. ll lay fourteen of my teeth, And yet, to my teen be it spoken, I have but four, She is not fourteen. How long is it now A fortnight and odd days. Even or odd, of all days in the year, Lammas Eve at night shall she be fourteen. God rest all Christian souls! Were of an age. Well, Susan is with God; She was too good for me. But as I said, On Lammas Eve at night shall she be fourteen; That shall she, marry; I remember it well. Tis since the earthquake now eleven years; I never shall forget it Of all the days of the year, upon that day: For I had then laid wormwood to my dug, Sitting in the sun under the dovehouse wall; My lord and you werM Nay, I do bear a brain. But as I said, When it did taste the wormwood on the nipple Of my dug and felt it bitter, pretty fool, To see it tetchy, and fall out with the dug! Shake, quoth the dovehouse: twas no need, I trow, And since that time it is eleven years; For then she could stand alone; nay, by th She could have run and waddled all about; For even the day before she broke her brow, And then my husband, God be with his soul! dost thou fall upon thy face? Thou wilt fall backward when thou hast more wit; Wilt thou not, Jule? and, by my holidame, The pretty wretch left crying, and said To see now how a jest shall come about. I warrant, and I should live a thousand years, I never should forget it. Wilt thou not, Jule? And, pretty fool, it stinted, and said Enough of this; I pray thee hold thy peace. Yes, madam, yet I cannot choM To think it should leave crying, and say And yet I warrant it had upon it brow A bump as big as a young cockerel A perilous knock, and it cried bitterly. Thou wilt fall backward when thou comest to age; Wilt thou not, Jule? it stinted, and said And stint thou too, I pray thee, Nurse, say I. Peace, I have done. God mark thee to his grace Thou wast the prettiest babe that e And I might live to see thee married once, I have my wish. Marry, that marry is the very theme I came to talk of. Tell me, daughter Juliet, How stands your disposition to be married? It is an honour that I dream not of. An honour! Were not I thine only nurse, I would say thou hadst suck d wisdom from thy teat. Well, think of marriage now: younger than you, Here in Verona, ladies of esteem, Are made already mothers. By my count other much upon these years That you are now a maid. Thus, then, in brief; The valiant Paris seeks you for his love. A man, young lady! Lady, such a man s summer hath not such a flower. s a flower, in faith a very flower. What say you, can you love the gentleman? This night you shall behold him at our feast; er the volume of young Paris And find delight writ there with beaM Examine every married lineament, And see how one another lends content; d in this fair volume lies, Find written in the margent of his eyes. This precious book of love, this unbound lover, To beautify him, only lacks a cover: The fish lives in the sea; and For fair without the fair within to hide. s eyes doth share the glory, That in gold clasps locks in the golden story; So shall you share all that he doth possess, By having him, makiM ng yourself no less. No less, nay bigger. Women grow by men. Speak briefly, can you like of Paris ll look to like, if looking liking move: But no more deep will I endart mine eye Than your consent gives strength to make it fly. Madam, the guests are come, supper served up, you called, my young lady asked for, the Nurse cursed in the pantry, and everything in extremity. I must hence to wait, I beseech you follow straight. Juliet, the County stays. Go, girl, seek happy nights to happy days. Enter Romeo, Mercutio, Benvolio, with five or six Maskers; Torch-bearers and others. What, shall this speech be spoke for our excuse? Or shall we on without apology? The date is out of such prolixity: ll have no Cupid hoodwink s painted bow of lath, like a crow-keeper; Nor no without-book prologue, faintly spoke After the prompter, for our entrance: But let them measure us by what they will, ll measure them a measure, and be gone. Give me a torch, I am not for this ambling; Being but heavy I will bear the light. Nay, gentle Romeo, we must have you dance. Not I, believe me, you have dancing shoes, With nimble soles, I have a soul of lead So stakes me to the ground I cannot move. You are a lover, borM And soar with them above a common bound. I am too sore enpierced with his shaft To soar with his light feathers, and so bound, I cannot bound a pitch above dull woe. s heavy burden do I sink. And, to sink in it, should you burden love; Too great oppression for a tender thing. Is love a tender thing? It is too rough, Too rude, too boisterous; and it pricks like thorn. If love be rough with you, be rough with love; or pricking, and you beat love down. Give me a case to put my visage in: [_Putting on a mask._] A visor for a visor. What care I What curious eye doth quote deformities? Here are the beetle-brows shall blush for me. Come, knock and enter; and no sooner in But every man betake him to his legs. A torch for me: let wantons, light of heart, Tickle the senseless rushes with their heels; d with a grandsire phrase, ll be a candle-holder and look on, er so fair, and I am done. s the mouse, the constable ll draw thee from the mire Or save your reverence love, wherein thou stickest Up to the ears. Come, we burn daylight, ho. I mean sir, in delay We waste our lights in vain, light lights by day. Take our good meaning, for our judgment sits Five times in that ere once in our five wits. And we mean well in going to this mask; I dreamt a dream tonight. Well what was yours? That dreamers often lie. In bed asleep, while they do dream things true. O, then, I see Queen Mab hath been with you. midwife, and she comes In shape no bigger than an agate-stone On the fore-finger of an alderman, Drawn with a team of little atomies s noses as they lie asleep: Her waggon-spokes made M The cover, of the wings of grasshoppers; Her traces, of the smallest spider The collars, of the moonshine s bone; the lash, of film; Her waggoner, a small grey-coated gnat, Not half so big as a round little worm d from the lazy finger of a maid: Her chariot is an empty hazelnut, Made by the joiner squirrel or old grub, And in this state she gallops night by night brains, and then they dream of love; knees, that dream on curtsies straight; fingers, who straight dream on fees; lips, who straight on kisses dream, Which oft the angry Mab with blisters plagues, Because their breaths with sweetmeats tainted are: Sometime she gallops o And then dreams he of smelling out a suit; And sometime comes she with a tithe-pig s nose as a lies asleep, of another benefice: Sometime she driveth o And then dreams he of cutting foreign throats, Of breaches, ambuscados, Spanish blades, Of healths five fathom deep; and then anon Drums in his ear, at which he starts and wakes; And, being thus frighted, swears a prayer or two, And sleeps again. This is that very Mab That plats the manes of horses in the night; And bakes the elf-locks in foul sluttish hairs, Which, once untangled, much misfortune bodes: This is the hag, when maids lieM That presses them, and learns them first to bear, Making them women of good carriage: Peace, peace, Mercutio, peace, True, I talk of dreams, Which are the children of an idle brain, Begot of nothing but vain fantasy, Which is as thin of substance as the air, And more inconstant than the wind, who wooes Even now the frozen bosom of the north, d, puffs away from thence, Turning his side to the dew-droppiM This wind you talk of blows us from ourselves: Supper is done, and we shall come too late. I fear too early: for my mind misgives Some consequence yet hanging in the stars, Shall bitterly begin his fearful date s revels; and expire the term Of a despised life, clos By some vile forfeit of untimely death. But he that hath the steerage of my course Direct my suit. On, lusty gentlemen! Musicians waiting. Enter Servants. s Potpan, that he helps not to take away? He shift a trencher! He scrape a trencher! When good manners shall lie all in one or two men Away with the join-stools, remove the court-cupboard, look to the plate. Good thou, save me a piece of marchpane; and as thou loves me, let the porter let in Susan Grindstone and Nell. AntonM You are looked for and called for, asked for and sought for, in the We cannot be here and there too. Cheerly, boys. Be brisk awhile, and the longer liver take all. Enter Capulet, &c. with the Guests and Gentlewomen to the Maskers. Welcome, gentlemen, ladies that have their toes d with corns will have a bout with you. Ah my mistresses, which of you all to dance? She that makes dainty, ll swear hath corns. Am I come near ye now? Welcome, gentlemen! I have seen the day That I have worn a visor, and could tell A whispering tale in a fair lady Such as would please; You are welcome, gentlemen! Come, musicians, play. A hall, a hall, give room! And foot it, girls. [_Music plays, and they dance._] More light, you knaves; and turn the tables up, And quench the fire, the room is grown too hot. d-for sport comes well. Nay sit, nay sit, good cousin Capulet, For you and I are past our dancing days; t now since last yourself and I r Lady, thirty years. Tis since the nuptial of Lucentio, Come Pentecost as quickly as it will, Some five and twenty years; and then we mask tis more, his son is elder, sir; Will you tell me that? His son was but a ward two years ago. What lady is that, which doth enrich the hand O, she doth teach the torches to burn bright! It seems she hangs upon the cheek of night As a rich jewel in an Ethiop Beauty too rich for use, for earth too dear! So shows a snowy dove trooping with crows er her fellows shows. ll watch her place of stand, g hers, make blessed my rude hand. Did my heart love till now? Forswear it, sight! er saw true beauty till this night. This by his voice, should be a Montague. Fetch me my rapier, boy. What, dares the slave d with an antic face, To fleer and scorn at our solemnity? Now by the stock and honour of my kin, To strike him dead I hold it not a sin. Why how now, kinsman! Wherefore storm you so? Uncle, this is a Montague, our foe; is hither come in spite, To scorn at our solemnity this night. Tis he, that villain Romeo. Content thee, gentle coz, let him alone, A bears him like a portly gentleman; And, to say truth, Verona brags of him To be a virtuous and well-govern I would not for the wealth of all the town Here in my house do him disparagement. Therefore be patient, take no note of him, It is my will; the which if thou respect, Show a fair presence and put ofM An ill-beseeming semblance for a feast. It fits when such a villain is a guest: What, goodman boy! I say he shall, go to; Am I the master here, or you? Go to. ll not endure him! God shall mend my soul, ll make a mutiny among my guests! You will set cock-a-hoop, you You are a saucy boy. Is This trick may chance M to scathe you, I know what. You must contrary me! Marry, Well said, my hearts! You are a princox; go: More light, more light! ll make you quiet. What, cheerly, my hearts. Patience perforce with wilful choler meeting Makes my flesh tremble in their different greeting. I will withdraw: but this intrusion shall, Now seeming sweet, convert to bitter gall. [_To Juliet._] If I profane with my unworthiest hand This holy shrine, theM gentle sin is this, My lips, two blushing pilgrims, ready stand To smooth that rough touch with a tender kiss. Good pilgrim, you do wrong your hand too much, Which mannerly devotion shows in this; For saints have hands that pilgrims And palm to palm is holy palmers Have not saints lips, and holy palmers too? Ay, pilgrim, lips that they must use in prayer. O, then, dear saint, let lips do what hands do: They pray, grant thou, lest faithM Saints do not move, though grant for prayers Then move not while my prayer Thus from my lips, by thine my sin is purg Then have my lips the sin that they have took. Sin from my lips? O trespass sweetly urg Give me my sin again. You kiss by the book. Madam, your mother craves a word with you. Her mother is the ladyM And a good lady, and a wise and virtuous. d her daughter that you talk I tell you, he that can lay hold of her Shall have the chinks. O dear account! My life is my foe Away, be gone; the sport is at the best. Ay, so I fear; the more is my unrest. Nay, gentlemen, prepare not to be gone, We have a trifling foolish banquet towards. en so? Why then, I thank you all; I thank you, honest gentM More torches here! Come on then, let Ah, sirrah, by my fay, it waxes late, [_Exeunt all but Juliet and Nurse._] Come hither, Nurse. What is yond gentleman? The son and heir of old Tiberio. s he that now is going out of door? Marry, that I think be young Petruchio. s he that follows here, that would not dance? Go ask his name. If he be married, is like to be my wedding bed. His name is Romeo, and a Montague, The only son of your great enemy. My only love sprung from my only hate! Too early seen unknown, and known too late! Prodigious birth of love it is to me, That I must love a loathed enemy. [_One calls within, s away, the strangers all are gone. Now old desire doth in his deathbed lie, And young affection gapes to be his heir; That fair for which love groan d for and would die, With tender Juliet match Alike bewitched by the charm of looks; But to his foe suppos s sweet bait from fearful hooks: Being held a foe, he may not have access To breathe such vows as lovers use to swear; And she as much in loM ve, her means much less To meet her new beloved anywhere. But passion lends them power, time means, to meet, Tem