Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions scripts/compare-claim-mirror.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Compare cambiatus.cm `mbrclaim` with Postgres member_action_claims, one account at a time.
# Usage: scripts/compare-claim-mirror.sh <chain_url> <pgdatabase> <account>...
# Exit 0 = identical for every account.
set -uo pipefail
CHAIN=$1; DB=$2; shift 2
fail=0
for acc in "$@"; do
chain=$(curl -s -X POST "$CHAIN/v1/chain/get_table_rows" \
-d "{\"code\":\"cambiatus.cm\",\"scope\":\"$acc\",\"table\":\"mbrclaim\",\"json\":true,\"limit\":1000}" \
| jq -c '[.rows[] | {action_id: (.action_id|tonumber), pending, approved, last_rejected_at}] | sort_by(.action_id)')
db=$(psql -X -At -d "$DB" -c "select coalesce(json_agg(json_build_object('action_id', action_id, 'pending', pending, 'approved', approved, 'last_rejected_at', coalesce(extract(epoch from last_rejected_at)::bigint, 0)) order by action_id), '[]') from member_action_claims where account = '$acc'" | jq -c '.')
if [ "$chain" = "$db" ]; then echo "OK $acc"; else echo "DIFF $acc"; echo " chain: $chain"; echo " db: $db"; fail=1; fi
done
exit $fail
57 changes: 57 additions & 0 deletions src/claim_mirror.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Mirror of cambiatus.cm `mbrclaim` (one row per member + action) in
// member_action_claims. Applies the contract's own arithmetic; never decides anything.
// Callers pass the block-level `db` (demux's transaction), never a nested withTransaction.
const { toUTC } = require('./dates')

async function claimOpened (db, account, actionId, blockNumber) {
await db.instance.none(
`INSERT INTO member_action_claims (account, action_id, pending, approved, updated_block)
VALUES ($1, $2, 1, 0, $3)
ON CONFLICT (account, action_id) DO UPDATE
SET pending = member_action_claims.pending + 1, updated_block = $3, updated_at = now()`,
[account, Number(actionId), blockNumber]
)
}

// The contract stores whole seconds (truncated). The column is timestamp(0), which ROUNDS,
// so a block at hh:mm:ss.500 would land one second late. Truncate before writing.
function wholeSeconds (date) {
return new Date(Math.floor(date.getTime() / 1000) * 1000)
}

async function claimClosed (db, account, actionId, approved, blockTimestamp, blockNumber) {
const rejectedAt = approved ? null : toUTC(wholeSeconds(blockTimestamp))
await db.instance.none(
`INSERT INTO member_action_claims (account, action_id, pending, approved, last_rejected_at, updated_block)
VALUES ($1, $2, 0, $3, $4, $5)
ON CONFLICT (account, action_id) DO UPDATE
SET pending = GREATEST(member_action_claims.pending - 1, 0),
approved = member_action_claims.approved + $3,
last_rejected_at = COALESCE($4, member_action_claims.last_rejected_at),
updated_block = $5, updated_at = now()`,
[account, Number(actionId), approved ? 1 : 0, rejectedAt, blockNumber]
)
}

async function rewarded (db, account, actionId, blockNumber) {
await db.instance.none(
`INSERT INTO member_action_claims (account, action_id, pending, approved, updated_block)
VALUES ($1, $2, 0, 1, $3)
ON CONFLICT (account, action_id) DO UPDATE
SET approved = member_action_claims.approved + 1, updated_block = $3, updated_at = now()`,
[account, Number(actionId), blockNumber]
)
}

async function seeded (db, account, row, blockNumber) {
const rejectedAt = Number(row.last_rejected_at) > 0 ? toUTC(new Date(Number(row.last_rejected_at) * 1000)) : null
await db.instance.none(
`INSERT INTO member_action_claims (account, action_id, pending, approved, last_rejected_at, updated_block)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (account, action_id) DO UPDATE
SET pending = $3, approved = $4, last_rejected_at = $5, updated_block = $6, updated_at = now()`,
[account, Number(row.action_id), Number(row.pending), Number(row.approved), rejectedAt, blockNumber]
)
}

module.exports = { claimOpened, claimClosed, rewarded, seeded }
39 changes: 39 additions & 0 deletions src/config/lane.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Claim-limits lane config: syncs a private lane nodeos (contracts repo `make lane-node-fresh`,
// port 8899) into an isolated `cambiatus_lane_cpa` Postgres DB. Never the shared chain or DB.
// Run with: NODE_ENV=lane node src/app.js
console.log('Loaded Lane configs')

const forbidden = ['cambiatus_local', 'cambiatus_dev', 'cambiatus_mirror', 'cambiatus_review']
const url = process.env.CHAIN_URL || 'http://127.0.0.1:8899'
if (url.includes(':8888') || forbidden.includes(process.env.PGDATABASE || '')) {
throw new Error('lane config refuses the shared chain (:8888) and the shared DBs')
}

module.exports = {
blockchain: {
contract: {
token: 'cambiatus.tk',
community: 'cambiatus.cm',
escrow: 'cambiatus.es'
},
initialBlock: 1,
url
},
db: {
// No password key: node-pg must NOT send an empty-string password or Postgres.app
// rejects it under trust auth ("failed to verify trust authentication").
user: 'postgres',
host: 'localhost',
port: 5432,
database: process.env.PGDATABASE || 'cambiatus_lane_cpa',
schema: 'public'
},
http: {
port: Number(process.env.HEALTH_PORT || 3011)
},
sentry: {
dsn: '',
environment: 'lane',
attachStacktrace: true
}
}
12 changes: 11 additions & 1 deletion src/updaters.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ const {
verifyClaim,
claimAction,
upsertRole,
assignRole
assignRole,
setActLimit,
seedMbrClaim
} = require('./updaters/community.js')
const {
createToken,
Expand Down Expand Up @@ -137,6 +139,14 @@ const updaters = [
actionType: `${config.blockchain.contract.community}::assignroles`,
updater: assignRole
},
{
actionType: `${config.blockchain.contract.community}::setactlimit`,
updater: setActLimit
},
{
actionType: `${config.blockchain.contract.community}::seedmbrclm`,
updater: seedMbrClaim
},
// ======== Token
{
actionType: `${config.blockchain.contract.token}::create`,
Expand Down
39 changes: 38 additions & 1 deletion src/updaters/community.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
const config = require(`../config/${process.env.NODE_ENV || 'dev'}`)
const { resolveClaimId, resolveCreatedActionId, resolveCreatedObjectiveId, ResolveError } = require('../chain')
const { toUTC } = require('../dates')
const claimMirror = require('../claim_mirror')

// S5a-es (plan §9): an Elm purchase of a batch offer takes its units from the live batch
// through the backend's frozen DB function. Transient database errors are retried; anything
Expand Down Expand Up @@ -706,6 +707,8 @@ async function reward (db, payload, blockInfo, context) {
})
if (Number(existing) > 0) return

await claimMirror.rewarded(db, payload.data.receiver, payload.data.action_id, blockInfo.blockNumber)

// Collect the action
return db.actions
.findOne(payload.data.action_id)
Expand Down Expand Up @@ -826,6 +829,7 @@ async function claimAction (db, payload, blockInfo, context) {
await db.claims
.insert(data)
.catch(e => logError('Something went wrong while inserting a claim', e))
await claimMirror.claimOpened(db, payload.data.maker, payload.data.action_id, blockInfo.blockNumber)
}

async function verifyClaim (db, payload, blockInfo, context) {
Expand Down Expand Up @@ -911,6 +915,13 @@ async function verifyClaim (db, payload, blockInfo, context) {
}

await db.claims.update(claim.id, { status })

// Mirror the contract's mbrclaim close. Only the first pending → final transition
// counts; on a replay claim.status is already final and this is skipped. Not gated on
// usages: the contract counts every action.
if (claim.status === 'pending' && status !== 'pending') {
await claimMirror.claimClosed(db, claim.claimer_id, claim.action_id, status === 'approved', blockInfo.timestamp, blockInfo.blockNumber)
}
console.log(`Cambiatus >>> Claim Verification: Status Updated to: ${status}`)

// Decrement usages_left exactly once, on the first pending -> resolved transition. On a
Expand Down Expand Up @@ -949,6 +960,30 @@ async function upsertRole (db, payload, blockInfo, _context) {
}
}

async function setActLimit (db, payload, blockInfo, _context) {
console.log('Cambiatus >>> Set action claim limit', blockInfo.blockNumber)
const actionId = Number(payload.data.action_id)
const max = Number(payload.data.max_per_account)

const action = await db.actions.findOne({ id: actionId })
if (action == null) {
// The create's id resolution failed earlier; writing now would guess. Skip + page.
throw new ResolveError(`setactlimit: action ${actionId} not indexed (block ${blockInfo.blockNumber})`)
}

const data = max === 0
? { max_claims_per_account: null, reclaim_cooldown_seconds: null }
: { max_claims_per_account: max, reclaim_cooldown_seconds: Number(payload.data.reclaim_cooldown) }
await db.actions.update({ id: actionId }, data)
}

async function seedMbrClaim (db, payload, blockInfo, _context) {
console.log('Cambiatus >>> Seed member claim counts', blockInfo.blockNumber)
for (const row of payload.data.rows) {
await claimMirror.seeded(db, payload.data.member, row, blockInfo.blockNumber)
}
}

async function assignRole (db, payload, blockInfo, _context) {
console.log('Cambiatus >>> Assign Role', blockInfo.blockNumber)

Expand Down Expand Up @@ -1024,5 +1059,7 @@ module.exports = {
verifyClaim,
claimAction,
upsertRole,
assignRole
assignRole,
setActLimit,
seedMbrClaim
}
Loading